Add request URL path checking and status responses to HTTP server
authorDr. David von Oheimb <David.von.Oheimb@siemens.com>
Mon, 25 May 2020 15:32:26 +0000 (17:32 +0200)
committerDr. David von Oheimb <David.von.Oheimb@siemens.com>
Sat, 13 Jun 2020 13:13:21 +0000 (15:13 +0200)
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/11998)

apps/cmp.c
apps/include/http_server.h
apps/lib/http_server.c
apps/ocsp.c
doc/man3/OSSL_CMP_SRV_CTX_new.pod

index 6f3e7ed..a229485 100644 (file)
@@ -2100,6 +2100,7 @@ static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *e)
     (void)BIO_snprintf(server_buf, sizeof(server_buf), "http%s://%s%s%s/%s",
                        opt_tls_used ? "s" : "", opt_server,
                        server_port == 0 ? "" : ":", server_port_s,
     (void)BIO_snprintf(server_buf, sizeof(server_buf), "http%s://%s%s%s/%s",
                        opt_tls_used ? "s" : "", opt_server,
                        server_port == 0 ? "" : ":", server_port_s,
+                       opt_path == NULL ? "" :
                        opt_path[0] == '/' ? opt_path + 1 : opt_path);
 
     if (opt_proxy != NULL)
                        opt_path[0] == '/' ? opt_path + 1 : opt_path);
 
     if (opt_proxy != NULL)
@@ -2977,12 +2978,13 @@ int cmp_main(int argc, char **argv)
         if ((acbio = http_server_init_bio(prog, opt_port)) == NULL)
             goto err;
         while (opt_max_msgs <= 0 || msgs < opt_max_msgs) {
         if ((acbio = http_server_init_bio(prog, opt_port)) == NULL)
             goto err;
         while (opt_max_msgs <= 0 || msgs < opt_max_msgs) {
+            char *path = NULL;
             OSSL_CMP_MSG *req = NULL;
             OSSL_CMP_MSG *resp = NULL;
 
             ret = http_server_get_asn1_req(ASN1_ITEM_rptr(OSSL_CMP_MSG),
             OSSL_CMP_MSG *req = NULL;
             OSSL_CMP_MSG *resp = NULL;
 
             ret = http_server_get_asn1_req(ASN1_ITEM_rptr(OSSL_CMP_MSG),
-                                           (ASN1_VALUE **)&req, &cbio, acbio,
-                                           prog, 0, 0);
+                                           (ASN1_VALUE **)&req, &path,
+                                           &cbio, acbio, prog, 0, 0);
             if (ret == 0)
                 continue;
             if (ret++ == -1)
             if (ret == 0)
                 continue;
             if (ret++ == -1)
@@ -2991,17 +2993,32 @@ int cmp_main(int argc, char **argv)
             ret = 0;
             msgs++;
             if (req != NULL) {
             ret = 0;
             msgs++;
             if (req != NULL) {
+                if (strcmp(path, "") != 0 && strcmp(path, "pkix/") != 0) {
+                    (void)http_server_send_status(cbio, 404, "Not Found");
+                    CMP_err1("Expecting empty path or 'pkix/' but got '%s'\n",
+                             path);
+                    OPENSSL_free(path);
+                    OSSL_CMP_MSG_free(req);
+                    goto cont;
+                }
+                OPENSSL_free(path);
                 resp = OSSL_CMP_CTX_server_perform(cmp_ctx, req);
                 OSSL_CMP_MSG_free(req);
                 resp = OSSL_CMP_CTX_server_perform(cmp_ctx, req);
                 OSSL_CMP_MSG_free(req);
-                if (resp == NULL)
+                if (resp == NULL) {
+                    (void)http_server_send_status(cbio,
+                                                  500, "Internal Server Error");
                     break; /* treated as fatal error */
                     break; /* treated as fatal error */
+                }
                 ret = http_server_send_asn1_resp(cbio, "application/pkixcmp",
                                                  ASN1_ITEM_rptr(OSSL_CMP_MSG),
                                                  (const ASN1_VALUE *)resp);
                 OSSL_CMP_MSG_free(resp);
                 if (!ret)
                     break; /* treated as fatal error */
                 ret = http_server_send_asn1_resp(cbio, "application/pkixcmp",
                                                  ASN1_ITEM_rptr(OSSL_CMP_MSG),
                                                  (const ASN1_VALUE *)resp);
                 OSSL_CMP_MSG_free(resp);
                 if (!ret)
                     break; /* treated as fatal error */
+            } else {
+                (void)http_server_send_status(cbio, 400, "Bad Request");
             }
             }
+        cont:
             BIO_free_all(cbio);
             cbio = NULL;
         }
             BIO_free_all(cbio);
             cbio = NULL;
         }
