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