Establish encrypted client connection earlier ...
authorFabian Keil <fk@fabiankeil.de>
Thu, 24 Jan 2019 12:24:15 +0000 (13:24 +0100)
committerFabian Keil <fk@fabiankeil.de>
Wed, 30 Oct 2019 10:23:10 +0000 (11:23 +0100)
... so that we can parse and filter the encrypted
request before deciding if it gets forwarded.

This commit is incomplete and hasn't been tested
yet with large POST requests.

Sponsored by: Robert Klemme

jcc.c
parsers.c
parsers.h
project.h
ssl.c
ssl.h

diff --git a/jcc.c b/jcc.c
index c30e75d..3101cbc 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -150,7 +150,7 @@ static int client_protocol_is_unsupported(const struct client_state *csp, char *
 static jb_err get_request_destination_elsewhere(struct client_state *csp, struct list *headers);
 static jb_err get_server_headers(struct client_state *csp);
 static const char *crunch_reason(const struct http_response *rsp);
-static void send_crunch_response(const struct client_state *csp, struct http_response *rsp);
+static void send_crunch_response(struct client_state *csp, struct http_response *rsp);
 static char *get_request_line(struct client_state *csp);
 static jb_err receive_client_request(struct client_state *csp);
 static jb_err parse_client_request(struct client_state *csp);
@@ -809,7 +809,7 @@ static void log_applied_actions(const struct current_action_spec *actions)
  * Returns     :  Nothing.
  *
  *********************************************************************/
-static void send_crunch_response(const struct client_state *csp, struct http_response *rsp)
+static void send_crunch_response(struct client_state *csp, struct http_response *rsp)
 {
       const struct http_request *http = csp->http;
       char status_code[4];
@@ -844,13 +844,33 @@ static void send_crunch_response(const struct client_state *csp, struct http_res
          csp->ip_addr_str, http->ocmd, status_code, rsp->content_length);
 
       /* Write the answer to the client */
-      if (write_socket_delayed(csp->cfd, rsp->head, rsp->head_length, get_write_delay(csp))
-       || write_socket_delayed(csp->cfd, rsp->body, rsp->content_length, get_write_delay(csp)))
+#ifdef FEATURE_HTTPS_FILTERING
+      if (client_use_ssl(csp))
       {
-         /* There is nothing we can do about it. */
-         log_error(LOG_LEVEL_CONNECT,
-            "Couldn't deliver the error message for %s through client socket %d: %E",
-            http->url, csp->cfd);
+         if ((ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+                 (const unsigned char *)rsp->head, rsp->head_length) < 0)
+          || (ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+                 (const unsigned char *)rsp->body, rsp->content_length) < 0))
+         {
+            /* There is nothing we can do about it. */
+            log_error(LOG_LEVEL_CONNECT, "Couldn't deliver the error message "
+               "for %s through client socket %d using TLS/SSL",
+               http->url, csp->cfd);
+         }
+      }
+      else
+#endif
+      {
+         if (write_socket_delayed(csp->cfd, rsp->head, rsp->head_length,
+                get_write_delay(csp))
+          || write_socket_delayed(csp->cfd, rsp->body, rsp->content_length,
+                get_write_delay(csp)))
+         {
+            /* There is nothing we can do about it. */
+            log_error(LOG_LEVEL_CONNECT,
+               "Couldn't deliver the error message for %s through client socket %d: %E",
+               http->url, csp->cfd);
+         }
       }
 
       /* Clean up and return */
@@ -1994,6 +2014,300 @@ static int send_http_request(struct client_state *csp)
 }
 
 
