Home | History | Annotate | Line # | Download | only in lib
      1 /*
      2  * Copyright 1995-2024 The OpenSSL Project Authors. All Rights Reserved.
      3  *
      4  * Licensed under the Apache License 2.0 (the "License").  You may not use
      5  * this file except in compliance with the License.  You can obtain a copy
      6  * in the file LICENSE in the source distribution or at
      7  * https://www.openssl.org/source/license.html
      8  */
      9 
     10 /* Very basic HTTP server */
     11 
     12 #if !defined(_POSIX_C_SOURCE) && defined(OPENSSL_SYS_VMS)
     13 /*
     14  * On VMS, you need to define this to get the declaration of fileno().  The
     15  * value 2 is to make sure no function defined in POSIX-2 is left undefined.
     16  */
     17 #define _POSIX_C_SOURCE 2
     18 #endif
     19 
     20 #include <ctype.h>
     21 #include "internal/e_os.h"
     22 #include "http_server.h"
     23 #include "internal/sockets.h" /* for openssl_fdset() */
     24 
     25 #include <openssl/err.h>
     26 #include <openssl/trace.h>
     27 #include <openssl/rand.h>
     28 #include "s_apps.h"
     29 #include "log.h"
     30 
     31 #define HTTP_PREFIX "HTTP/"
     32 #define HTTP_VERSION_PATT "1." /* allow 1.x */
     33 #define HTTP_PREFIX_VERSION HTTP_PREFIX "" HTTP_VERSION_PATT
     34 #define HTTP_1_0 HTTP_PREFIX_VERSION "0" /* "HTTP/1.0" */
     35 #define HTTP_VERSION_STR " " HTTP_PREFIX_VERSION
     36 
     37 #define log_HTTP(prog, level, text) \
     38     trace_log_message(OSSL_TRACE_CATEGORY_HTTP, prog, level, "%s", text)
     39 #define log_HTTP1(prog, level, fmt, arg) \
     40     trace_log_message(OSSL_TRACE_CATEGORY_HTTP, prog, level, fmt, arg)
     41 #define log_HTTP2(prog, level, fmt, arg1, arg2) \
     42     trace_log_message(OSSL_TRACE_CATEGORY_HTTP, prog, level, fmt, arg1, arg2)
     43 #define log_HTTP3(prog, level, fmt, a1, a2, a3) \
     44     trace_log_message(OSSL_TRACE_CATEGORY_HTTP, prog, level, fmt, a1, a2, a3)
     45 
     46 #ifdef HTTP_DAEMON
     47 int n_responders = 0; /* run multiple responder processes, set by ocsp.c */
     48 int acfd = (int)INVALID_SOCKET;
     49 
     50 void socket_timeout(int signum)
     51 {
     52     if (acfd != (int)INVALID_SOCKET)
     53         (void)shutdown(acfd, SHUT_RD);
     54 }
     55 
     56 static void killall(int ret, pid_t *kidpids)
     57 {
     58     int i;
     59 
     60     for (i = 0; i < n_responders; ++i)
     61         if (kidpids[i] != 0)
     62             (void)kill(kidpids[i], SIGTERM);
     63     OPENSSL_free(kidpids);
     64     OSSL_sleep(1000);
     65     exit(ret);
     66 }
     67 
     68 static int termsig = 0;
     69 
     70 static void noteterm(int sig)
     71 {
     72     termsig = sig;
     73 }
     74 
     75 /*
     76  * Loop spawning up to `multi` child processes, only child processes return
     77  * from this function.  The parent process loops until receiving a termination
     78  * signal, kills extant children and exits without returning.
     79  */
     80 void spawn_loop(const char *prog)
     81 {
     82     pid_t *kidpids = NULL;
     83     int status;
     84     int procs = 0;
     85     int i;
     86 
     87     openlog(prog, LOG_PID, LOG_DAEMON);
     88 
     89     if (setpgid(0, 0)) {
     90         log_HTTP1(prog, LOG_CRIT,
     91             "error detaching from parent process group: %s",
     92             strerror(errno));
     93         exit(1);
     94     }
     95     kidpids = app_malloc(n_responders * sizeof(*kidpids), "child PID array");
     96     for (i = 0; i < n_responders; ++i)
     97         kidpids[i] = 0;
     98 
     99     signal(SIGINT, noteterm);
    100     signal(SIGTERM, noteterm);
    101 
    102     while (termsig == 0) {
    103         pid_t fpid;
    104 
    105         /*
    106          * Wait for a child to replace when we're at the limit.
    107          * Slow down if a child exited abnormally or waitpid() < 0
    108          */
    109         while (termsig == 0 && procs >= n_responders) {
    110             if ((fpid = waitpid(-1, &status, 0)) > 0) {
    111                 for (i = 0; i < procs; ++i) {
    112                     if (kidpids[i] == fpid) {
    113                         kidpids[i] = 0;
    114                         --procs;
    115                         break;
    116                     }
    117                 }
    118                 if (i >= n_responders) {
    119                     log_HTTP1(prog, LOG_CRIT,
    120                         "internal error: no matching child slot for pid: %ld",
    121                         (long)fpid);
    122                     killall(1, kidpids);
    123                 }
    124                 if (status != 0) {
    125                     if (WIFEXITED(status)) {
    126                         log_HTTP2(prog, LOG_WARNING,
    127                             "child process: %ld, exit status: %d",
    128                             (long)fpid, WEXITSTATUS(status));
    129                     } else if (WIFSIGNALED(status)) {
    130                         char *dumped = "";
    131 
    132 #ifdef WCOREDUMP
    133                         if (WCOREDUMP(status))
    134                             dumped = " (core dumped)";
    135 #endif
    136                         log_HTTP3(prog, LOG_WARNING,
    137                             "child process: %ld, term signal %d%s",
    138                             (long)fpid, WTERMSIG(status), dumped);
    139                     }
    140                     OSSL_sleep(1000);
    141                 }
    142                 break;
    143             } else if (errno != EINTR) {
    144                 log_HTTP1(prog, LOG_CRIT,
    145                     "waitpid() failed: %s", strerror(errno));
    146                 killall(1, kidpids);
    147             }
    148         }
    149         if (termsig)
    150             break;
    151 
    152         switch (fpid = fork()) {
    153         case -1: /* error */
    154             /* System critically low on memory, pause and try again later */
    155             OSSL_sleep(30000);
    156             break;
    157         case 0: /* child */
    158             OPENSSL_free(kidpids);
    159             signal(SIGINT, SIG_DFL);
    160             signal(SIGTERM, SIG_DFL);
    161             if (termsig)
    162                 _exit(0);
    163             if (RAND_poll() <= 0) {
    164                 log_HTTP(prog, LOG_CRIT, "RAND_poll() failed");
    165                 _exit(1);
    166             }
    167             return;
    168         default: /* parent */
    169             for (i = 0; i < n_responders; ++i) {
    170                 if (kidpids[i] == 0) {
    171                     kidpids[i] = fpid;
    172                     procs++;
    173                     break;
    174                 }
    175             }
    176             if (i >= n_responders) {
    177                 log_HTTP(prog, LOG_CRIT,
    178                     "internal error: no free child slots");
    179                 killall(1, kidpids);
    180             }
    181             break;
    182         }
    183     }
    184 
    185     /* The loop above can only break on termsig */
    186     log_HTTP1(prog, LOG_INFO, "terminating on signal: %d", termsig);
    187     killall(0, kidpids);
    188 }
    189 #endif
    190 
    191 #ifndef OPENSSL_NO_SOCK
    192 BIO *http_server_init(const char *prog, const char *port, int verb)
    193 {
    194     BIO *acbio = NULL, *bufbio;
    195     int asock;
    196     int port_num;
    197     char name[40];
    198 
    199     BIO_snprintf(name, sizeof(name), "*:%s", port); /* port may be "0" */
    200     if (verb >= 0 && !log_set_verbosity(prog, verb))
    201         return NULL;
    202     bufbio = BIO_new(BIO_f_buffer());
    203     if (bufbio == NULL)
    204         goto err;
    205     acbio = BIO_new(BIO_s_accept());
    206     if (acbio == NULL
    207         || BIO_set_accept_ip_family(acbio, BIO_FAMILY_IPANY) <= 0 /* IPv4/6 */
    208         || BIO_set_bind_mode(acbio, BIO_BIND_REUSEADDR) <= 0
    209         || BIO_set_accept_name(acbio, name) <= 0) {
    210         log_HTTP(prog, LOG_ERR, "error setting up accept BIO");
    211         goto err;
    212     }
    213 
    214     BIO_set_accept_bios(acbio, bufbio);
    215     bufbio = NULL;
    216     if (BIO_do_accept(acbio) <= 0) {
    217         log_HTTP1(prog, LOG_ERR, "error setting accept on port %s", port);
    218         goto err;
    219     }
    220 
    221     /* Report back what address and port are used */
    222     BIO_get_fd(acbio, &asock);
    223     port_num = report_server_accept(bio_out, asock, 1, 1);
    224     if (port_num == 0) {
    225         log_HTTP(prog, LOG_ERR, "error printing ACCEPT string");
    226         goto err;
    227     }
    228 
    229     return acbio;
    230 
    231 err:
    232     ERR_print_errors(bio_err);
    233     BIO_free_all(acbio);
    234     BIO_free(bufbio);
    235     return NULL;
    236 }
    237 
    238 /*
    239  * Decode %xx URL-decoding in-place. Ignores malformed sequences.
    240  */
    241 static int urldecode(char *p)
    242 {
    243     unsigned char *out = (unsigned char *)p;
    244     unsigned char *save = out;
    245 
    246     for (; *p; p++) {
    247         if (*p != '%') {
    248             *out++ = *p;
    249         } else if (isxdigit(_UC(p[1])) && isxdigit(_UC(p[2]))) {
    250             /* Don't check, can't fail because of ixdigit() call. */
    251             *out++ = (OPENSSL_hexchar2int(p[1]) << 4)
    252                 | OPENSSL_hexchar2int(p[2]);
    253             p += 2;
    254         } else {
    255             return -1;
    256         }
    257     }
    258     *out = '\0';
    259     return (int)(out - save);
    260 }
    261 
    262 /* if *pcbio != NULL, continue given connected session, else accept new */
    263 /* if found_keep_alive != NULL, return this way connection persistence state */
    264 int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
    265     char **ppath, BIO **pcbio, BIO *acbio,
    266     int *found_keep_alive,
    267     const char *prog, int accept_get, int timeout)
    268 {
    269     BIO *cbio = *pcbio, *getbio = NULL, *b64 = NULL;
    270     int len;
    271     char reqbuf[2048], inbuf[2048];
    272     char *meth, *url, *end;
    273     ASN1_VALUE *req;
    274     int ret = 0;
    275 
    276     *preq = NULL;
    277     if (ppath != NULL)
    278         *ppath = NULL;
    279 
    280     if (cbio == NULL) {
    281         char *port;
    282 
    283         get_sock_info_address(BIO_get_fd(acbio, NULL), NULL, &port);
    284         if (port == NULL) {
    285             log_HTTP(prog, LOG_ERR, "cannot get port listening on");
    286             goto fatal;
    287         }
    288         log_HTTP1(prog, LOG_DEBUG,
    289             "awaiting new connection on port %s ...", port);
    290         OPENSSL_free(port);
    291 
    292         if (BIO_do_accept(acbio) <= 0)
    293             /* Connection loss before accept() is routine, ignore silently */
    294             return ret;
    295 
    296         *pcbio = cbio = BIO_pop(acbio);
    297     } else {
    298         log_HTTP(prog, LOG_DEBUG, "awaiting next request ...");
    299     }
    300     if (cbio == NULL) {
    301         /* Cannot call http_server_send_status(..., cbio, ...) */
    302         ret = -1;
    303         goto out;
    304     }
    305 
    306 #ifdef HTTP_DAEMON
    307     if (timeout > 0) {
    308         (void)BIO_get_fd(cbio, &acfd);
    309         alarm(timeout);
    310     }
    311 #endif
    312 
    313     /* Read the request line. */
    314     len = BIO_gets(cbio, reqbuf, sizeof(reqbuf));
    315     if (len == 0)
    316         return ret;
    317     ret = 1;
    318     if (len < 0) {
    319         log_HTTP(prog, LOG_WARNING, "request line read error");
    320         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    321         goto out;
    322     }
    323 
    324     if (((end = strchr(reqbuf, '\r')) != NULL && end[1] == '\n')
    325         || (end = strchr(reqbuf, '\n')) != NULL)
    326         *end = '\0';
    327     if (log_get_verbosity() < LOG_TRACE)
    328         trace_log_message(-1, prog, LOG_INFO,
    329             "received request, 1st line: %s", reqbuf);
    330     log_HTTP(prog, LOG_TRACE, "received request header:");
    331     log_HTTP1(prog, LOG_TRACE, "%s", reqbuf);
    332     if (end == NULL) {
    333         log_HTTP(prog, LOG_WARNING,
    334             "cannot parse HTTP header: missing end of line");
    335         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    336         goto out;
    337     }
    338 
    339     url = meth = reqbuf;
    340     if ((accept_get && CHECK_AND_SKIP_PREFIX(url, "GET "))
    341         || CHECK_AND_SKIP_PREFIX(url, "POST ")) {
    342 
    343         /* Expecting (GET|POST) {sp} /URL {sp} HTTP/1.x */
    344         url[-1] = '\0';
    345         while (*url == ' ')
    346             url++;
    347         if (*url != '/') {
    348             log_HTTP2(prog, LOG_WARNING,
    349                 "invalid %s -- URL does not begin with '/': %s",
    350                 meth, url);
    351             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    352             goto out;
    353         }
    354         url++;
    355 
    356         /* Splice off the HTTP version identifier. */
    357         for (end = url; *end != '\0'; end++)
    358             if (*end == ' ')
    359                 break;
    360         if (!HAS_PREFIX(end, HTTP_VERSION_STR)) {
    361             log_HTTP2(prog, LOG_WARNING,
    362                 "invalid %s -- bad HTTP/version string: %s",
    363                 meth, end + 1);
    364             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    365             goto out;
    366         }
    367         *end = '\0';
    368         /* above HTTP 1.0, connection persistence is the default */
    369         if (found_keep_alive != NULL)
    370             *found_keep_alive = end[sizeof(HTTP_VERSION_STR) - 1] > '0';
    371 
    372         /*-
    373          * Skip "GET / HTTP..." requests often used by load-balancers.
    374          * 'url' was incremented above to point to the first byte *after*
    375          * the leading slash, so in case 'GET / ' it is now an empty string.
    376          */
    377         if (strlen(meth) == 3 && url[0] == '\0') {
    378             (void)http_server_send_status(prog, cbio, 200, "OK");
    379             goto out;
    380         }
    381 
    382         len = urldecode(url);
    383         if (len < 0) {
    384             log_HTTP2(prog, LOG_WARNING,
    385                 "invalid %s request -- bad URL encoding: %s", meth, url);
    386             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    387             goto out;
    388         }
    389         if (strlen(meth) == 3) { /* GET */
    390             if ((getbio = BIO_new_mem_buf(url, len)) == NULL
    391                 || (b64 = BIO_new(BIO_f_base64())) == NULL) {
    392                 log_HTTP1(prog, LOG_ERR,
    393                     "could not allocate base64 bio with size = %d", len);
    394                 goto fatal;
    395             }
    396             BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    397             getbio = BIO_push(b64, getbio);
    398         }
    399     } else {
    400         log_HTTP2(prog, LOG_WARNING,
    401             "HTTP request does not begin with %sPOST: %s",
    402             accept_get ? "GET or " : "", reqbuf);
    403         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    404         goto out;
    405     }
    406 
    407     /* chop any further/duplicate leading or trailing '/' */
    408     while (*url == '/')
    409         url++;
    410     while (end >= url + 2 && end[-2] == '/' && end[-1] == '/')
    411         end--;
    412     *end = '\0';
    413 
    414     /* Read and skip past the headers. */
    415     for (;;) {
    416         char *key, *value;
    417 
    418         len = BIO_gets(cbio, inbuf, sizeof(inbuf));
    419         if (len <= 0) {
    420             log_HTTP(prog, LOG_WARNING, "error reading HTTP header");
    421             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    422             goto out;
    423         }
    424 
    425         if (((end = strchr(inbuf, '\r')) != NULL && end[1] == '\n')
    426             || (end = strchr(inbuf, '\n')) != NULL)
    427             *end = '\0';
    428         log_HTTP1(prog, LOG_TRACE, "%s", *inbuf == '\0' ? " " /* workaround for "" getting ignored */ : inbuf);
    429         if (end == NULL) {
    430             log_HTTP(prog, LOG_WARNING,
    431                 "error parsing HTTP header: missing end of line");
    432             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    433             goto out;
    434         }
    435 
    436         if (inbuf[0] == '\0')
    437             break;
    438 
    439         key = inbuf;
    440         value = strchr(key, ':');
    441         if (value == NULL) {
    442             log_HTTP(prog, LOG_WARNING,
    443                 "error parsing HTTP header: missing ':'");
    444             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    445             goto out;
    446         }
    447         *(value++) = '\0';
    448         while (*value == ' ')
    449             value++;
    450         /* https://tools.ietf.org/html/rfc7230#section-6.3 Persistence */
    451         if (found_keep_alive != NULL
    452             && OPENSSL_strcasecmp(key, "Connection") == 0) {
    453             if (OPENSSL_strcasecmp(value, "keep-alive") == 0)
    454                 *found_keep_alive = 1;
    455             else if (OPENSSL_strcasecmp(value, "close") == 0)
    456                 *found_keep_alive = 0;
    457         }
    458     }
    459 
    460 #ifdef HTTP_DAEMON
    461     /* Clear alarm before we close the client socket */
    462     alarm(0);
    463     timeout = 0;
    464 #endif
    465 
    466     /* Try to read and parse request */
    467     req = ASN1_item_d2i_bio(it, getbio != NULL ? getbio : cbio, NULL);
    468     if (req == NULL) {
    469         log_HTTP(prog, LOG_WARNING,
    470             "error parsing DER-encoded request content");
    471         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
    472     } else if (ppath != NULL && (*ppath = OPENSSL_strdup(url)) == NULL) {
    473         log_HTTP1(prog, LOG_ERR,
    474             "out of memory allocating %zu bytes", strlen(url) + 1);
    475         ASN1_item_free(req, it);
    476         goto fatal;
    477     }
    478 
    479     *preq = req;
    480 
    481 out:
    482     BIO_free_all(getbio);
    483 #ifdef HTTP_DAEMON
    484     if (timeout > 0)
    485         alarm(0);
    486     acfd = (int)INVALID_SOCKET;
    487 #endif
    488     return ret;
    489 
    490 fatal:
    491     (void)http_server_send_status(prog, cbio, 500, "Internal Server Error");
    492     if (ppath != NULL) {
    493         OPENSSL_free(*ppath);
    494         *ppath = NULL;
    495     }
    496     BIO_free_all(cbio);
    497     *pcbio = NULL;
    498     ret = -1;
    499     goto out;
    500 }
    501 
    502 /* assumes that cbio does not do an encoding that changes the output length */
    503 int http_server_send_asn1_resp(const char *prog, BIO *cbio, int keep_alive,
    504     const char *content_type,
    505     const ASN1_ITEM *it, const ASN1_VALUE *resp)
    506 {
    507     char buf[200], *p;
    508     int ret = BIO_snprintf(buf, sizeof(buf), HTTP_1_0 " 200 OK\r\n%s"
    509                                                       "Content-type: %s\r\n"
    510                                                       "Content-Length: %d\r\n",
    511         keep_alive ? "Connection: keep-alive\r\n" : "",
    512         content_type,
    513         ASN1_item_i2d(resp, NULL, it));
    514 
    515     if (ret < 0 || (size_t)ret >= sizeof(buf))
    516         return 0;
    517     if (log_get_verbosity() < LOG_TRACE && (p = strchr(buf, '\r')) != NULL)
    518         trace_log_message(-1, prog, LOG_INFO,
    519             "sending response, 1st line: %.*s", (int)(p - buf),
    520             buf);
    521     log_HTTP1(prog, LOG_TRACE, "sending response header:\n%s", buf);
    522 
    523     ret = BIO_printf(cbio, "%s\r\n", buf) > 0
    524         && ASN1_item_i2d_bio(it, cbio, resp) > 0;
    525 
    526     (void)BIO_flush(cbio);
    527     return ret;
    528 }
    529 
    530 int http_server_send_status(const char *prog, BIO *cbio,
    531     int status, const char *reason)
    532 {
    533     char buf[200];
    534     int ret = BIO_snprintf(buf, sizeof(buf), HTTP_1_0 " %d %s\r\n\r\n",
    535         /* This implicitly cancels keep-alive */
    536         status, reason);
    537 
    538     if (ret < 0 || (size_t)ret >= sizeof(buf))
    539         return 0;
    540     log_HTTP1(prog, LOG_TRACE, "sending response header:\n%s", buf);
    541 
    542     ret = BIO_printf(cbio, "%s\r\n", buf) > 0;
    543     (void)BIO_flush(cbio);
    544     return ret;
    545 }
    546 #endif
    547