index 8c65521..1264753 100644 (file)
@@ -60,23 +60,29 @@ void log_message(const char *prog, int level, const char *fmt, ...);
  * returns a BIO for accepting requests, NULL on error
  */
 BIO *http_server_init_bio(const char *prog, const char *port);
  * returns a BIO for accepting requests, NULL on error
  */
 BIO *http_server_init_bio(const char *prog, const char *port);
+
 /*-
  * Accept an ASN.1-formatted HTTP request
  * it: the expected request ASN.1 type
  * preq: pointer to variable where to place the parsed request
  * pcbio: pointer to variable where to place the BIO for sending the response to
 /*-
  * Accept an ASN.1-formatted HTTP request
  * it: the expected request ASN.1 type
  * preq: pointer to variable where to place the parsed request
  * pcbio: pointer to variable where to place the BIO for sending the response to
+ * ppath: pointer to variable where to place the request path, or NULL
  * acbio: the listening bio (typically as returned by http_server_init_bio())
  * prog: the name of the current app
  * acbio: the listening bio (typically as returned by http_server_init_bio())
  * prog: the name of the current app
- * accept_get: wheter to accept GET requests (in addition to POST requests)
+ * accept_get: whether to accept GET requests (in addition to POST requests)
  * timeout: connection timeout (in seconds), or 0 for none/infinite
  * timeout: connection timeout (in seconds), or 0 for none/infinite
- * returns 0 in case caller should retry, then *preq == *pcbio == NULL
- * returns -1 on fatal error; also in this case *preq == *pcbio == NULL
- * returns 1 otherwise. In this case it is guaranteed that *pcbio != NULL
- *                      while *preq == NULL if and only if request is invalid
+ * returns 0 in case caller should retry, then *preq == *ppath == *pcbio == NULL
+ * returns -1 on fatal error; also then holds *preq == *ppath == *pcbio == NULL
+ * returns 1 otherwise. In this case it is guaranteed that *pcbio != NULL while
+ * *ppath == NULL and *preq == NULL if and only if the request is invalid,
+ * On return value 1 the caller is responsible for sending an HTTP response,
+ * using http_server_send_asn1_resp() or http_server_send_status().
+ * The caller must free any non-NULL *preq, *ppath, and *pcbio pointers.
  */
 int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
  */
 int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
-                        BIO **pcbio, BIO *acbio,
-                        const char *prog, int accept_get, int timeout);
+                             char **ppath, BIO **pcbio, BIO *acbio,
+                             const char *prog, int accept_get, int timeout);
+
 /*-
  * Send an ASN.1-formatted HTTP response
  * cbio: destination BIO (typically as returned by http_server_get_asn1_req())
 /*-
  * Send an ASN.1-formatted HTTP response
  * cbio: destination BIO (typically as returned by http_server_get_asn1_req())
@@ -89,6 +95,16 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
  */
 int http_server_send_asn1_resp(BIO *cbio, const char *content_type,
                                const ASN1_ITEM *it, const ASN1_VALUE *resp);
  */
 int http_server_send_asn1_resp(BIO *cbio, const char *content_type,
                                const ASN1_ITEM *it, const ASN1_VALUE *resp);
+
+/*-
+ * Send a trivial HTTP response, typically to report an error or OK
+ * cbio: destination BIO (typically as returned by http_server_get_asn1_req())
+ * status: the status code to send
+ * reason: the corresponding human-readable string
+ * returns 1 on success, 0 on failure
+ */
+int http_server_send_status(BIO *cbio, int status, const char *reason);
+
 # endif
 
 # ifdef HTTP_DAEMON
 # endif
 
 # ifdef HTTP_DAEMON