+#ifdef FEATURE_HTTPS_FILTERING
+/*********************************************************************
+ *
+ * Function    : send_https_request
+ *
+ * Description : Sends the HTTP headers from the client request
+ *               and all the body data that has already been received.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  0 on success, anything else is an error.
+ *
+ *********************************************************************/
+static int send_https_request(struct client_state *csp)
+{
+   char *hdr;
+   int ret;
+   long flushed = 0;
+
+   hdr = list_to_text(csp->https_headers);
+   if (hdr == NULL)
+   {
+      /* FIXME Should handle error properly */
+      log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header");
+   }
+   list_remove_all(csp->https_headers);
+
+   /*
+    * Write the client's (modified) header to the server
+    * (along with anything else that may be in the buffer)
+    */
+   ret = ssl_send_data(&(csp->mbedtls_server_attr.ssl),
+      (const unsigned char *)hdr, strlen(hdr));
+   freez(hdr);
+
+   if (ret < 0)
+   {
+      log_error(LOG_LEVEL_CONNECT,
+         "Failed sending encrypted request headers to: %s: %E",
+         csp->http->hostport);
+      mark_server_socket_tainted(csp);
+      close_client_and_server_ssl_connections(csp);
+      return 1;
+   }
+
+   if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
+      && ((flushed = ssl_flush_socket(&(csp->mbedtls_server_attr.ssl),
+            csp->client_iob)) < 0))
+   {
+      log_error(LOG_LEVEL_CONNECT, "Failed sending request body to: %s: %E",
+         csp->http->hostport);
+      return 1;
+   }
+   if (flushed != 0)
+   {
+      if (csp->expected_client_content_length != 0)
+      {
+         if (csp->expected_client_content_length < flushed)
+         {
+            log_error(LOG_LEVEL_ERROR,
+               "Flushed %d bytes of request body while only expecting %llu",
+               flushed, csp->expected_client_content_length);
+            csp->expected_client_content_length = 0;
+         }
+         else
+         {
+            log_error(LOG_LEVEL_CONNECT,
+               "Flushed %d bytes of request body while expecting %llu",
+               flushed, csp->expected_client_content_length);
+            csp->expected_client_content_length -= (unsigned)flushed;
+         }
+      }
+      else
+      {
+         log_error(LOG_LEVEL_CONNECT,
+            "Flushed %d bytes of request body", flushed);
+      }
+   }
+
+   log_error(LOG_LEVEL_CONNECT, "Encrypted request sent");
+
+   return 0;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  receive_encrypted_request
+ *
+ * Description :  Receives an encrypted request.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success,
+ *                JB_ERR_PARSE or JB_ERR_MEMORY otherwise
+ *
+ *********************************************************************/
+static jb_err receive_encrypted_request(struct client_state *csp)
+{
+   char buf[BUFFER_SIZE];
+   int len;
+   char *p;
+
+   do
+   {
+      log_error(LOG_LEVEL_HEADER, "Reading encrypted headers");
+      if (!data_is_available(csp->cfd, (int)csp->config->keep_alive_timeout))
+      {
+         log_error(LOG_LEVEL_CONNECT,
+            "Socket %d timed out while waiting for client headers", csp->cfd);
+         return JB_ERR_PARSE;
+      }
+      len = ssl_recv_data(&(csp->mbedtls_client_attr.ssl),
+         (unsigned char *)buf, sizeof(buf));
+      if (len == -1)
+      {
+         return JB_ERR_PARSE;
+      }
+      if (add_to_iob(csp->client_iob, csp->config->buffer_limit, buf, len))
+      {
+         return JB_ERR_MEMORY;
+      }
+      p = strstr(csp->client_iob->cur, "\r\n\r\n");
+   } while (p == NULL);
+
+   log_error(LOG_LEVEL_HEADER, "Encrypted headers received completely");
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  process_encrypted_request
+ *
+ * Description :  Receives and parses an encrypted request.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success,
+ *                JB_ERR_PARSE or JB_ERR_MEMORY otherwise
+ *
+ *********************************************************************/
+static jb_err process_encrypted_request(struct client_state *csp)
+{
+   char *p;
+   char *request_line;
+   jb_err err;
+   /* Temporary copy of the client's headers before they get enlisted in csp->https_headers */
+   struct list header_list;
+   struct list *headers = &header_list;
+
+   err = receive_encrypted_request(csp);
+   if (err != JB_ERR_OK)
+   {
+      /* XXX: Also used for JB_ERR_MEMORY */
+      ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+         (const unsigned char *)CHEADER, strlen(CHEADER));
+      return err;
+   }
+
+   /* We don't need get_request_line() because the whole HTTP head is buffered. */
+   request_line = get_header(csp->client_iob);
+   if (request_line == NULL)
+   {
+      ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+         (const unsigned char *)CHEADER, strlen(CHEADER));
+      return JB_ERR_PARSE;
+   }
+   assert(*request_line != '\0');
+
+   if (client_protocol_is_unsupported(csp, request_line))
+   {
+      ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+         (const unsigned char *)CHEADER, strlen(CHEADER));
+      return JB_ERR_PARSE;
+   }
+
+#ifdef FEATURE_FORCE_LOAD
+   if (force_required(csp, request_line))
+   {
+      csp->flags |= CSP_FLAG_FORCED;
+   }
+#endif /* def FEATURE_FORCE_LOAD */
+
+   free_http_request(csp->http);
+
+   err = parse_http_request(request_line, csp->http);
+   /* XXX: Restore ssl setting. This is ugly */
+   csp->http->client_ssl = 1;
+   csp->http->server_ssl = 1;
+
+   freez(request_line);
+   if (JB_ERR_OK != err)
+   {
+      ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+         (const unsigned char *)CHEADER, strlen(CHEADER));
+      /* XXX: Use correct size */
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str);
+      log_error(LOG_LEVEL_ERROR,
+         "Couldn't parse request line received from %s: %s",
+         csp->ip_addr_str, jb_err_to_string(err));
+
+      free_http_request(csp->http);
+      return JB_ERR_PARSE;
+   }
+
+   /* Parse the rest of the client's headers. */
+   init_list(headers);
+   for (;;)
+   {
+      p = get_header(csp->client_iob);
+
+      if (p == NULL)
+      {
+         /* There are no additional headers to read. */
+         break;
+      }
+      enlist(headers, p);
+      freez(p);
+   }
+
+   if (JB_ERR_OK != get_destination_from_https_headers(headers, csp->http))
+   {
+      /*
+       * Our attempts to get the request destination
+       * elsewhere failed.
+       */
+      ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+         (const unsigned char *)CHEADER, strlen(CHEADER));
+      return JB_ERR_PARSE;
+   }
+
+#ifndef FEATURE_EXTENDED_HOST_PATTERNS
+   /* Split the domain we just got for pattern matching */
+   init_domain_components(csp->http);
+#endif
+
+   /*
+    * Determine the actions for this URL
+    */
+#ifdef FEATURE_TOGGLE
+   if (!(csp->flags & CSP_FLAG_TOGGLED_ON))
+   {
+      /* Most compatible set of actions (i.e. none) */
+      init_current_action(csp->action);
+   }
+   else
+#endif /* ndef FEATURE_TOGGLE */
+   {
+      get_url_actions(csp, csp->http);
+   }
+
+   enlist(csp->https_headers, csp->http->cmd);
+
+   /* Append the previously read headers */
+   err = list_append_list_unique(csp->https_headers, headers);
+   destroy_list(headers);
+   if (JB_ERR_OK != err)
+   {
+      /* XXX: Send error message */
+      return err;
+   }
+
+   /* XXX: Work around crash */
+   csp->error_message = NULL;
+
+   /* XXX: Why do this here? */
+   csp->http->ssl = 1;
+
+   err = sed_https(csp);
+   if (JB_ERR_OK != err)
+   {
+      ssl_send_data(&(csp->mbedtls_client_attr.ssl),
+         (const unsigned char *)CHEADER, strlen(CHEADER));
+      log_error(LOG_LEVEL_ERROR, "Failed to parse client request from %s.",
+         csp->ip_addr_str);
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0",
+         csp->ip_addr_str, csp->http->cmd);
+      return JB_ERR_PARSE;
+   }
+
+   log_error(LOG_LEVEL_HEADER, "Encrypted request processed");
+
+   return err;
+
+}
+#endif
+
+
 /*********************************************************************
  *
  * Function    :  handle_established_connection
@@ -3353,7 +3667,60 @@ static void chat(struct client_state *csp)
          mark_connection_closed(&csp->server_connection);
       }
 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+#ifdef FEATURE_HTTPS_FILTERING
+      if (http->ssl && !use_ssl_tunnel)
+      {
+         int ret;
+         /*
+          * Creating an SSL proxy. If forwarding is disabled, we must send
+          * CSUCCEED mesage to client. Then TLS/SSL connection with client
+          * is created.
+          */
+
+         if (fwd->forward_host == NULL)
+         {
+            /*
+             * We're lying to the client as the connection hasn't actually
+             * been established yet. We don't establish the connection until
+             * we have seen and parsed the encrypted client headers.
+             */
+            if (write_socket_delayed(csp->cfd, CSUCCEED,
+                  strlen(CSUCCEED), get_write_delay(csp)) != 0)
+            {
+               log_error(LOG_LEVEL_ERROR, "Sending SUCCEED to client failed");
+               return;
+            }
+         }
 
