MINOR: ssl: Add "update ssl ocsp-response" cli command

The new "update ssl ocsp-response <certfile>" CLI command allows to
update the stored OCSP response for a given certificate. It relies on
the http_client which is used to send an HTTP request to the OCSP
responder whose URI can be extracted from the certificate.
This command won't work for a certificate that did not have a stored
OCSP response yet.
diff --git a/doc/management.txt b/doc/management.txt
index c93bff5..ae3ab9a 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -3820,6 +3820,19 @@
   information available at the trace point. The first level above "quiet" is
   set by default.
 
+update ssl ocsp-response <certfile>
+  Create an OCSP request for the specified <certfile> and send it to the OCSP
+  responder whose URI should be specified in the "Authority Information Access"
+  section of the certificate. Only the first URI is taken into account. The
+  OCSP response that we should receive in return is then checked and inserted
+  in the local OCSP response tree. This command will only work for certificates
+  that already had a stored OCSP response, either because it was provided
+  during init or if it was previously set through the "set ssl cert" or "set
+  ssl ocsp-response" commands.
+  If the received OCSP response is valid and was properly inserted into the
+  local tree, its contents will be displayed on the standard output. The format
+  is the same as the one described in "show ssl ocsp-response".
+
 
 9.4. Master CLI
 ---------------
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 44f515c..d7c2430 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -83,6 +83,7 @@
 #include <haproxy/vars.h>
 #include <haproxy/xxhash.h>
 #include <haproxy/istbuf.h>