index 2b5c9f5..11f0b1f 100644 (file)
@@ -255,17 +255,19 @@ static int urldecode(char *p)
 }
 
 int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
 }
 
 int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
-                             BIO **pcbio, BIO *acbio,
+                             char **ppath, BIO **pcbio, BIO *acbio,
                              const char *prog, int accept_get, int timeout)
 {
     BIO *cbio = NULL, *getbio = NULL, *b64 = NULL;
     int len;
     char reqbuf[2048], inbuf[2048];
                              const char *prog, int accept_get, int timeout)
 {
     BIO *cbio = NULL, *getbio = NULL, *b64 = NULL;
     int len;
     char reqbuf[2048], inbuf[2048];
-    char *url, *end;
+    char *meth, *url, *end;
     ASN1_VALUE *req;
     int ret = 1;
 
     *preq = NULL;
     ASN1_VALUE *req;
     int ret = 1;
 
     *preq = NULL;
+    if (ppath != NULL)
+        *ppath = NULL;
     *pcbio = NULL;
 
     /* Connection loss before accept() is routine, ignore silently */
     *pcbio = NULL;
 
     /* Connection loss before accept() is routine, ignore silently */
@@ -275,6 +277,7 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
     cbio = BIO_pop(acbio);
     *pcbio = cbio;
     if (cbio == NULL) {
     cbio = BIO_pop(acbio);
     *pcbio = cbio;
     if (cbio == NULL) {
+        /* Cannot call http_server_send_status(cbio, ...) */
         ret = -1;
         goto out;
     }
         ret = -1;
         goto out;
     }
@@ -288,16 +291,26 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
 
     /* Read the request line. */
     len = BIO_gets(cbio, reqbuf, sizeof(reqbuf));
 
     /* Read the request line. */
     len = BIO_gets(cbio, reqbuf, sizeof(reqbuf));
-    if (len <= 0)
+    if (len <= 0) {
+        log_message(prog, LOG_INFO,
+                    "Request line read error or empty request");
+        (void)http_server_send_status(cbio, 400, "Bad Request");
         goto out;
         goto out;
+    }
 
 
-    if (accept_get && strncmp(reqbuf, "GET ", 4) == 0) {
-        /* Expecting GET {sp} /URL {sp} HTTP/1.x */
-        for (url = reqbuf + 4; *url == ' '; ++url)
-            continue;
+    meth = reqbuf;
+    url = meth + 3;
+    if ((accept_get && strncmp(meth, "GET ", 4) == 0)
+            || (url++, strncmp(meth, "POST ", 5) == 0)) {
+        /* Expecting (GET|POST) {sp} /URL {sp} HTTP/1.x */
+        *(url++) = '\0';
+        while (*url == ' ')
+            url++;
         if (*url != '/') {
             log_message(prog, LOG_INFO,
         if (*url != '/') {
             log_message(prog, LOG_INFO,
-                        "Invalid GET -- URL does not begin with '/': %s", url);
+                        "Invalid %s -- URL does not begin with '/': %s",
+                        meth, url);
+            (void)http_server_send_status(cbio, 400, "Bad Request");
             goto out;
         }
         url++;
             goto out;
         }
         url++;
@@ -308,7 +321,9 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
                 break;
         if (strncmp(end, " HTTP/1.", 7) != 0) {
             log_message(prog, LOG_INFO,
                 break;
         if (strncmp(end, " HTTP/1.", 7) != 0) {
             log_message(prog, LOG_INFO,
-                        "Invalid GET -- bad HTTP/version string: %s", end + 1);
+                        "Invalid %s -- bad HTTP/version string: %s",
+                        meth, end + 1);
+            (void)http_server_send_status(cbio, 400, "Bad Request");
             goto out;
         }
         *end = '\0';
             goto out;
         }
         *end = '\0';
@@ -318,39 +333,52 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
          * 'url' was incremented above to point to the first byte *after*
          * the leading slash, so in case 'GET / ' it is now an empty string.
          */
          * 'url' was incremented above to point to the first byte *after*
          * the leading slash, so in case 'GET / ' it is now an empty string.
          */
-        if (url[0] == '\0')
+        if (strlen(meth) == 3 && url[0] == '\0') {
+            (void)http_server_send_status(cbio, 200, "OK");
             goto out;
             goto out;
+        }
 
         len = urldecode(url);
 
         len = urldecode(url);
-        if (len <= 0) {
+        if (len < 0) {
             log_message(prog, LOG_INFO,
             log_message(prog, LOG_INFO,
-                        "Invalid GET request -- bad URL encoding: %s", url);
+                        "Invalid %s request -- bad URL encoding: %s",
+                        meth, url);
+            (void)http_server_send_status(cbio, 400, "Bad Request");
             goto out;
         }
             goto out;
         }
-        if ((getbio = BIO_new_mem_buf(url, len)) == NULL
-            || (b64 = BIO_new(BIO_f_base64())) == NULL) {
-            log_message(prog, LOG_ERR,
-                        "Could not allocate base64 bio with size = %d", len);
-            BIO_free_all(cbio);
-            *pcbio = NULL;
-            ret = -1;
-            goto out;
+        if (strlen(meth) == 3) { /* GET */
+            if ((getbio = BIO_new_mem_buf(url, len)) == NULL
+                || (b64 = BIO_new(BIO_f_base64())) == NULL) {
+                log_message(prog, LOG_ERR,
+                            "Could not allocate base64 bio with size = %d",
+                            len);
+                goto fatal;
+            }
+            BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+            getbio = BIO_push(b64, getbio);
         }
         }
-        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
-        getbio = BIO_push(b64, getbio);
-    } else if (strncmp(reqbuf, "POST ", 5) != 0) {
+    } else {
         log_message(prog, LOG_INFO,
                     "HTTP request does not start with GET/POST: %s", reqbuf);
         /* TODO provide better diagnosis in case client tries TLS */
         log_message(prog, LOG_INFO,
                     "HTTP request does not start with GET/POST: %s", reqbuf);
         /* TODO provide better diagnosis in case client tries TLS */
+        (void)http_server_send_status(cbio, 400, "Bad Request");
         goto out;
     }
 
         goto out;
     }
 
+    /* chop any further/duplicate leading or trailing '/' */
+    while (*url == '/')
+        url++;
+    while (end >= url + 2 && end[-2] == '/' && end[-1] == '/')
+        end--;
+    *end = '\0';
+
     /* Read and skip past the headers. */
     for (;;) {
         len = BIO_gets(cbio, inbuf, sizeof(inbuf));
         if (len <= 0) {
             log_message(prog, LOG_ERR,
                         "Error skipping remaining HTTP headers");
     /* Read and skip past the headers. */
     for (;;) {
         len = BIO_gets(cbio, inbuf, sizeof(inbuf));
         if (len <= 0) {
             log_message(prog, LOG_ERR,
                         "Error skipping remaining HTTP headers");
+            (void)http_server_send_status(cbio, 400, "Bad Request");
             goto out;
         }
         if ((inbuf[0] == '\r') || (inbuf[0] == '\n'))
             goto out;
         }
         if ((inbuf[0] == '\r') || (inbuf[0] == '\n'))
@@ -365,8 +393,14 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
 
     /* Try to read and parse request */
     req = ASN1_item_d2i_bio(it, getbio != NULL ? getbio : cbio, NULL);
 
     /* Try to read and parse request */
     req = ASN1_item_d2i_bio(it, getbio != NULL ? getbio : cbio, NULL);
-    if (req == NULL)
+    if (req == NULL) {
         log_message(prog, LOG_ERR, "Error parsing request");
         log_message(prog, LOG_ERR, "Error parsing request");
+    } else if (ppath != NULL && (*ppath = OPENSSL_strdup(url)) == NULL) {
+        log_message(prog, LOG_ERR,
+                    "Out of memory allocating %d bytes", strlen(url) + 1);
+        ASN1_item_free(req, it);
+        goto fatal;
+    }
 
     *preq = req;
 
 
     *preq = req;
 
@@ -378,6 +412,17 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
     acfd = (int)INVALID_SOCKET;
 # endif
     return ret;
     acfd = (int)INVALID_SOCKET;
 # endif
     return ret;
+
+ fatal:
+    (void)http_server_send_status(cbio, 500, "Internal Server Error");
+    if (ppath != NULL) {
+        OPENSSL_free(*ppath);
+        *ppath = NULL;
+    }
+    BIO_free_all(cbio);
+    *pcbio = NULL;
+    ret = -1;
+    goto out;
 }
 
 /* assumes that cbio does not do an encoding that changes the output length */
 }
 
 /* assumes that cbio does not do an encoding that changes the output length */
@@ -392,4 +437,12 @@ int http_server_send_asn1_resp(BIO *cbio, const char *content_type,
     (void)BIO_flush(cbio);
     return ret;
 }
     (void)BIO_flush(cbio);
     return ret;
 }