+         ret = create_client_ssl_connection(csp);
+         if (ret != 0)
+         {
+            log_error(LOG_LEVEL_ERROR,
+               "Can't open secure connection with client");
+            close_client_ssl_connection(csp); /* XXX: Is this needed? */
+            return;
+         }
+         if (JB_ERR_OK != process_encrypted_request(csp))
+         {
+            log_error(LOG_LEVEL_ERROR, "Failed to parse encrypted request.");
+            close_client_ssl_connection(csp);
+            return;
+         }
+         /*
+          * We have an encrypted request. Check if one of the crunchers now
+          * wants it (for example because the previously invisible path was
+          * required to match).
+          */
+         if (crunch_response_triggered(csp, crunchers_all))
+         {
+            /*
+             * Yes. The client got the crunch response and we're done here.
+             */
+            close_client_ssl_connection(csp);
+            return;
+         }
+      }
+#endif
       /*
        * Connecting to destination server
        */
@@ -3434,6 +3801,7 @@ static void chat(struct client_state *csp)
                log_error(LOG_LEVEL_CONNECT,
                   "Sending request headers to: %s failed", http->hostport);
                mark_server_socket_tainted(csp);
+               close_client_ssl_connection(csp);
                return;
             }
 
@@ -3452,6 +3820,7 @@ static void chat(struct client_state *csp)
                   send_crunch_response(csp, rsp);
                }
                mark_server_socket_tainted(csp);
