text: /*
   * 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
  