+
+int http_server_send_status(BIO *cbio, int status, const char *reason)
+{
+    int ret = BIO_printf(cbio, "HTTP/1.0 %d %s\r\n\r\n", status, reason) > 0;
+
+    (void)BIO_flush(cbio);
+    return ret;
+}
 #endif
 #endif
index fd03611..6095e6b 100644 (file)
@@ -234,7 +234,7 @@ int ocsp_main(int argc, char **argv)
     int noCAfile = 0, noCApath = 0, noCAstore = 0;
     int accept_count = -1, add_nonce = 1, noverify = 0, use_ssl = -1;
     int vpmtouched = 0, badsig = 0, i, ignore_err = 0, nmin = 0, ndays = -1;
     int noCAfile = 0, noCApath = 0, noCAstore = 0;
     int accept_count = -1, add_nonce = 1, noverify = 0, use_ssl = -1;
     int vpmtouched = 0, badsig = 0, i, ignore_err = 0, nmin = 0, ndays = -1;
-    int req_text = 0, resp_text = 0, ret = 1;
+    int req_text = 0, resp_text = 0, res, ret = 1;
     int req_timeout = -1;
     long nsec = MAX_VALIDITY_PERIOD, maxage = -1;
     unsigned long sign_flags = 0, verify_flags = 0, rflags = 0;
     int req_timeout = -1;
     long nsec = MAX_VALIDITY_PERIOD, maxage = -1;
     unsigned long sign_flags = 0, verify_flags = 0, rflags = 0;
