+ /* 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_delayed(&(csp->ssl_client_attr),
+ (const unsigned char *)CHEADER, strlen(CHEADER), get_write_delay(csp));
+ 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");
+ log_applied_actions(csp->action);
+ log_error(LOG_LEVEL_REQUEST, "https://%s%s", csp->http->hostport,
+ csp->http->path);
+
+ return err;
+
+}
+
+/*********************************************************************
+ *
+ * Function : cgi_page_requested
+ *
+ * Description : Checks if a request is for an internal CGI page.
+ *
+ * Parameters :
+ * 1 : host = The host requested by the client.
+ *
+ * Returns : 1 if a CGI page has been requested, 0 otherwise
+ *
+ *********************************************************************/
+static int cgi_page_requested(const char *host)
+{
+ if ((0 == strcmpic(host, CGI_SITE_1_HOST))
+ || (0 == strcmpic(host, CGI_SITE_1_HOST "."))
+ || (0 == strcmpic(host, CGI_SITE_2_HOST))
+ || (0 == strcmpic(host, CGI_SITE_2_HOST ".")))
+ {
+ return 1;
+ }
+
+ return 0;
+
+}
+
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+/*********************************************************************
+ *
+ * Function : continue_https_chat
+ *
+ * Description : Behaves similar to chat() but only deals with
+ * https-inspected requests that arrive on an already
+ * established connection. The first request is always
+ * served by chat() which is a lot more complex as it
+ * has to deal with forwarding settings and connection
+ * failures etc.
+ *
+ * If a connection to the server has already been
+ * opened it is reused unless the request is blocked
+ * or the forwarder changed.
+ *
+ * If a connection to the server has not yet been
+ * opened (because the previous request was crunched),
+ * or the forwarder changed, the connection is dropped
+ * so that the client retries on a fresh one.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : Nothing.
+ *
+ *********************************************************************/
+static void continue_https_chat(struct client_state *csp)
+{
+ const struct forward_spec *fwd;
+
+ if (JB_ERR_OK != process_encrypted_request(csp))
+ {
+ return;
+ }
+
+ csp->requests_received_total++;
+
+ /*
+ * We have an encrypted request. Check if one of the crunchers wants it.
+ */
+ if (crunch_response_triggered(csp, crunchers_all))
+ {
+ /*
+ * Yes. The client got the crunch response and we're done here.
+ */
+ return;
+ }
+ if (csp->ssl_with_server_is_opened == 0)
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Dropping the client connection on socket %d. "
+ "The server connection has not been established yet.",
+ csp->cfd);
+ csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
+ return;
+ }
+ assert(csp->server_connection.sfd != JB_INVALID_SOCKET);
+
+ fwd = forward_url(csp, csp->http);
+ if (!connection_destination_matches(&csp->server_connection, csp->http, fwd))
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Dropping the client connection on socket %d with "
+ "server socket %d connected to %s. The forwarder has changed.",
+ csp->cfd, csp->server_connection.sfd, csp->server_connection.host);
+ csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
+ return;
+ }
+
+ log_error(LOG_LEVEL_CONNECT,
+ "Reusing server socket %d connected to %s. Requests already sent: %u.",
+ csp->server_connection.sfd, csp->server_connection.host,
+ csp->server_connection.requests_sent_total);
+
+ if (send_https_request(csp))
+ {
+ /*
+ * Most likely the server connection timed out. We can't easily
+ * create a new one so simply drop the client connection without a
+ * error response to let the client retry.
+ */
+ log_error(LOG_LEVEL_CONNECT,
+ "Dropping client connection on socket %d. "
+ "Forwarding the encrypted client request failed.",
+ csp->cfd);
+ return;
+ }
+ csp->server_connection.requests_sent_total++;
+ handle_established_connection(csp);
+ freez(csp->receive_buffer);
+}
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+#endif
+
+
+/*********************************************************************
+ *
+ * Function : handle_established_connection
+ *
+ * Description : Shuffle data between client and server once the
+ * connection has been established.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : Nothing.
+ *
+ *********************************************************************/
+static void handle_established_connection(struct client_state *csp)
+{
+ char *hdr;
+ char *p;
+ int n;
+#ifdef HAVE_POLL
+ struct pollfd poll_fds[2];
+#else
+ fd_set rfds;
+ jb_socket maxfd;
+ struct timeval timeout;
+#endif
+ int server_body;
+ int ms_iis5_hack = 0;
+ unsigned long long byte_count = 0;
+ struct http_request *http;
+ long len = 0; /* for buffer sizes (and negative error codes) */
+ int buffer_and_filter_content = 0;
+ unsigned int write_delay;
+#ifdef FEATURE_HTTPS_INSPECTION
+ int ret = 0;
+ int use_ssl_tunnel = 0;
+ csp->dont_verify_certificate = 0;
+
+ if (csp->http->ssl && !(csp->action->flags & ACTION_HTTPS_INSPECTION))
+ {
+ /* Pass encrypted content without filtering. */
+ use_ssl_tunnel = 1;
+ }
+#endif
+
+ /* Skeleton for HTTP response, if we should intercept the request */
+ struct http_response *rsp;
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ int watch_client_socket;
+#endif
+
+ csp->receive_buffer_size = csp->config->receive_buffer_size;
+ csp->receive_buffer = zalloc(csp->receive_buffer_size + 1);
+ if (csp->receive_buffer == NULL)
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Out of memory. Failed to allocate the receive buffer.");
+ rsp = cgi_error_memory();
+ send_crunch_response(csp, rsp);
+ return;
+ }
+
+ http = csp->http;
+
+#ifndef HAVE_POLL
+ maxfd = (csp->cfd > csp->server_connection.sfd) ?
+ csp->cfd : csp->server_connection.sfd;
+#endif
+
+ /* pass data between the client and server
+ * until one or the other shuts down the connection.
+ */
+
+ server_body = 0;
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING);
+#endif
+ write_delay = get_write_delay(csp);
+
+ for (;;)
+ {
+#ifndef HAVE_POLL
+ FD_ZERO(&rfds);
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if (!watch_client_socket)
+ {
+ maxfd = csp->server_connection.sfd;
+ }
+ else
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+ {
+ FD_SET(csp->cfd, &rfds);
+ }
+
+ FD_SET(csp->server_connection.sfd, &rfds);
+#endif /* ndef HAVE_POLL */
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if ((csp->flags & CSP_FLAG_CHUNKED)
+ && !(csp->flags & CSP_FLAG_CONTENT_LENGTH_SET)
+ && ((csp->iob->eod - csp->iob->cur) >= 5)
+ && !memcmp(csp->iob->eod-5, "0\r\n\r\n", 5))
+ {
+ /*
+ * XXX: This check should be obsolete now,
+ * but let's wait a while to be sure.
+ */
+ log_error(LOG_LEVEL_CONNECT,
+ "Looks like we got the last chunk together with "
+ "the server headers but didn't detect it earlier. "
+ "We better stop reading.");
+ byte_count = (unsigned long long)(csp->iob->eod - csp->iob->cur);
+ csp->expected_content_length = byte_count;
+ csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET;
+ }
+ if (server_body && server_response_is_complete(csp, byte_count))
+ {
+ if (csp->expected_content_length == byte_count)
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Done reading from server. Content length: %llu as expected. "
+ "Bytes most recently read: %ld.",
+ byte_count, len);
+ }
+ else
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Done reading from server. Expected content length: %llu. "
+ "Actual content length: %llu. Bytes most recently read: %ld.",
+ csp->expected_content_length, byte_count, len);
+ }
+ len = 0;
+ /*
+ * XXX: Should not jump around, handle_established_connection()
+ * is complicated enough already.
+ */
+ goto reading_done;
+ }
+#endif /* FEATURE_CONNECTION_KEEP_ALIVE */
+
+#ifdef HAVE_POLL
+ poll_fds[0].fd = csp->cfd;
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if (!watch_client_socket)
+ {
+ /*
+ * Ignore incoming data, but still watch out
+ * for disconnects etc. These flags are always
+ * implied anyway but explicitly setting them
+ * doesn't hurt.
+ */
+ poll_fds[0].events = POLLERR|POLLHUP;
+ }
+ else
+#endif
+ {
+ poll_fds[0].events = POLLIN;
+ }
+ poll_fds[1].fd = csp->server_connection.sfd;
+ poll_fds[1].events = POLLIN;
+ n = poll(poll_fds, 2, csp->config->socket_timeout * 1000);
+#else
+ timeout.tv_sec = csp->config->socket_timeout;
+ timeout.tv_usec = 0;
+ n = select((int)maxfd + 1, &rfds, NULL, NULL, &timeout);
+#endif /* def HAVE_POLL */
+
+ /*server or client not responding in timeout */
+ if (n == 0)
+ {
+ log_error(LOG_LEVEL_CONNECT, "Socket timeout %d reached: %s",
+ csp->config->socket_timeout, http->url);
+ if ((byte_count == 0) && (http->ssl == 0))
+ {
+ send_crunch_response(csp, error_response(csp, "connection-timeout"));
+ }
+ mark_server_socket_tainted(csp);
+#ifdef FEATURE_HTTPS_INSPECTION
+ close_client_and_server_ssl_connections(csp);
+#endif
+ return;
+ }
+ else if (n < 0)
+ {
+#ifdef HAVE_POLL
+ log_error(LOG_LEVEL_ERROR, "poll() failed!: %E");
+#else
+ log_error(LOG_LEVEL_ERROR, "select() failed!: %E");
+#endif
+ mark_server_socket_tainted(csp);
+#ifdef FEATURE_HTTPS_INSPECTION
+ close_client_and_server_ssl_connections(csp);
+#endif
+ return;
+ }
+
+ /*
+ * This is the body of the browser's request,
+ * just read and write it.
+ *
+ * Receives data from browser and sends it to server
+ *
+ * XXX: Make sure the client doesn't use pipelining
+ * behind Privoxy's back.
+ */
+#ifdef HAVE_POLL
+ if ((poll_fds[0].revents & (POLLERR|POLLHUP|POLLNVAL)) != 0)
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "The client socket %d has become unusable while "
+ "the server socket %d is still open.",
+ csp->cfd, csp->server_connection.sfd);
+ mark_server_socket_tainted(csp);
+ break;
+ }
+
+ if (poll_fds[0].revents != 0)
+#else
+ if (FD_ISSET(csp->cfd, &rfds))
+#endif /* def HAVE_POLL*/
+ {
+ int max_bytes_to_read = (int)csp->receive_buffer_size;
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if ((csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ))
+ {
+ if (data_is_available(csp->cfd, 0))
+ {
+ /*
+ * If the next request is already waiting, we have
+ * to stop select()ing the client socket. Otherwise
+ * we would always return right away and get nothing
+ * else done.
+ */
+ watch_client_socket = 0;
+ log_error(LOG_LEVEL_CONNECT,
+ "Stop watching client socket %d. "
+ "There's already another request waiting.",
+ csp->cfd);
+ continue;
+ }
+ /*
+ * If the client socket is set, but there's no data
+ * available on the socket, the client went fishing
+ * and continuing talking to the server makes no sense.
+ */
+ log_error(LOG_LEVEL_CONNECT,
+ "The client closed socket %d while "
+ "the server socket %d is still open.",
+ csp->cfd, csp->server_connection.sfd);
+ mark_server_socket_tainted(csp);
+ break;
+ }
+ if (csp->expected_client_content_length != 0)
+ {
+ if (csp->expected_client_content_length < csp->receive_buffer_size)
+ {
+ max_bytes_to_read = (int)csp->expected_client_content_length;
+ }
+ log_error(LOG_LEVEL_CONNECT,
+ "Waiting for up to %d bytes from the client.",
+ max_bytes_to_read);
+ }
+ assert(max_bytes_to_read <= csp->receive_buffer_size);
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+#ifdef FEATURE_HTTPS_INSPECTION
+ if (client_use_ssl(csp))
+ {
+ if (csp->http->status == 101)
+ {
+ len = ssl_recv_data(&(csp->ssl_client_attr),
+ (unsigned char *)csp->receive_buffer,
+ (size_t)max_bytes_to_read);
+ if (len == -1)
+ {
+ log_error(LOG_LEVEL_ERROR, "Failed to receive data "
+ "on client socket %d for an upgraded connection",
+ csp->cfd);
+ break;
+ }
+ if (len == 0)
+ {
+ log_error(LOG_LEVEL_CONNECT, "Done receiving data "
+ "on client socket %d for an upgraded connection",
+ csp->cfd);
+ break;
+ }
+ byte_count += (unsigned long long)len;
+ len = ssl_send_data(&(csp->ssl_server_attr),
+ (unsigned char *)csp->receive_buffer, (size_t)len);
+ if (len == -1)
+ {
+ log_error(LOG_LEVEL_ERROR, "Failed to send data "
+ "on server socket %d for an upgraded connection",
+ csp->server_connection.sfd);
+ break;
+ }
+ continue;
+ }
+ log_error(LOG_LEVEL_CONNECT, "Breaking with TLS/SSL.");
+ break;
+ }
+ else
+#endif /* def FEATURE_HTTPS_INSPECTION */
+ {
+ len = read_socket(csp->cfd, csp->receive_buffer, max_bytes_to_read);
+
+ if (len <= 0)
+ {
+ /* XXX: not sure if this is necessary. */
+ mark_server_socket_tainted(csp);
+ break; /* "game over, man" */
+ }
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if (csp->expected_client_content_length != 0)
+ {
+ assert(len <= max_bytes_to_read);
+ csp->expected_client_content_length -= (unsigned)len;
+ log_error(LOG_LEVEL_CONNECT,
+ "Expected client content length set to %llu "
+ "after reading %ld bytes.",
+ csp->expected_client_content_length, len);
+ if (csp->expected_client_content_length == 0)
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Done reading from the client.");
+ csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
+ }
+ }
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+ if (write_socket(csp->server_connection.sfd, csp->receive_buffer, (size_t)len))
+ {
+ log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+ mark_server_socket_tainted(csp);
+ return;
+ }
+ }
+ continue;
+ }
+
+ /*
+ * The server wants to talk. It could be the header or the body.
+ * If `hdr' is null, then it's the header otherwise it's the body.
+ * FIXME: Does `hdr' really mean `host'? No.
+ */
+#ifdef HAVE_POLL
+ if (poll_fds[1].revents != 0)
+#else
+ if (FD_ISSET(csp->server_connection.sfd, &rfds))
+#endif /* HAVE_POLL */
+ {
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ /*
+ * If we are buffering content, we don't want to eat up to
+ * buffer-limit bytes if the client no longer cares about them.
+ * If we aren't buffering, however, a dead client socket will be
+ * noticed pretty much right away anyway, so we can reduce the
+ * overhead by skipping the check.
+ */
+ if (buffer_and_filter_content && !socket_is_still_alive(csp->cfd))
+ {
+#ifdef _WIN32
+ log_error(LOG_LEVEL_CONNECT,
+ "The server still wants to talk, but the client may already have hung up on us.");
+#else
+ log_error(LOG_LEVEL_CONNECT,
+ "The server still wants to talk, but the client hung up on us.");
+ mark_server_socket_tainted(csp);
+#ifdef FEATURE_HTTPS_INSPECTION
+ close_client_and_server_ssl_connections(csp);
+#endif
+ return;
+#endif /* def _WIN32 */
+ }
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+#ifdef FEATURE_HTTPS_INSPECTION
+ /*
+ * Reading data from standard or secured connection (HTTP/HTTPS)
+ */
+ if (server_use_ssl(csp))
+ {
+ len = ssl_recv_data(&(csp->ssl_server_attr),
+ (unsigned char *)csp->receive_buffer, csp->receive_buffer_size);
+ }
+ else
+#endif
+ {
+ len = read_socket(csp->server_connection.sfd, csp->receive_buffer,
+ (int)csp->receive_buffer_size);
+ }
+
+ if (len < 0)
+ {
+ log_error(LOG_LEVEL_ERROR, "read from: %s failed: %E", http->host);
+
+ if ((http->ssl && (csp->fwd == NULL))
+#ifdef FEATURE_HTTPS_INSPECTION
+ && use_ssl_tunnel
+#endif
+ )
+ {
+ /*
+ * Just hang up. We already confirmed the client's CONNECT
+ * request with status code 200 and unencrypted content is
+ * no longer welcome.
+ */
+ log_error(LOG_LEVEL_ERROR,
+ "CONNECT already confirmed. Unable to tell the client about the problem.");
+ return;
+ }
+ else if (byte_count)
+ {
+ /*
+ * Just hang up. We already transmitted the original headers
+ * and parts of the original content and therefore missed the
+ * chance to send an error message (without risking data corruption).
+ *
+ * XXX: we could retry with a fancy range request here.
+ */
+ log_error(LOG_LEVEL_ERROR, "Already forwarded the original headers. "
+ "Unable to tell the client about the problem.");
+ mark_server_socket_tainted(csp);
+#ifdef FEATURE_HTTPS_INSPECTION
+ close_client_and_server_ssl_connections(csp);
+#endif
+ return;
+ }
+ /*
+ * XXX: Consider handling the cases above the same.
+ */
+ mark_server_socket_tainted(csp);
+ len = 0;
+ }
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if (csp->flags & CSP_FLAG_CHUNKED)
+ {
+ if ((len >= 5) && !memcmp(csp->receive_buffer+len-5, "0\r\n\r\n", 5))
+ {
+ /* XXX: this is a temporary hack */
+ log_error(LOG_LEVEL_CONNECT,
+ "Looks like we reached the end of the last chunk. "
+ "We better stop reading.");
+ csp->expected_content_length = byte_count + (unsigned long long)len;
+ csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET;
+ }
+ }
+ reading_done:
+#endif /* FEATURE_CONNECTION_KEEP_ALIVE */
+
+ /*
+ * This is guaranteed by allocating with zalloc_or_die()
+ * and never (intentionally) writing to the last byte.
+ *
+ * csp->receive_buffer_size is the size of the part of the
+ * buffer we intentionally write to, but we actually
+ * allocated csp->receive_buffer_size+1 bytes so the assertion
+ * stays within the allocated range.
+ */
+ assert(csp->receive_buffer[csp->receive_buffer_size] == '\0');
+
+ /*
+ * Add a trailing zero to let be able to use string operations.
+ * XXX: do we still need this with filter_popups gone?
+ */
+ assert(len <= csp->receive_buffer_size);
+ csp->receive_buffer[len] = '\0';
+
+ /*
+ * Normally, this would indicate that we've read
+ * as much as the server has sent us and we can
+ * close the client connection. However, Microsoft
+ * in its wisdom has released IIS/5 with a bug that
+ * prevents it from sending the trailing \r\n in
+ * a 302 redirect header (and possibly other headers).
+ * To work around this if we've haven't parsed
+ * a full header we'll append a trailing \r\n
+ * and see if this now generates a valid one.
+ *