openssl/apps/lib/http_server.c
Randall S. Becker 08073700cc NonStop port updates for 3.0.0.
HPE NonStop Port Changes for 3.0.0  Includes unthreaded, PUT, and SPT for OSS.

The port changes include wrapping where necessary for FLOSS and
appropriate configuration changes to support that. Two tests
are excluded as being inappropriate for the platform.

The changes are:
* Added /usr/local/include to nonstop-nsx_spt_floss to load floss.h
* Added SPT Floss variant for NonStop
* Wrapped FLOSS definitions in OPENSSL_TANDEM_FLOSS to allow selective enablement.
* SPT build configuration for NonStop
* Skip tests not relevant for NonStop
* PUT configuration changes required for NonStop platforms
* Configurations/50-nonstop.conf: updates for TNS/X platform.
* FLOSS instrumentation for HPE NonStop TNS/X and TNS/E platforms.
* Configurations/50-nonstop.conf: modifications for non-PUT TNS/E platform b
* Fix use of DELAY in ssltestlib.c for HPNS.
* Fixed commit merge issues and added floss to http_server.c

CLA: Permission is granted by the author to the OpenSSL team to use these modifications.
Fixes #5087.

Signed-off-by: Randall S. Becker <rsbecker@nexbridge.com>

Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/12800)
2020-09-12 20:32:11 +02:00

455 lines
13 KiB
C