+#include <haproxy/http_client.h>
 
 
 /* ***** READ THIS before adding code here! *****
@@ -8126,17 +8127,285 @@
 	appctx->svcctx = NULL;
 	return 1;
 }
+
+struct ocsp_cli_ctx {
+	struct httpclient *hc;
+	struct ckch_data *ckch_data;
+	uint flags;
+	uint do_update;
+};
+
+const struct http_hdr ocsp_request_hdrs[] = {
+	{ IST("Content-Type"), IST("application/ocsp-request") },
+	{ IST_NULL, IST_NULL }
+};
+
+void cli_ocsp_res_stline_cb(struct httpclient *hc)
+{
+	struct appctx *appctx = hc->caller;
+	struct ocsp_cli_ctx *ctx;
+
+	if (!appctx)
+		return;
+
+	ctx = appctx->svcctx;
+	ctx->flags |= HC_F_RES_STLINE;
+	appctx_wakeup(appctx);
+}
+
+void cli_ocsp_res_headers_cb(struct httpclient *hc)
+{
+	struct appctx *appctx = hc->caller;
+	struct ocsp_cli_ctx *ctx;
+
+	if (!appctx)
+		return;
+
+	ctx = appctx->svcctx;
+	ctx->flags |= HC_F_RES_HDR;
+	appctx_wakeup(appctx);
+}
+
+void cli_ocsp_res_body_cb(struct httpclient *hc)
+{
+	struct appctx *appctx = hc->caller;
+	struct ocsp_cli_ctx *ctx;
+
+	if (!appctx)
+		return;
+
+	ctx = appctx->svcctx;
+	ctx->flags |= HC_F_RES_BODY;
+	appctx_wakeup(appctx);
+}
+
+void cli_ocsp_res_end_cb(struct httpclient *hc)
+{
+	struct appctx *appctx = hc->caller;
+	struct ocsp_cli_ctx *ctx;
+
+	if (!appctx)
+		return;
+
+	ctx = appctx->svcctx;
+	ctx->flags |= HC_F_RES_END;
+	appctx_wakeup(appctx);
+}
+
+static int cli_parse_update_ocsp_response(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	int errcode = 0;
+	char *err = NULL;
+	struct ckch_store *ckch_store = NULL;
+	X509 *cert = NULL;
+	struct ocsp_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+	struct httpclient *hc = NULL;
+	struct buffer *req_url = NULL;
+	struct buffer *req_body = NULL;
+	OCSP_CERTID *certid = NULL;
+
+	if (!*args[3]) {
+		memprintf(&err, "'update ssl ocsp-response' expects a filename\n");
+		return cli_dynerr(appctx, err);
+	}
+
+	req_url = alloc_trash_chunk();
+	if (!req_url) {
+		memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+		errcode |= ERR_ALERT | ERR_FATAL;
+		goto end;
+	}
+
+	req_body = alloc_trash_chunk();
+	if (!req_body) {
+		memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+		errcode |= ERR_ALERT | ERR_FATAL;
+		goto end;
+	}
+
+	/* The operations on the CKCH architecture are locked so we can
+	 * manipulate ckch_store and ckch_inst */
+	if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
+		memprintf(&err, "%sCan't update the certificate!\nOperations on certificates are currently locked!\n", err ? err : "");
+		errcode |= ERR_ALERT | ERR_FATAL;
+		goto end;
+	}
+
+	ckch_store = ckchs_lookup(args[3]);
+
+	if (!ckch_store) {
+		memprintf(&err, "%sCkch_store not found!\n", err ? err : "");
+		errcode |= ERR_ALERT | ERR_FATAL;
+		HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+		goto end;
+	}
+
+	ctx->ckch_data = ckch_store->data;
+
+	cert = ckch_store->data->cert;
+
+	if (ssl_ocsp_get_uri_from_cert(cert, req_url, &err)) {
+		errcode |= ERR_ALERT | ERR_FATAL;
+		HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+		goto end;
+	}
+
+	certid = OCSP_cert_to_id(NULL, ctx->ckch_data->cert, ctx->ckch_data->ocsp_issuer);
+	if (certid == NULL) {
+		memprintf(&err, "%sOCSP_cert_to_id() error\n", err ? err : "");
+		HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+		goto end;
+	}
+
+	/* From here on the lock is not needed anymore. */
+	HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+	/* Create ocsp request */
+	if (ssl_ocsp_create_request_details(certid, req_url, req_body, &err) != 0) {
+		memprintf(&err, "%sCreate ocsp request error\n", err ? err : "");
+		goto end;
+	}
+
+	hc = httpclient_new(appctx, b_data(req_body) ? HTTP_METH_POST : HTTP_METH_GET, ist2(b_orig(req_url), b_data(req_url)));
+	if (!hc) {
+		memprintf(&err, "%sCan't allocate httpclient\n", err ? err : "");
+		goto end;
+	}
+
+	if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, b_data(req_body) ? ocsp_request_hdrs : NULL,
+	                       ist2(b_orig(req_body), b_data(req_body))) != ERR_NONE) {
+		memprintf(&err, "%shttpclient_req_gen() error\n", err ? err : "");
+		goto end;
+	}
+
+	hc->ops.res_stline = cli_ocsp_res_stline_cb;
+	hc->ops.res_headers = cli_ocsp_res_headers_cb;
+	hc->ops.res_payload = cli_ocsp_res_body_cb;
+	hc->ops.res_end = cli_ocsp_res_end_cb;
+
+	ctx->hc = hc; /* store the httpclient ptr in the applet */
+	ctx->flags = 0;
+
+	if (!httpclient_start(hc)) {
+		memprintf(&err, "%shttpclient_start() error\n", err ? err : "");
+		goto end;
+	}
+
+	free_trash_chunk(req_url);
+
+	return 0;
+
+end:
+	free_trash_chunk(req_url);
+
+	if (errcode & ERR_CODE) {
+		return cli_dynerr(appctx, memprintf(&err, "%sCan't send ocsp request for %s!\n", err ? err : "", args[3]));
+	}
+	return cli_dynmsg(appctx, LOG_NOTICE, err);
+}
+
+static int cli_io_handler_update_ocsp_response(struct appctx *appctx)
+{
+	struct ocsp_cli_ctx *ctx = appctx->svcctx;
+	struct httpclient *hc = ctx->hc;
+
+	if (ctx->flags & HC_F_RES_STLINE) {
+		if (hc->res.status != 200) {
+			chunk_printf(&trash, "OCSP response error (status %d)\n", hc->res.status);
+			if (applet_putchk(appctx, &trash) == -1)
+				goto more;
+			goto end;
+		}
+		ctx->flags &= ~HC_F_RES_STLINE;
+	}
+
+	if (ctx->flags & HC_F_RES_HDR) {
+		struct http_hdr *hdr;
+		int found = 0;
+		/* Look for "Content-Type" header which should have
+		 * "application/ocsp-response" value. */
+		for (hdr = hc->res.hdrs; isttest(hdr->v); hdr++) {
+			if (isteqi(hdr->n, ist("Content-Type")) &&
+			    isteqi(hdr->v, ist("application/ocsp-response"))) {
+				found = 1;
+				break;
+			}
+		}
+		if (!found) {
+			fprintf(stderr, "Missing 'Content-Type: application/ocsp-response' header\n");
+			goto end;
+		}
+		ctx->flags &= ~HC_F_RES_HDR;
+	}
+
+	if (ctx->flags & HC_F_RES_BODY) {
+		/* Wait until the full body is received and HC_F_RES_END flag is
+		 * set. */
+	}
+
+	/* we must close only if F_END is the last flag */
+	if (ctx->flags & HC_F_RES_END) {
+		char *err = NULL;
+
+		if (ssl_ocsp_check_response(ctx->ckch_data->chain, ctx->ckch_data->ocsp_issuer, &hc->res.buf, &err)) {
+			chunk_printf(&trash, "%s", err);
+			if (applet_putchk(appctx, &trash) == -1)
+				goto more;
+			goto end;
+		}
+
+		if (ssl_sock_update_ocsp_response(&hc->res.buf, &err) != 0) {
+			chunk_printf(&trash, "%s", err);
+			if (applet_putchk(appctx, &trash) == -1)
+				goto more;
+			goto end;
+		}
+
+		chunk_reset(&trash);
+
+		if (ssl_ocsp_response_print(&hc->res.buf, &trash))
+			goto end;
+
+		if (applet_putchk(appctx, &trash) == -1)
+			goto more;
+		ctx->flags &= ~HC_F_RES_BODY;
+		ctx->flags &= ~HC_F_RES_END;
+		goto end;
+	}
+
+more:
+	if (!ctx->flags)
+		applet_have_no_more_data(appctx);
+	return 0;
+end:
+	return 1;
+}
+
+static void cli_release_update_ocsp_response(struct appctx *appctx)
+{
+	struct ocsp_cli_ctx *ctx = appctx->svcctx;
+	struct httpclient *hc = ctx->hc;
+
+	/* Everything possible was printed on the CLI, we can destroy the client */
+	httpclient_stop_and_destroy(hc);
+
+	return;
+}
+
 #endif
 
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
 #if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