+               close_client_ssl_connection(csp);
                return;
             }
 
@@ -3467,6 +3836,7 @@ static void chat(struct client_state *csp)
 
                write_socket(csp->cfd, server_response, (size_t)len);
                mark_server_socket_tainted(csp);
+               close_client_ssl_connection(csp);
                return;
             }
 
@@ -3503,6 +3873,7 @@ static void chat(struct client_state *csp)
                log_error(LOG_LEVEL_ERROR,
                   "Sending parent proxy response to client failed");
                mark_server_socket_tainted(csp);
+               close_client_ssl_connection(csp);
                return;
             }
          }/* -END- if (fwd->forward_host != NULL) */
@@ -3588,33 +3959,6 @@ static void chat(struct client_state *csp)
 #ifdef FEATURE_HTTPS_FILTERING
       else
       {
-         int ret;
-         /*
-          * Creating an SSL proxy. If forwarding is disabled, we must send
-          * CSUCCEED mesage to client. Then TLS/SSL connection with client
-          * is created.
-          */
-
-         if (fwd->forward_host == NULL)
-         {
-            if (write_socket_delayed(csp->cfd, CSUCCEED,
-                  strlen(CSUCCEED), get_write_delay(csp)) != 0)
-            {
-               log_error(LOG_LEVEL_ERROR, "Sending SUCCEED to client failed");
-               close_client_and_server_ssl_connections(csp);
-               return;
-            }
-         }
-
-         ret = create_client_ssl_connection(csp);
-         if (ret != 0)
-         {
-            log_error(LOG_LEVEL_ERROR,
-               "Can't open secure connection with client");
-            close_client_and_server_ssl_connections(csp);
-            return;
-         }
-
          /*
           * If server certificate is invalid, we must inform client and then
           * close connection with client.
@@ -3625,6 +3969,16 @@ static void chat(struct client_state *csp)
             close_client_and_server_ssl_connections(csp);
             return;
          }
+         if (send_https_request(csp))
+         {
+            rsp = error_response(csp, "connect-failed");
+            if (rsp)
+            {
+               send_crunch_response(csp, rsp); /* XXX: use ssl*/
+            }
+            close_client_and_server_ssl_connections(csp);
+            return;
+         }
       }
 #endif /* def FEATURE_HTTPS_FILTERING */
       clear_iob(csp->client_iob);