@@ -629,13 +629,17 @@ redo_accept:
 #endif
 
         req = NULL;
 #endif
 
         req = NULL;
-        if (!do_responder(&req, &cbio, acbio, req_timeout))
+        res = do_responder(&req, &cbio, acbio, req_timeout);
+        if (res == 0)
             goto redo_accept;
 
         if (req == NULL) {
             goto redo_accept;
 
         if (req == NULL) {
-            resp = OCSP_response_create(OCSP_RESPONSE_STATUS_MALFORMEDREQUEST,
-                                        NULL);
-            send_ocsp_response(cbio, resp);
+            if (res == 1) {
+                resp =
+                    OCSP_response_create(OCSP_RESPONSE_STATUS_MALFORMEDREQUEST,
+                                         NULL);
+                send_ocsp_response(cbio, resp);
+            }
             goto done_resp;
         }
     }
             goto done_resp;
         }
     }
@@ -1151,7 +1155,7 @@ static int do_responder(OCSP_REQUEST **preq, BIO **pcbio, BIO *acbio,
 {
 #ifndef OPENSSL_NO_SOCK
     return http_server_get_asn1_req(ASN1_ITEM_rptr(OCSP_RESPONSE),
 {
 #ifndef OPENSSL_NO_SOCK
     return http_server_get_asn1_req(ASN1_ITEM_rptr(OCSP_RESPONSE),
-                                    (ASN1_VALUE **)preq, pcbio, acbio,
+                                    (ASN1_VALUE **)preq, NULL, pcbio, acbio,
                                     prog, 1 /* accept_get */, timeout);
 #else
     BIO_printf(bio_err,
                                     prog, 1 /* accept_get */, timeout);
 #else
     BIO_printf(bio_err,
index a2d47a4..27d4f6c 100644 (file)
@@ -85,32 +85,33 @@ OSSL_CMP_SRV_CTX_set_grant_implicit_confirm
 =head1 DESCRIPTION
 
 OSSL_CMP_SRV_process_request() implements the generic aspects of a CMP server.
 =head1 DESCRIPTION
 
 OSSL_CMP_SRV_process_request() implements the generic aspects of a CMP server.
-It does the typical generic checks on the given request message, calls
+Its arguments are the B<OSSL_CMP_SRV_CTX> I<srv_ctx> and the CMP request message
+I<req>. It does the typical generic checks on I<req>, calls
 the respective callback function (if present) for more specific processing,
 and then assembles a result message, which may be a CMP error message.
 
 OSSL_CMP_CTX_server_perform() is an interface to
 B<OSSL_CMP_SRV_process_request()> that can be used by a CMP client
 in the same way as B<OSSL_CMP_MSG_http_perform()>.
 the respective callback function (if present) for more specific processing,
 and then assembles a result message, which may be a CMP error message.
 
 OSSL_CMP_CTX_server_perform() is an interface to
 B<OSSL_CMP_SRV_process_request()> that can be used by a CMP client
 in the same way as B<OSSL_CMP_MSG_http_perform()>.
-The B<OSSL_CMP_SRV_CTX> must be set as B<transfer_cb_arg> of B<client_ctx>.
+The B<OSSL_CMP_SRV_CTX> must be set as I<transfer_cb_arg> of I<client_ctx>.
 
 OSSL_CMP_SRV_CTX_new() creates and initializes an OSSL_CMP_SRV_CTX structure
 and returns a pointer to it on success, NULL on error.
 
 
 OSSL_CMP_SRV_CTX_new() creates and initializes an OSSL_CMP_SRV_CTX structure
 and returns a pointer to it on success, NULL on error.
 
-OSSL_CMP_SRV_CTX_free() deletes the given B<srv_ctx>.
+OSSL_CMP_SRV_CTX_free() deletes the given I<srv_ctx>.
 
 
-OSSL_CMP_SRV_CTX_init() sets in the given B<srv_ctx> a custom server context
+OSSL_CMP_SRV_CTX_init() sets in the given I<srv_ctx> a custom server context
 pointer as well as callback functions performing the specific processing of CMP
 certificate requests, revocation requests, certificate confirmation requests,
 general messages, error messages, and poll requests.
 pointer as well as callback functions performing the specific processing of CMP
 certificate requests, revocation requests, certificate confirmation requests,
 general messages, error messages, and poll requests.
-All arguments except B<srv_ctx> may be NULL.
+All arguments except I<srv_ctx> may be NULL.
 If a callback for some message type is not given this means that the respective
 type of CMP message is not supported by the server.
 
 If a callback for some message type is not given this means that the respective
 type of CMP message is not supported by the server.
 
-OSSL_CMP_SRV_CTX_get0_cmp_ctx() returns the B<OSSL_CMP_CTX> from the B<srv_ctx>.
+OSSL_CMP_SRV_CTX_get0_cmp_ctx() returns the B<OSSL_CMP_CTX> from the I<srv_ctx>.
 
 OSSL_CMP_SRV_CTX_get0_custom_ctx() returns the custom server context from
 
 OSSL_CMP_SRV_CTX_get0_custom_ctx() returns the custom server context from
-B<srv_ctx> that has been set using B<OSSL_CMP_SRV_CTX_init>.
+I<srv_ctx> that has been set using B<OSSL_CMP_SRV_CTX_init()>.
 
 OSSL_CMP_SRV_CTX_set_send_unprotected_errors() enables sending error messages
 and other forms of negative responses unprotected.
 
 OSSL_CMP_SRV_CTX_set_send_unprotected_errors() enables sending error messages
 and other forms of negative responses unprotected.
@@ -142,7 +143,7 @@ OSSL_CMP_SRV_CTX_get0_cmp_ctx() returns a B<OSSL_CMP_CTX> structure on success,
 NULL on error.
 
 OSSL_CMP_SRV_CTX_get0_custom_ctx() returns the custom server context
 NULL on error.
 
 OSSL_CMP_SRV_CTX_get0_custom_ctx() returns the custom server context
-that has been set using B<OSSL_CMP_SRV_CTX_init>.
+that has been set using B<OSSL_CMP_SRV_CTX_init()>.
 
 All other functions return 1 on success, 0 on error.
 
 
 All other functions return 1 on success, 0 on error.