From a1649d1c0992d9b1459d2b92cc02f7b723baf5e0 Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Sat, 24 Dec 2016 15:58:49 +0000 Subject: [PATCH] Factor handle_established_connection() out of chat() ... to make fuzzing it more convenient. --- jcc.c | 705 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 367 insertions(+), 338 deletions(-) diff --git a/jcc.c b/jcc.c index 4da35a4d..408c649d 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.446 2016/05/25 10:54:01 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.447 2016/09/27 22:48:28 ler762 Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -1806,21 +1806,10 @@ static jb_err parse_client_request(struct client_state *csp) /********************************************************************* * - * Function : chat - * - * Description : Once a connection from the client has been accepted, - * this function is called (via serve()) to handle the - * main business of the communication. This function - * returns after dealing with a single request. It can - * be called multiple times with the same client socket - * if the client is keeping the connection alive. + * Function : handle_established_connection * - * The decision whether or not a client connection will - * be kept alive is up to the caller which also must - * close the client socket when done. - * - * FIXME: chat is nearly thousand lines long. - * Ridiculous. + * Description : Shuffle data between client and server once the + * connection has been established. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -1828,7 +1817,8 @@ static jb_err parse_client_request(struct client_state *csp) * Returns : Nothing. * *********************************************************************/ -static void chat(struct client_state *csp) +static void handle_established_connection(struct client_state *csp, + const struct forward_spec *fwd) { char buf[BUFFER_SIZE]; char *hdr; @@ -1839,7 +1829,6 @@ static void chat(struct client_state *csp) int server_body; int ms_iis5_hack = 0; unsigned long long byte_count = 0; - const struct forward_spec *fwd; struct http_request *http; long len = 0; /* for buffer sizes (and negative error codes) */ int buffer_and_filter_content = 0; @@ -1855,354 +1844,106 @@ static void chat(struct client_state *csp) http = csp->http; - if (receive_client_request(csp) != JB_ERR_OK) - { - return; - } - if (parse_client_request(csp) != JB_ERR_OK) - { - return; - } - - /* decide how to route the HTTP request */ - fwd = forward_url(csp, http); - if (NULL == fwd) - { - log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!? This can't happen!"); - /* Never get here - LOG_LEVEL_FATAL causes program exit */ - return; - } + maxfd = (csp->cfd > csp->server_connection.sfd) ? + csp->cfd : csp->server_connection.sfd; - /* - * build the http request to send to the server - * we have to do one of the following: - * - * create = use the original HTTP request to create a new - * HTTP request that has either the path component - * without the http://domainspec (w/path) or the - * full orininal URL (w/url) - * Note that the path and/or the HTTP version may - * have been altered by now. - * - * connect = Open a socket to the host:port of the server - * and short-circuit server and client socket. - * - * pass = Pass the request unchanged if forwarding a CONNECT - * request to a parent proxy. Note that we'll be sending - * the CFAIL message ourselves if connecting to the parent - * fails, but we won't send a CSUCCEED message if it works, - * since that would result in a double message (ours and the - * parent's). After sending the request to the parent, we simply - * tunnel. - * - * here's the matrix: - * SSL - * 0 1 - * +--------+--------+ - * | | | - * 0 | create | connect| - * | w/path | | - * Forwarding +--------+--------+ - * | | | - * 1 | create | pass | - * | w/url | | - * +--------+--------+ - * + /* pass data between the client and server + * until one or the other shuts down the connection. */ - if (http->ssl && connect_port_is_forbidden(csp)) - { - const char *acceptable_connect_ports = - csp->action->string[ACTION_STRING_LIMIT_CONNECT]; - assert(NULL != acceptable_connect_ports); - log_error(LOG_LEVEL_INFO, "Request from %s marked for blocking. " - "limit-connect{%s} doesn't allow CONNECT requests to %s", - csp->ip_addr_str, acceptable_connect_ports, csp->http->hostport); - csp->action->flags |= ACTION_BLOCK; - http->ssl = 0; - } + server_body = 0; - if (http->ssl == 0) - { - freez(csp->headers->first->str); - build_request_line(csp, fwd, &csp->headers->first->str); - } +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); +#endif - /* - * We have a request. Check if one of the crunchers wants it. - */ - if (crunch_response_triggered(csp, crunchers_all)) + for (;;) { +#ifdef __OS2__ /* - * Yes. The client got the crunch response and we're done here. + * FD_ZERO here seems to point to an errant macro which crashes. + * So do this by hand for now... */ - return; - } - - log_applied_actions(csp->action); - log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path); - - if (fwd->forward_host) - { - log_error(LOG_LEVEL_CONNECT, "via [%s]:%d to: %s", - fwd->forward_host, fwd->forward_port, http->hostport); - } - else - { - log_error(LOG_LEVEL_CONNECT, "to %s", http->hostport); - } + memset(&rfds,0x00,sizeof(fd_set)); +#else + FD_ZERO(&rfds); +#endif +#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); + } - /* here we connect to the server, gateway, or the forwarder */ + FD_SET(csp->server_connection.sfd, &rfds); #ifdef FEATURE_CONNECTION_KEEP_ALIVE - if ((csp->server_connection.sfd != JB_INVALID_SOCKET) - && socket_is_still_alive(csp->server_connection.sfd) - && connection_destination_matches(&csp->server_connection, http, fwd)) - { - log_error(LOG_LEVEL_CONNECT, - "Reusing server socket %d connected to %s. Total requests: %u.", - csp->server_connection.sfd, csp->server_connection.host, - csp->server_connection.requests_sent_total); - } - else - { - if (csp->server_connection.sfd != JB_INVALID_SOCKET) + 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)) { -#ifdef FEATURE_CONNECTION_SHARING - if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + /* + * 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) { - remember_connection(&csp->server_connection); + log_error(LOG_LEVEL_CONNECT, + "Done reading from server. Content length: %llu as expected. " + "Bytes most recently read: %d.", + byte_count, len); } else -#endif /* def FEATURE_CONNECTION_SHARING */ { log_error(LOG_LEVEL_CONNECT, - "Closing server socket %d connected to %s. Total requests: %u.", - csp->server_connection.sfd, csp->server_connection.host, - csp->server_connection.requests_sent_total); - close_socket(csp->server_connection.sfd); + "Done reading from server. Expected content length: %llu. " + "Actual content length: %llu. Bytes most recently read: %d.", + csp->expected_content_length, byte_count, len); } - mark_connection_closed(&csp->server_connection); + len = 0; + /* + * XXX: should not jump around, + * chat() is complicated enough already. + */ + goto reading_done; } -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ +#endif /* FEATURE_CONNECTION_KEEP_ALIVE */ - csp->server_connection.sfd = forwarded_connect(fwd, http, csp); + timeout.tv_sec = csp->config->socket_timeout; + timeout.tv_usec = 0; + n = select((int)maxfd+1, &rfds, NULL, NULL, &timeout); - if (csp->server_connection.sfd == JB_INVALID_SOCKET) + if (n == 0) { - if ((fwd->type != SOCKS_NONE) && (fwd->type != FORWARD_WEBSERVER)) - { - /* Socks error. */ - rsp = error_response(csp, "forwarding-failed"); - } - else if (errno == EINVAL) - { - rsp = error_response(csp, "no-such-domain"); - } - else - { - rsp = error_response(csp, "connect-failed"); - } - - /* Write the answer to the client */ - if (rsp != NULL) + log_error(LOG_LEVEL_ERROR, + "Didn't receive data in time: %s", http->url); + if ((byte_count == 0) && (http->ssl == 0)) { - send_crunch_response(csp, rsp); + send_crunch_response(csp, error_response(csp, "connection-timeout")); } - - /* - * Temporary workaround to prevent already-read client - * bodies from being parsed as new requests. For now we - * err on the safe side and throw all the following - * requests under the bus, even if no client body has been - * buffered. A compliant client will repeat the dropped - * requests on an untainted connection. - * - * The proper fix is to discard the no longer needed - * client body in the buffer (if there is one) and to - * continue parsing the bytes that follow. - */ - drain_and_close_socket(csp->cfd); - csp->cfd = JB_INVALID_SOCKET; - + mark_server_socket_tainted(csp); return; } -#ifdef FEATURE_CONNECTION_KEEP_ALIVE - save_connection_destination(csp->server_connection.sfd, - http, fwd, &csp->server_connection); - csp->server_connection.keep_alive_timeout = - (unsigned)csp->config->keep_alive_timeout; - } -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - - csp->server_connection.requests_sent_total++; - - if ((fwd->type == SOCKS_5T) && (NULL == csp->headers->first)) - { - /* Client headers have been sent optimistically */ - assert(csp->headers->last == NULL); - } - else if (fwd->forward_host || (http->ssl == 0)) - { - int write_failure; - hdr = list_to_text(csp->headers); - if (hdr == NULL) + else if (n < 0) { - /* FIXME Should handle error properly */ - log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); - } - list_remove_all(csp->headers); - - /* - * Write the client's (modified) header to the server - * (along with anything else that may be in the buffer) - */ - write_failure = 0 != write_socket(csp->server_connection.sfd, hdr, strlen(hdr)); - freez(hdr); - - if (write_failure) - { - log_error(LOG_LEVEL_CONNECT, - "Failed sending request headers to: %s: %E", http->hostport); - } - else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) - && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0)) - { - write_failure = 1; - log_error(LOG_LEVEL_CONNECT, - "Failed sending request body to: %s: %E", http->hostport); - } - - if (write_failure) - { - rsp = error_response(csp, "connect-failed"); - if (rsp) - { - send_crunch_response(csp, rsp); - } - return; - } - } - else - { - /* - * We're running an SSL tunnel and we're not forwarding, - * so just ditch the client headers, send the "connect succeeded" - * message to the client, flush the rest, and get out of the way. - */ - list_remove_all(csp->headers); - if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED))) - { - return; - } - clear_iob(csp->client_iob); - } - - log_error(LOG_LEVEL_CONNECT, "to %s successful", http->hostport); - - /* XXX: should the time start earlier for optimistically sent data? */ - csp->server_connection.request_sent = time(NULL); - - maxfd = (csp->cfd > csp->server_connection.sfd) ? - csp->cfd : csp->server_connection.sfd; - - /* 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 - - for (;;) - { -#ifdef __OS2__ - /* - * FD_ZERO here seems to point to an errant macro which crashes. - * So do this by hand for now... - */ - memset(&rfds,0x00,sizeof(fd_set)); -#else - FD_ZERO(&rfds); -#endif -#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); - -#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: %d.", - 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: %d.", - csp->expected_content_length, byte_count, len); - } - len = 0; - /* - * XXX: should not jump around, - * chat() is complicated enough already. - */ - goto reading_done; - } -#endif /* FEATURE_CONNECTION_KEEP_ALIVE */ - - timeout.tv_sec = csp->config->socket_timeout; - timeout.tv_usec = 0; - n = select((int)maxfd+1, &rfds, NULL, NULL, &timeout); - - if (n == 0) - { - log_error(LOG_LEVEL_ERROR, - "Didn't receive data in time: %s", http->url); - if ((byte_count == 0) && (http->ssl == 0)) - { - send_crunch_response(csp, error_response(csp, "connection-timeout")); - } - mark_server_socket_tainted(csp); - return; - } - else if (n < 0) - { - log_error(LOG_LEVEL_ERROR, "select() failed!: %E"); - mark_server_socket_tainted(csp); - return; + log_error(LOG_LEVEL_ERROR, "select() failed!: %E"); + mark_server_socket_tainted(csp); + return; } /* @@ -2798,6 +2539,294 @@ static void chat(struct client_state *csp) csp->server_connection.timestamp = time(NULL); } +/********************************************************************* + * + * Function : chat + * + * Description : Once a connection from the client has been accepted, + * this function is called (via serve()) to handle the + * main business of the communication. This function + * returns after dealing with a single request. It can + * be called multiple times with the same client socket + * if the client is keeping the connection alive. + * + * The decision whether or not a client connection will + * be kept alive is up to the caller which also must + * close the client socket when done. + * + * FIXME: chat is nearly thousand lines long. + * Ridiculous. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : Nothing. + * + *********************************************************************/ +static void chat(struct client_state *csp) +{ + char buf[BUFFER_SIZE]; + char *hdr; + const struct forward_spec *fwd; + struct http_request *http; + /* Skeleton for HTTP response, if we should intercept the request */ + struct http_response *rsp; + + memset(buf, 0, sizeof(buf)); + + http = csp->http; + + if (receive_client_request(csp) != JB_ERR_OK) + { + return; + } + if (parse_client_request(csp) != JB_ERR_OK) + { + return; + } + + /* decide how to route the HTTP request */ + fwd = forward_url(csp, http); + if (NULL == fwd) + { + log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!? This can't happen!"); + /* Never get here - LOG_LEVEL_FATAL causes program exit */ + return; + } + + /* + * build the http request to send to the server + * we have to do one of the following: + * + * create = use the original HTTP request to create a new + * HTTP request that has either the path component + * without the http://domainspec (w/path) or the + * full orininal URL (w/url) + * Note that the path and/or the HTTP version may + * have been altered by now. + * + * connect = Open a socket to the host:port of the server + * and short-circuit server and client socket. + * + * pass = Pass the request unchanged if forwarding a CONNECT + * request to a parent proxy. Note that we'll be sending + * the CFAIL message ourselves if connecting to the parent + * fails, but we won't send a CSUCCEED message if it works, + * since that would result in a double message (ours and the + * parent's). After sending the request to the parent, we simply + * tunnel. + * + * here's the matrix: + * SSL + * 0 1 + * +--------+--------+ + * | | | + * 0 | create | connect| + * | w/path | | + * Forwarding +--------+--------+ + * | | | + * 1 | create | pass | + * | w/url | | + * +--------+--------+ + * + */ + + if (http->ssl && connect_port_is_forbidden(csp)) + { + const char *acceptable_connect_ports = + csp->action->string[ACTION_STRING_LIMIT_CONNECT]; + assert(NULL != acceptable_connect_ports); + log_error(LOG_LEVEL_INFO, "Request from %s marked for blocking. " + "limit-connect{%s} doesn't allow CONNECT requests to %s", + csp->ip_addr_str, acceptable_connect_ports, csp->http->hostport); + csp->action->flags |= ACTION_BLOCK; + http->ssl = 0; + } + + if (http->ssl == 0) + { + freez(csp->headers->first->str); + build_request_line(csp, fwd, &csp->headers->first->str); + } + + /* + * We have a 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; + } + + log_applied_actions(csp->action); + log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path); + + if (fwd->forward_host) + { + log_error(LOG_LEVEL_CONNECT, "via [%s]:%d to: %s", + fwd->forward_host, fwd->forward_port, http->hostport); + } + else + { + log_error(LOG_LEVEL_CONNECT, "to %s", http->hostport); + } + + /* here we connect to the server, gateway, or the forwarder */ + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + if ((csp->server_connection.sfd != JB_INVALID_SOCKET) + && socket_is_still_alive(csp->server_connection.sfd) + && connection_destination_matches(&csp->server_connection, http, fwd)) + { + log_error(LOG_LEVEL_CONNECT, + "Reusing server socket %d connected to %s. Total requests: %u.", + csp->server_connection.sfd, csp->server_connection.host, + csp->server_connection.requests_sent_total); + } + else + { + if (csp->server_connection.sfd != JB_INVALID_SOCKET) + { +#ifdef FEATURE_CONNECTION_SHARING + if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + { + remember_connection(&csp->server_connection); + } + else +#endif /* def FEATURE_CONNECTION_SHARING */ + { + log_error(LOG_LEVEL_CONNECT, + "Closing server socket %d connected to %s. Total requests: %u.", + csp->server_connection.sfd, csp->server_connection.host, + csp->server_connection.requests_sent_total); + close_socket(csp->server_connection.sfd); + } + mark_connection_closed(&csp->server_connection); + } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + + csp->server_connection.sfd = forwarded_connect(fwd, http, csp); + + if (csp->server_connection.sfd == JB_INVALID_SOCKET) + { + if (fwd->type != SOCKS_NONE) + { + /* Socks error. */ + rsp = error_response(csp, "forwarding-failed"); + } + else if (errno == EINVAL) + { + rsp = error_response(csp, "no-such-domain"); + } + else + { + rsp = error_response(csp, "connect-failed"); + } + + /* Write the answer to the client */ + if (rsp != NULL) + { + send_crunch_response(csp, rsp); + } + + /* + * Temporary workaround to prevent already-read client + * bodies from being parsed as new requests. For now we + * err on the safe side and throw all the following + * requests under the bus, even if no client body has been + * buffered. A compliant client will repeat the dropped + * requests on an untainted connection. + * + * The proper fix is to discard the no longer needed + * client body in the buffer (if there is one) and to + * continue parsing the bytes that follow. + */ + drain_and_close_socket(csp->cfd); + csp->cfd = JB_INVALID_SOCKET; + + return; + } +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + save_connection_destination(csp->server_connection.sfd, + http, fwd, &csp->server_connection); + csp->server_connection.keep_alive_timeout = + (unsigned)csp->config->keep_alive_timeout; + } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + + csp->server_connection.requests_sent_total++; + + if ((fwd->type == SOCKS_5T) && (NULL == csp->headers->first)) + { + /* Client headers have been sent optimistically */ + assert(csp->headers->last == NULL); + } + else if (fwd->forward_host || (http->ssl == 0)) + { + int write_failure; + hdr = list_to_text(csp->headers); + if (hdr == NULL) + { + /* FIXME Should handle error properly */ + log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); + } + list_remove_all(csp->headers); + + /* + * Write the client's (modified) header to the server + * (along with anything else that may be in the buffer) + */ + write_failure = 0 != write_socket(csp->server_connection.sfd, hdr, strlen(hdr)); + freez(hdr); + + if (write_failure) + { + log_error(LOG_LEVEL_CONNECT, + "Failed sending request headers to: %s: %E", http->hostport); + } + else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) + && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0)) + { + write_failure = 1; + log_error(LOG_LEVEL_CONNECT, + "Failed sending request body to: %s: %E", http->hostport); + } + + if (write_failure) + { + rsp = error_response(csp, "connect-failed"); + if (rsp) + { + send_crunch_response(csp, rsp); + } + return; + } + } + else + { + /* + * We're running an SSL tunnel and we're not forwarding, + * so just ditch the client headers, send the "connect succeeded" + * message to the client, flush the rest, and get out of the way. + */ + list_remove_all(csp->headers); + if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED))) + { + return; + } + clear_iob(csp->client_iob); + } + + log_error(LOG_LEVEL_CONNECT, "to %s successful", http->hostport); + + /* XXX: should the time start earlier for optimistically sent data? */ + csp->server_connection.request_sent = time(NULL); + + handle_established_connection(csp, fwd); +} + #ifdef FEATURE_CONNECTION_KEEP_ALIVE /********************************************************************* -- 2.39.2