index f56345f..e48ae25 100644 (file)
--- a/parsers.c
+++ b/parsers.c
@@ -1185,6 +1185,46 @@ jb_err sed(struct client_state *csp, int filter_server_headers)
 }
 
 
+#ifdef FEATURE_HTTPS_FILTERING
+/*********************************************************************
+ *
+ * Function    :  sed_https
+ *
+ * Description :  add, delete or modify lines in the HTTPS client
+ *                header streams. Wrapper around sed().
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK in case off success, or
+ *                JB_ERR_MEMORY on some out-of-memory errors, or
+ *                JB_ERR_PARSE in case of fatal parse errors.
+ *
+ *********************************************************************/
+jb_err sed_https(struct client_state *csp)
+{
+   jb_err err;
+   struct list headers;
+
+   /*
+    * Temporarly replace csp->headers with csp->https_headers
+    * to trick sed() into filtering the https headers.
+    */
+   headers.first = csp->headers->first;
+   headers.last  = csp->headers->last;
+   csp->headers->first = csp->https_headers->first;
+   csp->headers->last  = csp->https_headers->last;
+
+   err = sed(csp, FILTER_CLIENT_HEADERS);
+
+   csp->headers->first = headers.first;
+   csp->headers->last  = headers.last;
+
+   return err;
+}
+#endif /* def FEATURE_HTTPS_FILTERING */
+
+
 /*********************************************************************
  *
  * Function    :  update_server_headers
@@ -4492,6 +4532,88 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ
 }
 
 
+#ifdef FEATURE_HTTPS_FILTERING
+/*********************************************************************
+ *
+ * Function    :  get_destination_from_https_headers
+ *
+ * Description :  Parse the previously encrypted "Host:" header to
+ *                get the request's destination.
+ *
+ * Parameters  :
+ *          1  :  headers = List of headers (one of them hopefully being
+ *                the "Host:" header)
+ *          2  :  http = storage for the result (host, port and hostport).
+ *
+ * Returns     :  JB_ERR_MEMORY (or terminates) in case of memory problems,
+ *                JB_ERR_PARSE if the host header couldn't be found,
+ *                JB_ERR_OK otherwise.
+ *
+ *********************************************************************/
+jb_err get_destination_from_https_headers(const struct list *headers, struct http_request *http)
+{
+   char *q;
+   char *p;
+   char *host;
+
+   host = get_header_value(headers, "Host:");
+
+   if (NULL == host)
+   {
+      log_error(LOG_LEVEL_ERROR, "No \"Host:\" header found.");
+      return JB_ERR_PARSE;
+   }
+
+   p = strdup_or_die(host);
+   chomp(p);
+   q = strdup_or_die(p);
+
+   freez(http->hostport);
+   http->hostport = p;
+   freez(http->host);
+   http->host = q;
+   q = strchr(http->host, ':');
+   if (q != NULL)
+   {
+      /* Terminate hostname and evaluate port string */
+      *q++ = '\0';
+      http->port = atoi(q);
+   }
+   else
+   {
+      http->port = 443;
+   }
+
+   /* Rebuild request URL */
+   freez(http->url);
+   http->url = strdup_or_die(http->path);
+
+   log_error(LOG_LEVEL_HEADER,
+      "Destination extracted from \"Host\" header. New request URL: %s",
+      http->url);
+
+   /*
+    * Regenerate request line in "proxy format"
+    * to make rewrites more convenient.
+    */
+   assert(http->cmd != NULL);
+   freez(http->cmd);
+   http->cmd = strdup_or_die(http->gpc);
+   string_append(&http->cmd, " ");
+   string_append(&http->cmd, http->url);
+   string_append(&http->cmd, " ");
+   string_append(&http->cmd, http->ver);
+   if (http->cmd == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+
+}
+#endif /* def FEATURE_HTTPS_FILTERING */
+
+
 /*********************************************************************
  *
  * Function    :  create_forged_referrer
index d4cfc1c..4fa8aae 100644 (file)
--- a/parsers.h
+++ b/parsers.h
@@ -56,9 +56,15 @@ extern jb_err decompress_iob(struct client_state *csp);
 extern char *get_header(struct iob *iob);
 extern char *get_header_value(const struct list *header_list, const char *header_name);
 extern jb_err sed(struct client_state *csp, int filter_server_headers);
+#ifdef FEATURE_HTTPS_FILTERING
+extern jb_err sed_https(struct client_state *csp);
+#endif
 extern jb_err update_server_headers(struct client_state *csp);
 extern void get_http_time(int time_offset, char *buf, size_t buffer_size);
 extern jb_err get_destination_from_headers(const struct list *headers, struct http_request *http);
+#ifdef FEATURE_HTTPS_FILTERING
+extern jb_err get_destination_from_https_headers(const struct list *headers, struct http_request *http);
+#endif
 extern unsigned long long get_expected_content_length(struct list *headers);
 extern jb_err client_transfer_encoding(struct client_state *csp, char **header);
 
index 75aaa65..20f693a 100644 (file)
--- a/project.h
+++ b/project.h
@@ -1060,6 +1060,11 @@ struct client_state
    /** List of all headers for this request */
    struct list headers[1];
 
+#ifdef FEATURE_HTTPS_FILTERING
+   /** List of all encrypted headers for this request */
+   struct list https_headers[1];
+#endif
+
    /** List of all tags that apply to this request */
    struct list tags[1];
 
diff --git a/ssl.c b/ssl.c
index 591e9a5..0a45564 100644 (file)
--- a/ssl.c
+++ b/ssl.c
@@ -578,7 +578,7 @@ exit:
  * Returns     :  N/A
  *
  *********************************************************************/
-static void close_client_ssl_connection(struct client_state *csp)
+extern void close_client_ssl_connection(struct client_state *csp)
 {
    int ret = 0;
 
diff --git a/ssl.h b/ssl.h
index b552bfe..33ab33a 100644 (file)
--- a/ssl.h
+++ b/ssl.h
@@ -62,5 +62,6 @@ extern void ssl_send_certificate_error(struct client_state *csp);
 extern int  create_client_ssl_connection(struct client_state *csp);
 extern int  create_server_ssl_connection(struct client_state *csp);
 extern void close_client_and_server_ssl_connections(struct client_state *csp);
+extern void close_client_ssl_connection(struct client_state *csp);
 
 #endif /* ndef SSL_H_INCLUDED */