xref: /openssl/apps/lib/http_server.c (revision ec4b123a)
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 
socket_timeout(int signum)50 void socket_timeout(int signum)
51 {
52     if (acfd != (int)INVALID_SOCKET)
53         (void)shutdown(acfd, SHUT_RD);
54 }
55 
killall(int ret,pid_t * kidpids)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 
noteterm(int sig)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  */
spawn_loop(const char * prog)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
http_server_init(const char * prog,const char * port,int verb)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  */
urldecode(char * p)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 */
http_server_get_asn1_req(const ASN1_ITEM * it,ASN1_VALUE ** preq,char ** ppath,BIO ** pcbio,BIO * acbio,int * found_keep_alive,const char * prog,int accept_get,int timeout)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' ?
429                   " " /* workaround for "" getting ignored */ : inbuf);
430         if (end == NULL) {
431             log_HTTP(prog, LOG_WARNING,
432                      "error parsing HTTP header: missing end of line");
433             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
434             goto out;
435         }
436 
437         if (inbuf[0] == '\0')
438             break;
439 
440         key = inbuf;
441         value = strchr(key, ':');
442         if (value == NULL) {
443             log_HTTP(prog, LOG_WARNING,
444                      "error parsing HTTP header: missing ':'");
445             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
446             goto out;
447         }
448         *(value++) = '\0';
449         while (*value == ' ')
450             value++;
451         /* https://tools.ietf.org/html/rfc7230#section-6.3 Persistence */
452         if (found_keep_alive != NULL
453             && OPENSSL_strcasecmp(key, "Connection") == 0) {
454             if (OPENSSL_strcasecmp(value, "keep-alive") == 0)
455                 *found_keep_alive = 1;
456             else if (OPENSSL_strcasecmp(value, "close") == 0)
457                 *found_keep_alive = 0;
458         }
459     }
460 
461 # ifdef HTTP_DAEMON
462     /* Clear alarm before we close the client socket */
463     alarm(0);
464     timeout = 0;
465 # endif
466 
467     /* Try to read and parse request */
468     req = ASN1_item_d2i_bio(it, getbio != NULL ? getbio : cbio, NULL);
469     if (req == NULL) {
470         log_HTTP(prog, LOG_WARNING,
471                  "error parsing DER-encoded request content");
472         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
473     } else if (ppath != NULL && (*ppath = OPENSSL_strdup(url)) == NULL) {
474         log_HTTP1(prog, LOG_ERR,
475                   "out of memory allocating %zu bytes", strlen(url) + 1);
476         ASN1_item_free(req, it);
477         goto fatal;
478     }
479 
480     *preq = req;
481 
482  out:
483     BIO_free_all(getbio);
484 # ifdef HTTP_DAEMON
485     if (timeout > 0)
486         alarm(0);
487     acfd = (int)INVALID_SOCKET;
488 # endif
489     return ret;
490 
491  fatal:
492     (void)http_server_send_status(prog, cbio, 500, "Internal Server Error");
493     if (ppath != NULL) {
494         OPENSSL_free(*ppath);
495         *ppath = NULL;
496     }
497     BIO_free_all(cbio);
498     *pcbio = NULL;
499     ret = -1;
500     goto out;
501 }
502 
503 /* assumes that cbio does not do an encoding that changes the output length */
http_server_send_asn1_resp(const char * prog,BIO * cbio,int keep_alive,const char * content_type,const ASN1_ITEM * it,const ASN1_VALUE * resp)504 int http_server_send_asn1_resp(const char *prog, BIO *cbio, int keep_alive,
505                                const char *content_type,
506                                const ASN1_ITEM *it, const ASN1_VALUE *resp)
507 {
508     char buf[200], *p;
509     int ret = BIO_snprintf(buf, sizeof(buf), HTTP_1_0" 200 OK\r\n%s"
510                            "Content-type: %s\r\n"
511                            "Content-Length: %d\r\n",
512                            keep_alive ? "Connection: keep-alive\r\n" : "",
513                            content_type,
514                            ASN1_item_i2d(resp, NULL, it));
515 
516     if (ret < 0 || (size_t)ret >= sizeof(buf))
517         return 0;
518     if (log_get_verbosity() < LOG_TRACE && (p = strchr(buf, '\r')) != NULL)
519         trace_log_message(-1, prog, LOG_INFO,
520                           "sending response, 1st line: %.*s", (int)(p - buf),
521                           buf);
522     log_HTTP1(prog, LOG_TRACE, "sending response header:\n%s", buf);
523 
524     ret = BIO_printf(cbio, "%s\r\n", buf) > 0
525         && ASN1_item_i2d_bio(it, cbio, resp) > 0;
526 
527     (void)BIO_flush(cbio);
528     return ret;
529 }
530 
http_server_send_status(const char * prog,BIO * cbio,int status,const char * reason)531 int http_server_send_status(const char *prog, BIO *cbio,
532                             int status, const char *reason)
533 {
534     char buf[200];
535     int ret = BIO_snprintf(buf, sizeof(buf), HTTP_1_0" %d %s\r\n\r\n",
536                            /* This implicitly cancels keep-alive */
537                            status, reason);
538 
539     if (ret < 0 || (size_t)ret >= sizeof(buf))
540         return 0;
541     log_HTTP1(prog, LOG_TRACE, "sending response header:\n%s", buf);
542 
543     ret = BIO_printf(cbio, "%s\r\n", buf) > 0;
544     (void)BIO_flush(cbio);
545     return ret;
546 }
547 #endif
548