From 392c97e3d1341147d71d597aefd175e34e1aee6a Mon Sep 17 00:00:00 2001 From: Robert James Kaes Date: Tue, 12 Sep 2000 00:04:42 +0000 Subject: MAJOR RE-WRITE! Read the ChangeLog and look at the source. It's shorter than redocumenting the changes here. :) --- src/reqs.c | 1071 +++++++++++++++++++++++++----------------------------------- src/reqs.h | 8 +- 2 files changed, 449 insertions(+), 630 deletions(-) diff --git a/src/reqs.c b/src/reqs.c index 0c33e19..4ee6038 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -1,16 +1,16 @@ -/* $Id: reqs.c,v 1.7 2000-03-31 22:55:22 rjkaes Exp $ +/* $Id: reqs.c,v 1.8 2000-09-12 00:04:42 rjkaes Exp $ * * This is where all the work in tinyproxy is actually done. Incoming - * connections are added to the active list of connections and then the header - * is processed to determine what is being requested. tinyproxy then connects - * to the remote server and begins to relay the bytes back and forth between - * the client and the remote server. Basically, we sit there and sling bytes - * back and forth. Optionally, we can weed out headers we do not want to send, - * and also add a header of our own. + * connections have a new thread created for them. The thread then + * processes the headers from the client, the response from the server, + * and then relays the bytes between the two. + * If the UPSTEAM_PROXY is enabled, then tinyproxy will actually work + * as a simple buffering TCP tunnel. Very cool! (Robert actually uses + * this feature for a buffering NNTP tunnel). * - * Copyright (C) 1998 Steven Young - * Copyright (C) 1999 Robert James Kaes (rjkaes@flarenet.com) - * Copyright (C) 2000 Chris Lightfoot (chris@ex-parrot.com) + * Copyright (C) 1998 Steven Young + * Copyright (C) 1999,2000 Robert James Kaes (rjkaes@flarenet.com) + * Copyright (C) 2000 Chris Lightfoot (chris@ex-parrot.com) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -23,64 +23,19 @@ * General Public License for more details. */ -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" #include "tinyproxy.h" -#include "sock.h" -#include "utils.h" -#include "conns.h" -#include "log.h" -#include "reqs.h" + +#include "acl.h" +#include "anonymous.h" #include "buffer.h" #include "filter.h" -#include "uri.h" +#include "log.h" #include "regexp.h" -#include "anonymous.h" - -/* chris - for asynchronous DNS */ -#include "dnscache.h" -#include -extern adns_state adns; - -#ifdef XTINYPROXY - -static void Add_XTinyproxy_Header(struct conn_s *connptr) -{ - char *header_line; - char ipaddr[PEER_IP_LENGTH]; - int length; - - assert(connptr); - - if (!(header_line = xmalloc(sizeof(char) * 32))) - return; - - length = sprintf(header_line, "X-Tinyproxy: %s\r\n", - getpeer_ip(connptr->client_fd, ipaddr)); - - unshift_buffer(connptr->cbuffer, header_line, length); -} - -#endif +#include "reqs.h" +#include "sock.h" +#include "stats.h" +#include "uri.h" +#include "utils.h" #define HTTPPATTERN "^([a-z]+)[ \t]+([^ \t]+)([ \t]+(HTTP/[0-9]+\\.[0-9]+))?" #define NMATCH 4 @@ -93,77 +48,121 @@ static void Add_XTinyproxy_Header(struct conn_s *connptr) #define HTTP500ERROR "Unable to connect to remote server." #define HTTP503ERROR "Internal server error." +#define LINE_LENGTH (MAXBUFFSIZE / 3) +#define HTTP_PORT 80 + +/* + * Write the buffer to the socket. If an EINTR occurs, pick up and try + * again. + */ +static ssize_t safe_write(int fd, void *buffer, size_t count) +{ + ssize_t len; + + do { + len = write(fd, buffer, count); + } while (len < 0 && errno == EINTR); + + return len; +} + +/* + * Matched pair for safe_write(). If an EINTR occurs, pick up and try + * again. + */ +static ssize_t safe_read(int fd, void *buffer, size_t count) +{ + ssize_t len; + + do { + len = read(fd, buffer, count); + } while (len < 0 && errno == EINTR); + + return len; +} + /* * Parse a client HTTP request and then establish connection. */ -static int clientreq(struct conn_s *connptr) +static int process_method(struct conn_s *connptr) { URI *uri = NULL; - char *inbuf, *buffer, *request, *port; - char *inbuf_ptr; + char inbuf[LINE_LENGTH]; + char *buffer = NULL, *request = NULL, *port = NULL; + char *inbuf_ptr = NULL; regex_t preg; regmatch_t pmatch[NMATCH]; + size_t request_len; long len; - int fd, port_no; + int fd, port_no = HTTP_PORT; char peer_ipaddr[PEER_IP_LENGTH]; - assert(connptr); - getpeer_ip(connptr->client_fd, peer_ipaddr); - /* chris - getpeer_string could block, so for the moment take it out */ - if ((len - = readline(connptr->client_fd, connptr->cbuffer, &inbuf)) <= 0) { - return len; + if (readline(connptr->client_fd, inbuf, LINE_LENGTH) <= 0) { + log(LOG_ERR, "client closed before read"); + update_stats(STAT_BADCONN); + return -2; } + len = strlen(inbuf); inbuf_ptr = inbuf + len - 1; while (*inbuf_ptr == '\r' || *inbuf_ptr == '\n') *inbuf_ptr-- = '\0'; - /* Log the incoming connection */ - if (!config.restricted) { - log("Connect: %s", peer_ipaddr); - log("Request: %s", inbuf); - } + log(LOG_INFO, "Request: %s", inbuf); if (regcomp(&preg, HTTPPATTERN, REG_EXTENDED | REG_ICASE) != 0) { - log("ERROR clientreq: regcomp"); - return 0; + log(LOG_ERR, "clientreq: regcomp"); + httperr(connptr, 503, HTTP503ERROR); + update_stats(STAT_BADCONN); + goto COMMON_EXIT; } if (regexec(&preg, inbuf, NMATCH, pmatch, 0) != 0) { - log("ERROR clientreq: regexec"); + log(LOG_ERR, "clientreq: regexec"); regfree(&preg); - return -1; + httperr(connptr, 503, HTTP503ERROR); + update_stats(STAT_BADCONN); + goto COMMON_EXIT; } regfree(&preg); - if (pmatch[VERSION_MARK].rm_so == -1) + /* + * Test for a simple request, or a request from version 0.9 + * - rjkaes + */ + if (pmatch[VERSION_MARK].rm_so == -1 + || !strncasecmp("http/0.9", inbuf + pmatch[VERSION_IND].rm_so, 8)) connptr->simple_req = TRUE; + if (pmatch[METHOD_IND].rm_so == -1 || pmatch[URI_IND].rm_so == -1) { - log("ERROR clientreq: Incomplete line from %s (%s)", + log(LOG_ERR, "clientreq: Incomplete line from %s (%s)", peer_ipaddr, inbuf); httperr(connptr, 400, HTTP400ERROR); + update_stats(STAT_BADCONN); goto COMMON_EXIT; } len = pmatch[URI_IND].rm_eo - pmatch[URI_IND].rm_so; - if (!(buffer = xmalloc(len + 1))) { - log("ERROR clientreq: Cannot allocate buffer for request from %s", + if (!(buffer = malloc(len + 1))) { + log(LOG_ERR, + "clientreq: Cannot allocate buffer for request from %s", peer_ipaddr); httperr(connptr, 503, HTTP503ERROR); + update_stats(STAT_BADCONN); goto COMMON_EXIT; } memcpy(buffer, inbuf + pmatch[URI_IND].rm_so, len); buffer[len] = '\0'; if (!(uri = explode_uri(buffer))) { safefree(buffer); - log("ERROR clientreq: Problem with explode_uri"); + log(LOG_ERR, "clientreq: Problem with explode_uri"); httperr(connptr, 503, HTTP503ERROR); + update_stats(STAT_BADCONN); goto COMMON_EXIT; } safefree(buffer); @@ -171,21 +170,32 @@ static int clientreq(struct conn_s *connptr) if (!uri->scheme || strcasecmp(uri->scheme, "http") != 0) { char *error_string; if (uri->scheme) { - error_string = xmalloc(strlen(uri->scheme) + 64); + error_string = malloc(strlen(uri->scheme) + 64); + if (!error_string) { + log(LOG_CRIT, "Out of Memory!"); + return -1; + } sprintf(error_string, "Invalid scheme (%s). Only HTTP is allowed.", uri->scheme); } else { - error_string = strdup("Invalid scheme (NULL). Only HTTP is allowed."); + error_string = + strdup("Invalid scheme (NULL). Only HTTP is allowed."); + if (!error_string) { + log(LOG_CRIT, "Out of Memory!"); + return -1; + } } httperr(connptr, 400, error_string); safefree(error_string); + update_stats(STAT_BADCONN); goto COMMON_EXIT; } if (!uri->authority) { httperr(connptr, 400, "Invalid authority."); + update_stats(STAT_BADCONN); goto COMMON_EXIT; } @@ -195,694 +205,503 @@ static int clientreq(struct conn_s *connptr) goto COMMON_EXIT; } - port_no = 80; if ((port = strchr(uri->authority, ':'))) { *port++ = '\0'; if (strlen(port) > 0) port_no = atoi(port); } - /* chris - so this can be passed on to clientreq_dnscomplete. */ - connptr->port_no = port_no; - #ifdef FILTER_ENABLE /* Filter domains out */ if (config.filter) { if (filter_host(uri->authority)) { - log("ERROR clientreq: Filtered connection (%s)", + log(LOG_ERR, "clientreq: Filtered connection (%s)", peer_ipaddr); httperr(connptr, 404, "Unable to connect to filtered host."); + update_stats(STAT_DENIED); goto COMMON_EXIT; } } -#endif /* FILTER_ENABLE */ +#endif /* FILTER_ENABLE */ /* Build a new request from the first line of the header */ - if (!(request = xmalloc(strlen(inbuf) + 1))) { - log("ERROR clientreq: cannot allocate buffer for request from %s", + request_len = strlen(inbuf) + 1; + if (!(request = malloc(request_len))) { + log(LOG_ERR, + "clientreq: cannot allocate buffer for request from %s", peer_ipaddr); httperr(connptr, 503, HTTP503ERROR); + update_stats(STAT_BADCONN); goto COMMON_EXIT; } - /* We need to set the version number WE support */ memcpy(request, inbuf, pmatch[METHOD_IND].rm_eo); request[pmatch[METHOD_IND].rm_eo] = '\0'; - strcat(request, " "); + strlcat(request, " ", request_len); if (strlen(uri->path) > 0) { - strcat(request, uri->path); + strlcat(request, uri->path, request_len); if (uri->query) { - strcat(request, "?"); - strcat(request, uri->query); + strlcat(request, "?", request_len); + strlcat(request, uri->query, request_len); } } else { - strcat(request, "/"); - } - strcat(request, " HTTP/1.0\r\n"); - - /* chris - If domain is in dotted-quad format or is already in the - * DNS cache, then go straight to WAITCONN. - */ - if (inet_aton(uri->authority, NULL) - || lookup(NULL, uri->authority)) { - if ((fd = opensock(uri->authority, port_no)) < 0) { - safefree(request); - httperr(connptr, 500, - "Unable to connect to host (cannot create sock)"); - stats.num_badcons++; - goto COMMON_EXIT; - } - - connptr->server_fd = fd; - connptr->type = WAITCONN; - } - /* Otherwise submit a DNS request and hope for the best. */ - else { - if (adns_submit(adns, uri->authority, adns_r_a, adns_qf_quoteok_cname | adns_qf_cname_loose, connptr, &(connptr->adns_qu))) { - safefree(request); - httperr(connptr, 500, "Resolver error connecting to host"); - stats.num_badcons++; - goto COMMON_EXIT; - } else { -#ifdef __DEBUG__ - log("DNS request submitted"); -#endif - /* copy domain for later caching */ - connptr->domain = strdup(uri->authority); - connptr->type = DNS_WAITCONN; - } - } - -#ifdef XTINYPROXY - /* Add a X-Tinyproxy header which contains our IP address */ - if (config.my_domain - && xstrstr(uri->authority, config.my_domain, - strlen(uri->authority), FALSE)) { - Add_XTinyproxy_Header(connptr); + strlcat(request, "/", request_len); } -#endif - - /* Add the rewritten request to the buffer */ - unshift_buffer(connptr->cbuffer, request, strlen(request)); - - COMMON_EXIT: - safefree(inbuf); - free_uri(uri); - return 0; -} - -/* chris - added this to move a connection from the DNS_WAITCONN state - * to the WAITCONN state by connecting it to the server, once the name - * has been resolved. - */ -static int clientreq_dnscomplete(struct conn_s *connptr, struct in_addr *inaddr) { - int fd; - - fd = opensock_inaddr(inaddr, connptr->port_no); + strlcat(request, " HTTP/1.0\r\n", request_len); + fd = opensock(uri->authority, port_no); if (fd < 0) { -#ifdef __DEBUG__ - log("Failed to open connection to server"); -#endif - httperr(connptr, 500, - "Unable to connect to host (cannot create sock)"); - stats.num_badcons++; - - return 0; - } else { -#ifdef __DEBUG__ - log("Connected to server"); -#endif - connptr->server_fd = fd; - connptr->type = WAITCONN; + httperr(connptr, 500, HTTP500ERROR); + update_stats(STAT_DENIED); + goto COMMON_EXIT; } - return 0; -} - -/* - * Finish the client request - */ -static int clientreq_finish(struct conn_s *connptr) -{ - int sockopt, len = sizeof(sockopt); - - assert(connptr); + connptr->server_fd = fd; - if (getsockopt - (connptr->server_fd, SOL_SOCKET, SO_ERROR, &sockopt, &len) < 0) { - log("ERROR clientreq_finish: getsockopt error (%s)", - strerror(errno)); - return -1; - } + if (safe_write(connptr->server_fd, request, strlen(request)) < 0) + goto COMMON_EXIT; + + /* + * Send the Host: header + */ + if (safe_write(connptr->server_fd, "Host: ", 6) < 0) + goto COMMON_EXIT; + if (safe_write(connptr->server_fd, uri->authority, strlen(uri->authority)) < 0) + goto COMMON_EXIT; + if (safe_write(connptr->server_fd, "\r\n", 2) < 0) + goto COMMON_EXIT; - if (sockopt != 0) { - if (sockopt == EINPROGRESS) - return 0; - else if (sockopt == ETIMEDOUT) { - httperr(connptr, 408, "Connect Timed Out"); - return 0; - } else { - log("ERROR clientreq_finish: could not create connection (%s)", - strerror(sockopt)); - return -1; - } - } + /* + * Send the Connection header since we don't support persistant + * connections. + */ + if (safe_write(connptr->server_fd, "Connection: close\r\n", 19) < 0) + goto COMMON_EXIT; - connptr->type = RELAYCONN; - stats.num_opens++; + free(request); + free_uri(uri); return 0; + + COMMON_EXIT: + free(request); + free_uri(uri); + return -1; } /* * Check to see if the line is allowed or not depending on the anonymous * headers which are to be allowed. */ -static int anonheader(char *line) +static int compare_header(char *line) { char *buffer, *ptr; int ret; - assert(line); - if ((ptr = xstrstr(line, ":", strlen(line), FALSE)) == NULL) - return 0; + return -1; ptr++; - if ((buffer = xmalloc(ptr - line + 1)) == NULL) - return 0; + if ((buffer = malloc(ptr - line + 1)) == NULL) + return -1; memcpy(buffer, line, ptr - line); buffer[ptr - line] = '\0'; ret = anon_search(buffer); - free(buffer); - return ret; + safefree(buffer); + return ret ? 0 : -1; } /* - * Used to read in the lines from the header (client side) when we're doing - * the anonymous header reduction. + * pull_client_data is used to pull across any client data (like in a + * POST) which needs to be handled before an error can be reported, or + * server headers can be processed. + * - rjkaes */ -static int readanonconn(struct conn_s *connptr) +static int pull_client_data(struct conn_s *connptr, unsigned long int length) { - char *line = NULL; - int retv; + char buffer[MAXBUFFSIZE]; + int len; - assert(connptr); + do { + len = safe_read(connptr->client_fd, buffer, min(MAXBUFFSIZE, length)); - if ((retv = readline(connptr->client_fd, connptr->cbuffer, &line)) <= - 0) { - return retv; - } + if (len <= 0) { + return -1; + } + + if (!connptr->output_message) { + if (safe_write(connptr->server_fd, buffer, len) < 0) { + return -1; + } + } + + length -= len; + } while (length > 0); - if ((line[0] == '\n') || (strncmp(line, "\r\n", 2) == 0)) { - connptr->clientheader = TRUE; - } else if (!anonheader(line)) { - safefree(line); - return 0; - } - - push_buffer(connptr->cbuffer, line, strlen(line)); return 0; } +#ifdef XTINYPROXY_ENABLE /* - * Read in the bytes from the socket + * Add the X-Tinyproxy header to the collection of headers being sent to + * the server. + * -rjkaes */ -static int readconn(int fd, struct buffer_s *buffptr) +static int add_xtinyproxy_header(struct conn_s *connptr) { - int bytesin; - - assert(fd >= 0); - assert(buffptr); + char ipaddr[PEER_IP_LENGTH]; + char xtinyproxy[32]; + int length; - if ((bytesin = readbuff(fd, buffptr)) < 0) { + length = snprintf(xtinyproxy, sizeof(xtinyproxy), + "X-Tinyproxy: %s\r\n", + getpeer_ip(connptr->client_fd, ipaddr)); + if (safe_write(connptr->server_fd, xtinyproxy, length) < 0) return -1; - } -#ifdef __DEBUG__ - log("readconn [%d]: %d", fd, bytesin); -#endif - stats.num_rx += bytesin; - return bytesin; + return 0; } +#endif /* XTINYPROXY */ /* - * Write the bytes from the buffer to the socket + * Here we loop through all the headers the client is sending. If we + * are running in anonymous mode, we will _only_ send the headers listed + * (plus a few which are required for various methods). + * - rjkaes */ -static int writeconn(int fd, struct buffer_s *buffptr) +static int process_client_headers(struct conn_s *connptr) { - int bytessent; + char header[LINE_LENGTH]; + long content_length = -1; + + char *skipheaders[] = { + "proxy-connection", + "host", + "connection" + }; + int i; + + for ( ; ; ) { + if (readline(connptr->client_fd, header, LINE_LENGTH) <= 0) { + return -1; + } - assert(fd >= 0); - assert(buffptr); + if (header[0] == '\n' + || (header[0] == '\r' && header[1] == '\n')) { + break; + } - if ((bytessent = writebuff(fd, buffptr)) < 0) { - return -1; - } + if (connptr->output_message) + continue; - stats.num_tx += bytessent; - return bytessent; -} + if (config.anonymous && compare_header(header) < 0) + continue; -/* - * Factored out the common function to read from the client. It was used in - * two different places with no change, so no point in having the same code - * twice. - */ -static int read_from_client(struct conn_s *connptr, fd_set * readfds) -{ - assert(connptr); - assert(readfds); - - if (FD_ISSET(connptr->client_fd, readfds)) { - if (config.anonymous && !connptr->clientheader) { - if (readanonconn(connptr) < 0) { - shutdown(connptr->client_fd, 2); - shutdown(connptr->server_fd, 2); - connptr->type = FINISHCONN; - return -1; + /* + * Don't send certain headers. + */ + for (i = 0; i < (sizeof(skipheaders) / sizeof(char *)); i++) { + if (strncasecmp(header, skipheaders[i], strlen(skipheaders[i])) == 0) { + break; } - } else if (readconn(connptr->client_fd, connptr->cbuffer) < 0) { - shutdown(connptr->client_fd, 2); - shutdown(connptr->server_fd, 2); - connptr->type = FINISHCONN; - return -1; } - connptr->actiontime = time(NULL); + if (i != (sizeof(skipheaders) / sizeof(char *))) + continue; + + if (strncasecmp(header, "content-length", 14) == 0) { + char *content_ptr = strchr(header, ':') + 1; + content_length = atol(content_ptr); + } + + if (safe_write(connptr->server_fd, header, strlen(header)) < 0) + return -1; } - return 0; -} + if (!connptr->output_message) { +#ifdef XTINYPROXY_ENABLE + if (config.my_domain + && add_xtinyproxy_header(connptr) < 0) { + return -1; + } +#endif /* XTINYPROXY */ -/* - * Factored out the common write to client function since, again, it was used - * in two different places with no changes. - */ -static int write_to_client(struct conn_s *connptr, fd_set * writefds) -{ - assert(connptr); - assert(writefds); - - if (FD_ISSET(connptr->client_fd, writefds)) { - if (writeconn(connptr->client_fd, connptr->sbuffer) < 0) { - shutdown(connptr->client_fd, 2); - shutdown(connptr->server_fd, 2); - connptr->type = FINISHCONN; + if (safe_write(connptr->server_fd, header, strlen(header)) < 0) { return -1; } - connptr->actiontime = time(NULL); } - return 0; + /* + * Spin here pulling the data from the client. + */ + if (content_length >= 0) + return pull_client_data(connptr, content_length); + else + return 0; } /* - * All of the *_req functions handle the various stages a connection can go - * through. I moved them out from getreqs because they handle a lot of error - * code and it was indenting too far in. As you can see they are very simple, - * and are only called once from getreqs, hence the inlining. + * Loop through all the headers (including the response code) from the + * server. */ - -inline static void newconn_req(struct conn_s *connptr, fd_set * readfds) +static int process_server_headers(struct conn_s *connptr) { -#ifdef UPSTREAM_PROXY - int fd; - char peer_ipaddr[PEER_IP_LENGTH]; -#endif /* UPSTREAM_PROXY */ - - assert(connptr); - assert(readfds); - - if (FD_ISSET(connptr->client_fd, readfds)) { -#ifdef UPSTREAM_PROXY - if (config.upstream_name && config.upstream_port != 0) { - getpeer_ip(connptr->client_fd, peer_ipaddr); - /* Log the incoming connection */ - if (!config.restricted) { - log("Connect (upstream): %s", peer_ipaddr); - } + char header[LINE_LENGTH]; - if (inet_aton(config.upstream_name, NULL) - || lookup(NULL, config.upstream_name)) { - if ((fd = opensock(config.upstream_name, config.upstream_port)) < 0) { - httperr(connptr, 500, - "Unable to connect to host (cannot create sock)"); - stats.num_badcons++; - return; - } - - connptr->server_fd = fd; - connptr->type = WAITCONN; - } - /* Otherwise submit a DNS request and hope for - the best. */ - else { - if (adns_submit(adns, config.upstream_name, adns_r_a, adns_qf_quoteok_cname | adns_qf_cname_loose, connptr, &(connptr->adns_qu))) { - httperr(connptr, 500, "Resolver error connecting to host"); - stats.num_badcons++; - return; - } else { -#ifdef __DEBUG__ - log("DNS request submitted"); -#endif - /* copy domain for later caching */ - connptr->domain = xstrdup(config.upstream_name); - connptr->port_no = config.upstream_port; - connptr->type = DNS_WAITCONN; - } - } - } else { -#endif - if (clientreq(connptr) < 0) { - shutdown(connptr->client_fd, 2); - connptr->type = FINISHCONN; - return; - } + for ( ; ; ) { + if (readline(connptr->server_fd, header, LINE_LENGTH) <= 0) { + return -1; + } - if (!connptr) - abort(); + if (header[0] == '\n' + || (header[0] == '\r' && header[1] == '\n')) { + break; + } - connptr->actiontime = time(NULL); -#ifdef UPSTREAM_PROXY + if (!connptr->simple_req + && safe_write(connptr->client_fd, header, strlen(header)) < 0) { + return -1; } -#endif /* UPSTREAM_PROXY */ } + + if (!connptr->simple_req + && safe_write(connptr->client_fd, header, strlen(header)) < 0) { + return -1; + } + return 0; } -inline static void waitconn_req(struct conn_s *connptr, fd_set * readfds, - fd_set * writefds) +/* + * Switch the sockets into nonblocking mode and begin relaying the bytes + * between the two connections. We continue to use the buffering code + * since we want to be able to buffer a certain amount for slower + * connections (as this was the reason why I originally modified + * tinyproxy oh so long ago...) + * - rjkaes + */ +static void relay_connection(struct conn_s *connptr) { - assert(connptr); - assert(readfds); - assert(writefds); - - if (read_from_client(connptr, readfds) < 0) - return; - - if (FD_ISSET(connptr->server_fd, readfds) - || FD_ISSET(connptr->server_fd, writefds)) { - if (clientreq_finish(connptr) < 0) { - shutdown(connptr->server_fd, 2); - shutdown(connptr->client_fd, 2); - connptr->type = FINISHCONN; + fd_set rset, wset; + struct timeval tv; + time_t last_access; + int ret; + int len; + double tdiff; + int maxfd = (connptr->client_fd > connptr->server_fd) + ? connptr->client_fd : connptr->server_fd; + + socket_nonblocking(connptr->client_fd); + socket_nonblocking(connptr->server_fd); + + last_access = time(NULL); + + for ( ; ; ) { + FD_ZERO(&rset); + FD_ZERO(&wset); + + tv.tv_sec = config.idletimeout - difftime(time(NULL), last_access); + tv.tv_usec = 0; + + if (buffer_size(connptr->sbuffer) > 0) + FD_SET(connptr->client_fd, &wset); + if (buffer_size(connptr->cbuffer) > 0) + FD_SET(connptr->server_fd, &wset); + if (buffer_size(connptr->sbuffer) < MAXBUFFSIZE) + FD_SET(connptr->server_fd, &rset); + if (buffer_size(connptr->cbuffer) < MAXBUFFSIZE) + FD_SET(connptr->client_fd, &rset); + + tdiff = difftime(time(NULL), last_access); + if (tdiff > config.idletimeout) { + log(LOG_INFO, "Idle Timeout (before select) %g > %u", tdiff, config.idletimeout); return; } - connptr->actiontime = time(NULL); - } -} - -inline static void relayconn_req(struct conn_s *connptr, fd_set * readfds, - fd_set * writefds) -{ - assert(connptr); - assert(readfds); - assert(writefds); - - if (read_from_client(connptr, readfds) < 0) - return; - - if (FD_ISSET(connptr->server_fd, readfds)) { - if (connptr->serverheader) { - if (readconn(connptr->server_fd, connptr->sbuffer) < 0) { - shutdown(connptr->server_fd, 2); - connptr->type = CLOSINGCONN; + + ret = select(maxfd + 1, &rset, &wset, NULL, &tv); + if (ret == 0) { + tdiff = difftime(time(NULL), last_access); + if (tdiff > config.idletimeout) { + log(LOG_INFO, "Idle Timeout (after select) %g > %u", tdiff, config.idletimeout); return; + } else { + continue; } - } else { - /* - * We need to read in the first line to rewrite the - * version back down to HTTP/1.0 (if needed) - */ - char *line = NULL, *ptr, *newline; - int retv; - - if ( - (retv = - readline(connptr->server_fd, connptr->sbuffer, - &line)) < 0) { - shutdown(connptr->server_fd, 2); - httperr(connptr, 500, "Server Closed Early"); + } else if (ret < 0) + return; + + if (FD_ISSET(connptr->server_fd, &rset)) { + len = readbuff(connptr->server_fd, connptr->sbuffer); + if (len < 0) { + shutdown(connptr->server_fd, SHUT_WR); + break; + } + last_access = time(NULL); + } + if (FD_ISSET(connptr->client_fd, &rset)) { + len = readbuff(connptr->client_fd, connptr->cbuffer); + if (len < 0) { return; - } else if (retv == 0) + } + last_access = time(NULL); + } + if (FD_ISSET(connptr->server_fd, &wset)) { + len = writebuff(connptr->server_fd, connptr->cbuffer); + if (len < 0) { + shutdown(connptr->server_fd, SHUT_WR); + break; + } + last_access = time(NULL); + } + if (FD_ISSET(connptr->client_fd, &wset)) { + len = writebuff(connptr->client_fd, connptr->sbuffer); + if (len < 0) { return; - - connptr->serverheader = TRUE; - - if (strncasecmp(line, "HTTP/1.0", 8)) { - /* Okay, we need to rewrite it then */ - if (!(ptr = strchr(line, ' '))) { - shutdown(connptr->server_fd, 2); - httperr(connptr, 500, - "There was Server Error"); - return; - } - ptr++; - - if (!(newline = xmalloc(strlen(line) + 1))) { - shutdown(connptr->server_fd, 2); - httperr(connptr, 503, - "No Memory Available"); - return; - } - - sprintf(newline, "HTTP/1.0 %s", ptr); - safefree(line); - line = newline; } - - push_buffer(connptr->sbuffer, line, strlen(line)); + last_access = time(NULL); } - connptr->actiontime = time(NULL); } - if (write_to_client(connptr, writefds) < 0) - return; - - if (FD_ISSET(connptr->server_fd, writefds)) { - if (writeconn(connptr->server_fd, connptr->cbuffer) < 0) { - shutdown(connptr->server_fd, 2); - connptr->type = CLOSINGCONN; + /* + * Here the server has closed the connection... write the + * remainder to the client and then exit. + */ + socket_blocking(connptr->client_fd); + while (buffer_size(connptr->sbuffer) > 0) { + len = writebuff(connptr->client_fd, connptr->sbuffer); + if (len < 0) { return; } - connptr->actiontime = time(NULL); } + + return; } -inline static void closingconn_req(struct conn_s *connptr, fd_set * writefds) +static void initialize_conn(struct conn_s *connptr) { - assert(connptr); - assert(writefds); + connptr->client_fd = connptr->server_fd = -1; + connptr->cbuffer = new_buffer(); + connptr->sbuffer = new_buffer(); + + connptr->output_message = NULL; + connptr->simple_req = FALSE; - write_to_client(connptr, writefds); + update_stats(STAT_OPEN); } -/* - * Check against the valid subnet to see if we should allow the access - */ -static int validuser(int fd) +static void destroy_conn(struct conn_s *connptr) { - char ipaddr[PEER_IP_LENGTH]; + connptr->client_fd = -1; + if (connptr->server_fd != -1) + close(connptr->server_fd); - assert(fd >= 0); + if (connptr->cbuffer) + delete_buffer(connptr->cbuffer); + if (connptr->sbuffer) + delete_buffer(connptr->sbuffer); - if (config.subnet == NULL) - return 1; + safefree(connptr->output_message); + safefree(connptr); - if (!strncmp(config.subnet, getpeer_ip(fd, ipaddr), - strlen(config.subnet))) { - return 1; - } else { - return 0; - } + update_stats(STAT_CLOSE); } /* - * Loop that checks for new connections, dispatches to the correct - * routines if bytes are pending, checks to see if it's time for a - * garbage collect. + * This is the main drive for each connection. As you can tell, for the + * first few steps we are using a blocking socket. If you remember the + * older tinyproxy code, this use to be a very confusing state machine. + * Well, no more! :) The sockets are only switched into nonblocking mode + * when we start the relay portion. This makes most of the original + * tinyproxy code, which was confusing, redundant. Hail progress. + * - rjkaes */ -int getreqs(void) +void handle_connection(int fd) { - static unsigned int garbc = 0; - fd_set readfds, writefds, exceptfds; /* chris - ADNS expects exceptfds */ struct conn_s *connptr; - int fd; - struct timeval tv, now; /* chris - for ADNS timeouts */ - char peer_ipaddr[PEER_IP_LENGTH]; + char peer_string[PEER_STRING_LENGTH]; - if (setup_fd < 0) { - log("ERROR getreqs: setup_fd not a socket"); - return -1; - } + log(LOG_INFO, "Connect: %s [%s]", getpeer_string(fd, peer_string), + getpeer_ip(fd, peer_ipaddr)); - /* Garbage collect the dead connections and close any idle ones */ - if (garbc++ >= GARBCOLL_INTERVAL) { - garbcoll(); - garbc = 0; + connptr = malloc(sizeof(struct conn_s)); + if (!connptr) { + log(LOG_CRIT, "Out of memory!"); + return; } - conncoll(); - - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_SET(setup_fd, &readfds); - - for (connptr = connections; connptr; connptr = connptr->next) { -#ifdef __DEBUG__ - log("Connptr: %p - %d / client %d server %d", connptr, - connptr->type, connptr->client_fd, connptr->server_fd); -#endif - switch (connptr->type) { - case NEWCONN: - if (buffer_size(connptr->cbuffer) < MAXBUFFSIZE) - FD_SET(connptr->client_fd, &readfds); - else { - httperr(connptr, 414, - "Your Request is way too long."); - } - break; - - /* no case here for DNS_WAITCONN */ - - case WAITCONN: - FD_SET(connptr->server_fd, &readfds); - FD_SET(connptr->server_fd, &writefds); - - if (buffer_size(connptr->cbuffer) < MAXBUFFSIZE) - FD_SET(connptr->client_fd, &readfds); - break; - case RELAYCONN: - if (buffer_size(connptr->sbuffer) > 0) - FD_SET(connptr->client_fd, &writefds); - if (buffer_size(connptr->sbuffer) < MAXBUFFSIZE) - FD_SET(connptr->server_fd, &readfds); + initialize_conn(connptr); + connptr->client_fd = fd; - if (buffer_size(connptr->cbuffer) > 0) - FD_SET(connptr->server_fd, &writefds); - if (buffer_size(connptr->cbuffer) < MAXBUFFSIZE) - FD_SET(connptr->client_fd, &readfds); - - break; - - case CLOSINGCONN: - if (buffer_size(connptr->sbuffer) > 0) - FD_SET(connptr->client_fd, &writefds); - else { - shutdown(connptr->client_fd, 2); - shutdown(connptr->server_fd, 2); - connptr->type = FINISHCONN; - } - break; + if (check_acl(fd) <= 0) { + update_stats(STAT_DENIED); + httperr(connptr, 403, "You do not have authorization for using this service."); + goto send_error; + } - default: - break; +#ifdef TUNNEL_SUPPORT + /* + * If an upstream proxy has been configured then redirect any + * connections to it. If we cannot connect to the upstream, see if + * we can handle it ourselves. I know I used GOTOs, but it seems to + * me to be the best way of handling this situations. Sue me. :) + * - rjkaes + */ + if (config.tunnel_name && config.tunnel_port != -1) { + log(LOG_INFO, "Redirecting to %s:%d", + config.tunnel_name, config.tunnel_port); + + connptr->server_fd = opensock(config.tunnel_name, config.tunnel_port); + if (connptr->server_fd < 0) { + log(LOG_ERR, "Could not connect to tunnel's end, see if we can handle it ourselves."); + goto internal_proxy; } - } - /* Set a 60 second time out */ - tv.tv_sec = 1;//60; - tv.tv_usec = 0; - - /* chris - Make ADNS do its stuff, too. */ - { - struct timeval *tv_mod = &tv; - int foo = FD_SETSIZE; - gettimeofday(&now, NULL); - FD_ZERO(&exceptfds); - adns_beforeselect(adns, &foo, &readfds, &writefds, &exceptfds, &tv_mod, NULL, &now); + /* + * I know GOTOs are evil, but duplicating the code is even + * more evil. + * - rjkaes + */ + goto relay_proxy; } +#endif /* TUNNEL_SUPPORT */ - if (select(FD_SETSIZE, &readfds, &writefds, &exceptfds, &tv) < 0) { -#ifdef __DEBUG__ - log("select error: %s", strerror(errno)); -#endif - return 0; +internal_proxy: + if (process_method(connptr) < -1) { + destroy_conn(connptr); + return; } - /* chris - see whether any ADNS lookups have completed */ - gettimeofday(&now, NULL); - adns_afterselect(adns, FD_SETSIZE, &readfds, &writefds, &exceptfds, &now); - - for (connptr = connections; connptr; connptr = connptr->next) { - adns_answer *ans; - - if (connptr->type == DNS_WAITCONN && - adns_check(adns, &(connptr->adns_qu), &ans, (void**)&connptr) == 0) { - - if (ans->status == adns_s_ok) { - if (connptr->domain) { - insert(ans->rrs.inaddr, connptr->domain); - free(connptr->domain); - } - - clientreq_dnscomplete(connptr, ans->rrs.inaddr); - free(ans); - - /* hack */ - FD_SET(connptr->server_fd, &readfds); -#ifdef __DEBUG__ - log("DNS resolution successful"); -#endif - } else { - free(ans); - - httperr(connptr, 500, "Unable to resolve hostname in URL"); -#ifdef __DEBUG__ - log("DNS resolution failed"); -#endif - } +send_error: + if (!connptr->simple_req) { + if (process_client_headers(connptr) < 0) { + update_stats(STAT_BADCONN); + destroy_conn(connptr); + return; } } - /* Check to see if there are new connections pending */ - if (FD_ISSET(setup_fd, &readfds) && (fd = listen_sock()) >= 0) { - new_conn(fd); /* make a connection from the FD */ - - if (validuser(fd)) { - if (config.cutoffload && (load > config.cutoffload)) { - stats.num_refused++; - httperr(connptr, 503, - "tinyproxy is not accepting connections due to high system load"); - } - } else { - httperr(connptr, 403, - "You are not authorized to use the service."); - log("AUTH Rejected connection from %s", - getpeer_ip(fd, peer_ipaddr)); - } + if (connptr->output_message) { + safe_write(connptr->client_fd, connptr->output_message, + strlen(connptr->output_message)); + + destroy_conn(connptr); + return; } - /* - * Loop through the connections and dispatch them to the appropriate - * handler - */ - for (connptr = connections; connptr; connptr = connptr->next) { - switch (connptr->type) { - case NEWCONN: - newconn_req(connptr, &readfds); - break; - - case WAITCONN: - waitconn_req(connptr, &readfds, &writefds); - break; - - case RELAYCONN: - relayconn_req(connptr, &readfds, &writefds); - break; - - case CLOSINGCONN: - closingconn_req(connptr, &writefds); - break; - - default: - break; - } + if (process_server_headers(connptr) < 0) { + update_stats(STAT_BADCONN); + destroy_conn(connptr); + return; } - return 0; +relay_proxy: + relay_connection(connptr); + + /* + * All done... close everything and go home... :) + */ + destroy_conn(connptr); + return; } diff --git a/src/reqs.h b/src/reqs.h index 182fc20..5b6f1f9 100644 --- a/src/reqs.h +++ b/src/reqs.h @@ -1,4 +1,4 @@ -/* $Id: reqs.h,v 1.1.1.1 2000-02-16 17:32:23 sdyoung Exp $ +/* $Id: reqs.h,v 1.2 2000-09-12 00:04:42 rjkaes Exp $ * * See 'reqs.c' for a detailed description. * @@ -16,9 +16,9 @@ * General Public License for more details. */ -#ifndef _REQS_H_ -#define _REQS_H_ 1 +#ifndef _TINYPROXY_REQS_H_ +#define _TINYPROXY_REQS_H_ -extern int getreqs(void); +extern void handle_connection(int fd); #endif -- cgit v1.2.3