/*
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
/* Very basic HTTP server */
#if !defined(_POSIX_C_SOURCE) && defined(OPENSSL_SYS_VMS)
/*
* On VMS, you need to define this to get the declaration of fileno(). The
* value 2 is to make sure no function defined in POSIX-2 is left undefined.
*/
# define _POSIX_C_SOURCE 2
#endif
#include <string.h>
#include <ctype.h>
#include "http_server.h"
#include "internal/sockets.h"
#include <openssl/err.h>
#include <openssl/rand.h>
#if defined(__TANDEM)
# if defined(OPENSSL_TANDEM_FLOSS)
# include <floss.h(floss_fork)>
# endif
#endif
int multi = 0; /* run multiple responder processes */
#ifdef HTTP_DAEMON
int acfd = (int) INVALID_SOCKET;
#endif
#ifdef HTTP_DAEMON
static int print_syslog(const char *str, size_t len, void *levPtr)
{
int level = *(int *)levPtr;
int ilen = len > MAXERRLEN ? MAXERRLEN : len;
syslog(level, "%.*s", ilen, str);
return ilen;
}
#endif
void log_message(const char *prog, int level, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
#ifdef HTTP_DAEMON
if (multi) {
char buf[1024];
if (vsnprintf(buf, sizeof(buf), fmt, ap) > 0)
syslog(level, "%s", buf);
if (level >= LOG_ERR)
ERR_print_errors_cb(print_syslog, &level);
}
#endif
if (!multi) {
BIO_printf(bio_err, "%s: ", prog);
BIO_vprintf(bio_err, fmt, ap);
BIO_printf(bio_err, "\n");
}
va_end(ap);
}
#ifdef HTTP_DAEMON
void socket_timeout(int signum)
{
if (acfd != (int)INVALID_SOCKET)
(void)shutdown(acfd, SHUT_RD);
}
static void killall(int ret, pid_t *kidpids)
{
int i;
for (i = 0; i < multi; ++i)
if (kidpids[i] != 0)
(void)kill(kidpids[i], SIGTERM);
OPENSSL_free(kidpids);
sleep(1);
exit(ret);
}
static int termsig = 0;
static void noteterm(int sig)
{
termsig = sig;
}
/*
* Loop spawning up to `multi` child processes, only child processes return
* from this function. The parent process loops until receiving a termination
* signal, kills extant children and exits without returning.
*/
void spawn_loop(const char *prog)
{
pid_t *kidpids = NULL;
int status;
int procs = 0;
int i;
openlog(prog, LOG_PID, LOG_DAEMON);
if (setpgid(0, 0)) {
syslog(LOG_ERR, "fatal: error detaching from parent process group: %s",
strerror(errno));
exit(1);
}
kidpids = app_malloc(multi * sizeof(*kidpids), "child PID array");
for (i = 0; i < multi; ++i)
kidpids[i] = 0;
signal(SIGINT, noteterm);
signal(SIGTERM, noteterm);
while (termsig == 0) {
pid_t fpid;
/*
* Wait for a child to replace when we're at the limit.
* Slow down if a child exited abnormally or waitpid() < 0
*/
while (termsig == 0 && procs >= multi) {
if ((fpid = waitpid(-1, &status, 0)) > 0) {
for (i = 0; i < procs; ++i) {
if (kidpids[i] == fpid) {
kidpids[i] = 0;
--procs;
break;
}
}
if (i >= multi) {
syslog(LOG_ERR, "fatal: internal error: "
"no matching child slot for pid: %ld",
(long) fpid);
killall(1, kidpids);
}
if (status != 0) {
if (WIFEXITED(status))
syslog(LOG_WARNING, "child process: %ld, exit status: %d",
(long)fpid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
syslog(LOG_WARNING, "child process: %ld, term signal %d%s",
(long)fpid, WTERMSIG(status),
# ifdef WCOREDUMP
WCOREDUMP(status) ? " (core dumped)" :
# endif
"");
sleep(1);
}
break;
} else if (errno != EINTR) {
syslog(LOG_ERR, "fatal: waitpid(): %s", strerror(errno));
killall(1, kidpids);
}
}
if (termsig)
break;
switch (fpid = fork()) {
case -1: /* error */
/* System critically low on memory, pause and try again later */
sleep(30);
break;
case 0: /* child */
OPENSSL_free(kidpids);
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
if (termsig)
_exit(0);
if (RAND_poll() <= 0) {
syslog(LOG_ERR, "fatal: RAND_poll() failed");
_exit(1);
}
return;
default: /* parent */
for (i = 0; i < multi; ++i) {
if (kidpids[i] == 0) {
kidpids[i] = fpid;
procs++;
break;
}
}
if (i >= multi) {
syslog(LOG_ERR, "fatal: internal error: no free child slots");
killall(1, kidpids);
}
break;
}
}
/* The loop above can only break on termsig */
syslog(LOG_INFO, "terminating on signal: %d", termsig);
killall(0, kidpids);
}
#endif
#ifndef OPENSSL_NO_SOCK
BIO *http_server_init_bio(const char *prog, const char *port)
{
BIO *acbio = NULL, *bufbio;
bufbio = BIO_new(BIO_f_buffer());
if (bufbio == NULL)
goto err;
acbio = BIO_new(BIO_s_accept());
if (acbio == NULL
|| BIO_set_bind_mode(acbio, BIO_BIND_REUSEADDR) < 0
|| BIO_set_accept_port(acbio, port) < 0) {
log_message(prog, LOG_ERR, "Error setting up accept BIO");
goto err;
}
BIO_set_accept_bios(acbio, bufbio);
bufbio = NULL;
if (BIO_do_accept(acbio) <= 0) {
log_message(prog, LOG_ERR, "Error starting accept");
goto err;
}
return acbio;
err:
BIO_free_all(acbio);
BIO_free(bufbio);
return NULL;
}
/*
* Decode %xx URL-decoding in-place. Ignores malformed sequences.
*/
static int urldecode(char *p)
{
unsigned char *out = (unsigned char *)p;
unsigned char *save = out;
for (; *p; p++) {
if (*p != '%') {
*out++ = *p;
} else if (isxdigit(_UC(p[1])) && isxdigit(_UC(p[2]))) {
/* Don't check, can't fail because of ixdigit() call. */
*out++ = (OPENSSL_hexchar2int(p[1]) << 4)
| OPENSSL_hexchar2int(p[2]);
p += 2;
} else {
return -1;
}
}
*out = '\0';
return (int)(out - save);
}
int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
char **ppath, BIO **pcbio, BIO *acbio,
const char *prog, int accept_get, int timeout)
{
BIO *cbio = NULL, *getbio = NULL, *b64 = NULL;
int len;
char reqbuf[2048], inbuf[2048];
char *meth, *url, *end;
ASN1_VALUE *req;
int ret = 1;
*preq = NULL;
if (ppath != NULL)
*ppath = NULL;
*pcbio = NULL;
/* Connection loss before accept() is routine, ignore silently */
if (BIO_do_accept(acbio) <= 0)
return 0;
cbio = BIO_pop(acbio);
*pcbio = cbio;
if (cbio == NULL) {
/* Cannot call http_server_send_status(cbio, ...) */
ret = -1;
goto out;
}
# ifdef HTTP_DAEMON
if (timeout > 0) {
(void)BIO_get_fd(cbio, &acfd);
alarm(timeout);
}
# endif
/* Read the request line. */
len = BIO_gets(cbio, reqbuf, sizeof(reqbuf));
if (len <= 0) {
log_message(prog, LOG_INFO,
"Request line read error or empty request");
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
}
meth = reqbuf;
url = meth + 3;
if ((accept_get && strncmp(meth, "GET ", 4) == 0)
|| (url++, strncmp(meth, "POST ", 5) == 0)) {
/* Expecting (GET|POST) {sp} /URL {sp} HTTP/1.x */
*(url++) = '\0';
while (*url == ' ')
url++;
if (*url != '/') {
log_message(prog, LOG_INFO,
"Invalid %s -- URL does not begin with '/': %s",
meth, url);
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
}
url++;
/* Splice off the HTTP version identifier. */
for (end = url; *end != '\0'; end++)
if (*end == ' ')
break;
if (strncmp(end, " HTTP/1.", 7) != 0) {
log_message(prog, LOG_INFO,
"Invalid %s -- bad HTTP/version string: %s",
meth, end + 1);
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
}
*end = '\0';
/*-
* Skip "GET / HTTP..." requests often used by load-balancers.
* 'url' was incremented above to point to the first byte *after*
* the leading slash, so in case 'GET / ' it is now an empty string.
*/
if (strlen(meth) == 3 && url[0] == '\0') {
(void)http_server_send_status(cbio, 200, "OK");
goto out;
}
len = urldecode(url);
if (len < 0) {
log_message(prog, LOG_INFO,
"Invalid %s request -- bad URL encoding: %s",
meth, url);
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
}
if (strlen(meth) == 3) { /* GET */
if ((getbio = BIO_new_mem_buf(url, len)) == NULL
|| (b64 = BIO_new(BIO_f_base64())) == NULL) {
log_message(prog, LOG_ERR,
"Could not allocate base64 bio with size = %d",
len);
goto fatal;
}
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
getbio = BIO_push(b64, getbio);
}
} else {
log_message(prog, LOG_INFO,
"HTTP request does not start with GET/POST: %s", reqbuf);
/* TODO provide better diagnosis in case client tries TLS */
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
}
/* chop any further/duplicate leading or trailing '/' */
while (*url == '/')
url++;
while (end >= url + 2 && end[-2] == '/' && end[-1] == '/')
end--;
*end = '\0';
/* Read and skip past the headers. */
for (;;) {
len = BIO_gets(cbio, inbuf, sizeof(inbuf));
if (len <= 0) {
log_message(prog, LOG_ERR,
"Error skipping remaining HTTP headers");
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
}
if ((inbuf[0] == '\r') || (inbuf[0] == '\n'))
break;
}
# ifdef HTTP_DAEMON
/* Clear alarm before we close the client socket */
alarm(0);
timeout = 0;
# endif
/* Try to read and parse request */
req = ASN1_item_d2i_bio(it, getbio != NULL ? getbio : cbio, NULL);
if (req == NULL) {
log_message(prog, LOG_ERR, "Error parsing request");
} else if (ppath != NULL && (*ppath = OPENSSL_strdup(url)) == NULL) {
log_message(prog, LOG_ERR,
"Out of memory allocating %zu bytes", strlen(url) + 1);
ASN1_item_free(req, it);
goto fatal;
}
*preq = req;
out:
BIO_free_all(getbio);
# ifdef HTTP_DAEMON
if (timeout > 0)
alarm(0);
acfd = (int)INVALID_SOCKET;
# endif
return ret;
fatal:
(void)http_server_send_status(cbio, 500, "Internal Server Error");
if (ppath != NULL) {
OPENSSL_free(*ppath);
*ppath = NULL;
}
BIO_free_all(cbio);
*pcbio = NULL;
ret = -1;
goto out;
}
/* assumes that cbio does not do an encoding that changes the output length */
int http_server_send_asn1_resp(BIO *cbio, const char *content_type,
const ASN1_ITEM *it, const ASN1_VALUE *resp)
{
int ret = BIO_printf(cbio, "HTTP/1.0 200 OK\r\nContent-type: %s\r\n"
"Content-Length: %d\r\n\r\n", content_type,
ASN1_item_i2d(resp, NULL, it)) > 0
&& ASN1_item_i2d_bio(it, cbio, resp) > 0;
(void)BIO_flush(cbio);
return ret;
}
int http_server_send_status(BIO *cbio, int status, const char *reason)
{
int ret = BIO_printf(cbio, "HTTP/1.0 %d %s\r\n\r\n", status, reason) > 0;
(void)BIO_flush(cbio);
return ret;
}
#endif