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