VirtualBox

source: vbox/trunk/src/libs/openssl-3.4.1/apps/lib/http_server.c@ 109302

Last change on this file since 109302 was 109052, checked in by vboxsync, 4 weeks ago

openssl-3.4.1: Applied our changes, regenerated files, added missing files and functions. This time with a three way merge. ​bugref:10890

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette