xref: /openssl/apps/lib/http_server.c (revision 23b795d3)
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_bind_mode(acbio, BIO_BIND_REUSEADDR) < 0
208         || BIO_set_accept_name(acbio, name) < 0) {
209         log_HTTP(prog, LOG_ERR, "error setting up accept BIO");
210         goto err;
211     }
212 
213     BIO_set_accept_bios(acbio, bufbio);
214     bufbio = NULL;
215     if (BIO_do_accept(acbio) <= 0) {
216         log_HTTP1(prog, LOG_ERR, "error setting accept on port %s", port);
217         goto err;
218     }
219 
220     /* Report back what address and port are used */
221     BIO_get_fd(acbio, &asock);
222     port_num = report_server_accept(bio_out, asock, 1, 1);
223     if (port_num == 0) {
224         log_HTTP(prog, LOG_ERR, "error printing ACCEPT string");
225         goto err;
226     }
227 
228     return acbio;
229 
230  err:
231     ERR_print_errors(bio_err);
232     BIO_free_all(acbio);
233     BIO_free(bufbio);
234     return NULL;
235 }
236 
237 /*
238  * Decode %xx URL-decoding in-place. Ignores malformed sequences.
239  */
urldecode(char * p)240 static int urldecode(char *p)
241 {
242     unsigned char *out = (unsigned char *)p;
243     unsigned char *save = out;
244 
245     for (; *p; p++) {
246         if (*p != '%') {
247             *out++ = *p;
248         } else if (isxdigit(_UC(p[1])) && isxdigit(_UC(p[2]))) {
249             /* Don't check, can't fail because of ixdigit() call. */
250             *out++ = (OPENSSL_hexchar2int(p[1]) << 4)
251                 | OPENSSL_hexchar2int(p[2]);
252             p += 2;
253         } else {
254             return -1;
255         }
256     }
257     *out = '\0';
258     return (int)(out - save);
259 }
260 
261 /* if *pcbio != NULL, continue given connected session, else accept new */
262 /* 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)263 int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
264                              char **ppath, BIO **pcbio, BIO *acbio,
265                              int *found_keep_alive,
266                              const char *prog, int accept_get, int timeout)
267 {
268     BIO *cbio = *pcbio, *getbio = NULL, *b64 = NULL;
269     int len;
270     char reqbuf[2048], inbuf[2048];
271     char *meth, *url, *end;
272     ASN1_VALUE *req;
273     int ret = 0;
274 
275     *preq = NULL;
276     if (ppath != NULL)
277         *ppath = NULL;
278 
279     if (cbio == NULL) {
280         char *port;
281 
282         get_sock_info_address(BIO_get_fd(acbio, NULL), NULL, &port);
283         if (port == NULL) {
284             log_HTTP(prog, LOG_ERR, "cannot get port listening on");
285             goto fatal;
286         }
287         log_HTTP1(prog, LOG_DEBUG,
288                   "awaiting new connection on port %s ...", port);
289         OPENSSL_free(port);
290 
291         if (BIO_do_accept(acbio) <= 0)
292             /* Connection loss before accept() is routine, ignore silently */
293             return ret;
294 
295         *pcbio = cbio = BIO_pop(acbio);
296     } else {
297         log_HTTP(prog, LOG_DEBUG, "awaiting next request ...");
298     }
299     if (cbio == NULL) {
300         /* Cannot call http_server_send_status(..., cbio, ...) */
301         ret = -1;
302         goto out;
303     }
304 
305 # ifdef HTTP_DAEMON
306     if (timeout > 0) {
307         (void)BIO_get_fd(cbio, &acfd);
308         alarm(timeout);
309     }
310 # endif
311 
312     /* Read the request line. */
313     len = BIO_gets(cbio, reqbuf, sizeof(reqbuf));
314     if (len == 0)
315         return ret;
316     ret = 1;
317     if (len < 0) {
318         log_HTTP(prog, LOG_WARNING, "request line read error");
319         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
320         goto out;
321     }
322 
323     if (((end = strchr(reqbuf, '\r')) != NULL && end[1] == '\n')
324             || (end = strchr(reqbuf, '\n')) != NULL)
325         *end = '\0';
326     if (log_get_verbosity() < LOG_TRACE)
327         trace_log_message(-1, prog, LOG_INFO,
328                           "received request, 1st line: %s", reqbuf);
329     log_HTTP(prog, LOG_TRACE, "received request header:");
330     log_HTTP1(prog, LOG_TRACE, "%s", reqbuf);
331     if (end == NULL) {
332         log_HTTP(prog, LOG_WARNING,
333                  "cannot parse HTTP header: missing end of line");
334         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
335         goto out;
336     }
337 
338     url = meth = reqbuf;
339     if ((accept_get && CHECK_AND_SKIP_PREFIX(url, "GET "))
340             || CHECK_AND_SKIP_PREFIX(url, "POST ")) {
341 
342         /* Expecting (GET|POST) {sp} /URL {sp} HTTP/1.x */
343         url[-1] = '\0';
344         while (*url == ' ')
345             url++;
346         if (*url != '/') {
347             log_HTTP2(prog, LOG_WARNING,
348                       "invalid %s -- URL does not begin with '/': %s",
349                       meth, url);
350             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
351             goto out;
352         }
353         url++;
354 
355         /* Splice off the HTTP version identifier. */
356         for (end = url; *end != '\0'; end++)
357             if (*end == ' ')
358                 break;
359         if (!HAS_PREFIX(end, HTTP_VERSION_STR)) {
360             log_HTTP2(prog, LOG_WARNING,
361                       "invalid %s -- bad HTTP/version string: %s",
362                       meth, end + 1);
363             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
364             goto out;
365         }
366         *end = '\0';
367         /* above HTTP 1.0, connection persistence is the default */
368         if (found_keep_alive != NULL)
369             *found_keep_alive = end[sizeof(HTTP_VERSION_STR) - 1] > '0';
370 
371         /*-
372          * Skip "GET / HTTP..." requests often used by load-balancers.
373          * 'url' was incremented above to point to the first byte *after*
374          * the leading slash, so in case 'GET / ' it is now an empty string.
375          */
376         if (strlen(meth) == 3 && url[0] == '\0') {
377             (void)http_server_send_status(prog, cbio, 200, "OK");
378             goto out;
379         }
380 
381         len = urldecode(url);
382         if (len < 0) {
383             log_HTTP2(prog, LOG_WARNING,
384                       "invalid %s request -- bad URL encoding: %s", meth, url);
385             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
386             goto out;
387         }
388         if (strlen(meth) == 3) { /* GET */
389             if ((getbio = BIO_new_mem_buf(url, len)) == NULL
390                 || (b64 = BIO_new(BIO_f_base64())) == NULL) {
391                 log_HTTP1(prog, LOG_ERR,
392                           "could not allocate base64 bio with size = %d", len);
393                 goto fatal;
394             }
395             BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
396             getbio = BIO_push(b64, getbio);
397         }
398     } else {
399         log_HTTP2(prog, LOG_WARNING,
400                   "HTTP request does not begin with %sPOST: %s",
401                   accept_get ? "GET or " : "", reqbuf);
402         (void)http_server_send_status(prog, cbio, 400, "Bad Request");
403         goto out;
404     }
405 
406     /* chop any further/duplicate leading or trailing '/' */
407     while (*url == '/')
408         url++;
409     while (end >= url + 2 && end[-2] == '/' && end[-1] == '/')
410         end--;
411     *end = '\0';
412 
413     /* Read and skip past the headers. */
414     for (;;) {
415         char *key, *value;
416 
417         len = BIO_gets(cbio, inbuf, sizeof(inbuf));
418         if (len <= 0) {
419             log_HTTP(prog, LOG_WARNING, "error reading HTTP header");
420             (void)http_server_send_status(prog, cbio, 400, "Bad Request");
421             goto out;
422         }
423 
424         if (((end = strchr(inbuf, '\r')) != NULL && end[1] == '\n')
425             || (end = strchr(inbuf, '\n')) != NULL)
426             *end = '\0';
427         log_HTTP1(prog, LOG_TRACE, "%s", *inbuf == '\0' ?
428                   " " /* 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 */
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)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 
http_server_send_status(const char * prog,BIO * cbio,int status,const char * reason)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