-	{ { "show", "tls-keys", NULL },            "show tls-keys [id|*]                    : show tls keys references or dump tls ticket keys when id specified", cli_parse_show_tlskeys, cli_io_handler_tlskeys_files },
-	{ { "set", "ssl", "tls-key", NULL },       "set ssl tls-key [id|file] <key>         : set the next TLS key for the <id> or <file> listener to <key>",      cli_parse_set_tlskeys, NULL },
+	{ { "show", "tls-keys", NULL },               "show tls-keys [id|*]                    : show tls keys references or dump tls ticket keys when id specified", cli_parse_show_tlskeys, cli_io_handler_tlskeys_files },
+	{ { "set", "ssl", "tls-key", NULL },          "set ssl tls-key [id|file] <key>         : set the next TLS key for the <id> or <file> listener to <key>",      cli_parse_set_tlskeys, NULL },
 #endif
-	{ { "set", "ssl", "ocsp-response", NULL }, "set ssl ocsp-response <resp|payload>    : update a certificate's OCSP Response from a base64-encode DER",      cli_parse_set_ocspresponse, NULL },
+	{ { "set", "ssl", "ocsp-response", NULL },    "set ssl ocsp-response <resp|payload>    : update a certificate's OCSP Response from a base64-encode DER",      cli_parse_set_ocspresponse, NULL },
 
-	{ { "show", "ssl", "ocsp-response", NULL },"show ssl ocsp-response [id]             : display the IDs of the OCSP responses used in memory, or the details of a single OCSP response", cli_parse_show_ocspresponse, cli_io_handler_show_ocspresponse, NULL },
+	{ { "show", "ssl", "ocsp-response", NULL },   "show ssl ocsp-response [id]             : display the IDs of the OCSP responses used in memory, or the details of a single OCSP response", cli_parse_show_ocspresponse, cli_io_handler_show_ocspresponse, NULL },
+#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
+	{ { "update", "ssl", "ocsp-response", NULL }, "update ssl ocsp-response <certfile>     : send ocsp request and update stored ocsp response",                  cli_parse_update_ocsp_response, cli_io_handler_update_ocsp_response, cli_release_update_ocsp_response },
+#endif
 #ifdef HAVE_SSL_PROVIDERS
 	{ { "show", "ssl", "providers", NULL },    "show ssl providers                      : show loaded SSL providers", NULL, cli_io_handler_show_providers },
 #endif