/* $Id: reqs.c,v 1.69 2002-04-26 19:33:09 rjkaes Exp $ * * This is where all the work in tinyproxy is actually done. Incoming * 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 TUNNEL_SUPPORT 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-2002 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 * Free Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #include "tinyproxy.h" #include "acl.h" #include "anonymous.h" #include "buffer.h" #include "conns.h" #include "filter.h" #include "hashmap.h" #include "log.h" #include "regexp.h" #include "reqs.h" #include "sock.h" #include "stats.h" #include "utils.h" #include "vector.h" #define HTTP400ERROR "Unrecognizable request. Only HTTP is allowed." #define HTTP500ERROR "Unable to connect to remote server." #define HTTP503ERROR "Internal server error." /* * Maximum length of a HTTP line */ #define HTTP_LINE_LENGTH (MAXBUFFSIZE / 6) /* * Macro to help test if the Upstream proxy supported is compiled in and * enabled. */ #ifdef UPSTREAM_SUPPORT # define UPSTREAM_CONFIGURED() (config.upstream_name && config.upstream_port != -1) #else # define UPSTREAM_CONFIGURED() (0) #endif /* * Macro to help test if tunnel support is compiled in, and is enabled. */ #ifdef TUNNEL_SUPPORT # define TUNNEL_CONFIGURED() (config.tunnel_name && config.tunnel_port != -1) #else # define TUNNEL_CONFIGURED() (0) #endif /* * Codify the test for the carriage return and new line characters. */ #define CHECK_CRLF(header, len) ((len == 1 && header[0] == '\n') || (len == 2 && header[0] == '\r' && header[1] == '\n')) /* * This is a global variable which stores which ports are allowed by * the CONNECT method. It's a security thing. */ static vector_t ports_allowed_by_connect = NULL; /* * Now, this routine adds a "port" to the list. It also creates the list if * it hasn't already by done. */ void add_connect_port_allowed(int port) { if (!ports_allowed_by_connect) { ports_allowed_by_connect = vector_create(); if (!ports_allowed_by_connect) { log_message(LOG_WARNING, "Could not create a list of allowed CONNECT ports"); return; } } log_message(LOG_INFO, "Adding Port [%d] to the list allowed by CONNECT", port); vector_insert(ports_allowed_by_connect, (void **)&port, sizeof(port)); } /* * This routine checks to see if a port is allowed in the CONNECT method. * * Returns: 1 if allowed * 0 if denied * negative upon error */ static int check_allowed_connect_ports(int port) { ssize_t i; ssize_t ret; int *data; /* * If the port list doesn't exist, allow everything. This might need * to be changed in the future. */ if (!ports_allowed_by_connect) return 1; for (i = 0; i < vector_length(ports_allowed_by_connect); ++i) { ret = vector_getentry(ports_allowed_by_connect, i, (void **)&data); if (ret < 0) return -1; if (*data == port) return 1; } return 0; } /* * Read in the first line from the client (the request line for HTTP * connections. The request line is allocated from the heap, but it must * be freed in another function. */ static int read_request_line(struct conn_s *connptr) { ssize_t len; retry: len = readline(connptr->client_fd, &connptr->request_line); if (len <= 0) { log_message(LOG_ERR, "read_request_line: Client (file descriptor: %d) closed socket before read.", connptr->client_fd); return -1; } /* * Strip the new line and character return from the string. */ if (chomp(connptr->request_line, len) == len) { /* * If the number of characters removed is the same as the * length then it was a blank line. Free the buffer and * try again (since we're looking for a request line.) */ safefree(connptr->request_line); goto retry; } log_message(LOG_CONN, "Request (file descriptor %d): %s", connptr->client_fd, connptr->request_line); return 0; } /* * This structure holds the information pulled from a URL request. */ struct request_s { char *method; char *protocol; char *host; uint16_t port; char *path; }; static void free_request_struct(struct request_s *request) { if (!request) return; safefree(request->method); safefree(request->protocol); safefree(request->host); /* * The path is not used for SSL connection, so don't try to delete * it. */ if (request->path) safefree(request->path); safefree(request); } /* * Pull the information out of the URL line. */ static int extract_http_url(const char *url, struct request_s *request) { request->host = safemalloc(strlen(url) + 1); request->path = safemalloc(strlen(url) + 1); if (!request->host || !request->path) goto ERROR_EXIT; if (sscanf (url, "http://%[^:/]:%hu%s", request->host, &request->port, request->path) == 3) ; else if (sscanf(url, "http://%[^/]%s", request->host, request->path) == 2) request->port = 80; else if (sscanf(url, "http://%[^:/]:%hu", request->host, &request->port) == 2) strcpy(request->path, "/"); else if (sscanf(url, "http://%[^/]", request->host) == 1) { request->port = 80; strcpy(request->path, "/"); } else { log_message(LOG_ERR, "extract_http_url: Can't parse URL."); goto ERROR_EXIT; } return 0; ERROR_EXIT: if (request->host) safefree(request->host); if (request->path) safefree(request->path); return -1; } /* * Extract the URL from a SSL connection. */ static int extract_ssl_url(const char *url, struct request_s *request) { request->host = safemalloc(strlen(url) + 1); if (!request->host) return -1; if (sscanf(url, "%[^:]:%hu", request->host, &request->port) == 2) ; else if (sscanf(url, "%s", request->host) == 1) request->port = 443; else { log_message(LOG_ERR, "extract_ssl_url: Can't parse URL."); safefree(request->host); return -1; } return 0; } /* * Create a connection for HTTP connections. */ static int establish_http_connection(struct conn_s *connptr, struct request_s *request) { return write_message(connptr->server_fd, "%s %s HTTP/1.0\r\n" \ "Host: %s\r\n" \ "Connection: close\r\n", request->method, request->path, request->host); } /* * These two defines are for the SSL tunnelling. */ #define SSL_CONNECTION_RESPONSE "HTTP/1.0 200 Connection established" #define PROXY_AGENT "Proxy-agent: " PACKAGE "/" VERSION /* * Send the appropriate response to the client to establish a SSL * connection. */ static inline int send_ssl_response(struct conn_s *connptr) { return write_message(connptr->client_fd, "%s\r\n" \ "%s\r\n" \ "\r\n", SSL_CONNECTION_RESPONSE, PROXY_AGENT); } /* * Break the request line apart and figure out where to connect and * build a new request line. Finally connect to the remote server. */ static struct request_s * process_request(struct conn_s *connptr) { char *url; struct request_s *request; int ret; size_t request_len; /* NULL out all the fields so free's don't cause segfaults. */ request = safecalloc(1, sizeof(struct request_s)); if (!request) return NULL; request_len = strlen(connptr->request_line) + 1; request->method = safemalloc(request_len); url = safemalloc(request_len); request->protocol = safemalloc(request_len); if (!request->method || !url || !request->protocol) { safefree(url); free_request_struct(request); return NULL; } ret = sscanf(connptr->request_line, "%[^ ] %[^ ] %[^ ]", request->method, url, request->protocol); if (ret < 2) { log_message(LOG_ERR, "process_request: Bad Request on file descriptor %d", connptr->client_fd); indicate_http_error(connptr, 400, "Bad Request. No request found."); safefree(url); free_request_struct(request); return NULL; } /* * FIXME: We need to add code for the simple HTTP/0.9 style GET * request. */ if (!url) { log_message(LOG_ERR, "process_request: Null URL on file descriptor %d", connptr->client_fd); indicate_http_error(connptr, 400, "Bad Request. Null URL."); safefree(url); free_request_struct(request); return NULL; } if (strncasecmp(url, "http://", 7) == 0) { /* Make sure the first four characters are lowercase */ memcpy(url, "http", 4); if (extract_http_url(url, request) < 0) { indicate_http_error(connptr, 400, "Bad Request. Could not parse URL."); safefree(url); free_request_struct(request); return NULL; } } else if (strcmp(request->method, "CONNECT") == 0) { if (extract_ssl_url(url, request) < 0) { indicate_http_error(connptr, 400, "Bad Request. Could not parse URL."); safefree(url); free_request_struct(request); return NULL; } /* Verify that the port in the CONNECT method is allowed */ if (check_allowed_connect_ports(request->port) <= 0) { indicate_http_error(connptr, 403, "CONNECT method not allowed with selected port."); log_message(LOG_INFO, "Refused CONNECT method on port %d", request->port); safefree(url); free_request_struct(request); return NULL; } connptr->connect_method = TRUE; } else { log_message(LOG_ERR, "process_request: Unknown URL type on file descriptor %d", connptr->client_fd); indicate_http_error(connptr, 400, "Bad Request. Unknown URL type."); safefree(url); free_request_struct(request); return NULL; } safefree(url); #ifdef FILTER_ENABLE /* * Filter restricted domains */ if (config.filter) { if (filter_url(request->host)) { update_stats(STAT_DENIED); log_message(LOG_NOTICE, "Proxying refused on filtered domain \"%s\"", request->host); indicate_http_error(connptr, 404, "Connection to filtered domain is now allowed."); free_request_struct(request); return NULL; } } #endif /* * Check to see if they're requesting the stat host */ if (config.stathost && strcmp(config.stathost, request->host) == 0) { log_message(LOG_NOTICE, "Request for the stathost."); connptr->show_stats = TRUE; free_request_struct(request); return NULL; } /* * Break apart the protocol and update the connection structure. */ if (strncasecmp(request->protocol, "http", 4) == 0) { memcpy(request->protocol, "HTTP", 4); sscanf(request->protocol, "HTTP/%u.%u", &connptr->protocol.major, &connptr->protocol.minor); } return request; } /* * 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 pull_client_data(struct conn_s *connptr, unsigned long int length) { char *buffer; ssize_t len; buffer = safemalloc(min(MAXBUFFSIZE, length)); if (!buffer) return -1; do { len = safe_read(connptr->client_fd, buffer, min(MAXBUFFSIZE, length)); if (len <= 0) goto ERROR_EXIT; if (!connptr->error_string) { if (safe_write(connptr->server_fd, buffer, len) < 0) goto ERROR_EXIT; } length -= len; } while (length > 0); safefree(buffer); return 0; ERROR_EXIT: safefree(buffer); return -1; } #ifdef XTINYPROXY_ENABLE /* * Add the X-Tinyproxy header to the collection of headers being sent to * the server. * -rjkaes */ static int add_xtinyproxy_header(struct conn_s *connptr) { char ipaddr[PEER_IP_LENGTH]; /* * Don't try to send if we have an invalid server handle. */ if (connptr->server_fd < 0) return 0; return write_message(connptr->server_fd, "X-Tinyproxy: %s\r\n", getpeer_ip(connptr->client_fd, ipaddr)); } #endif /* XTINYPROXY */ /* * Take a complete header line and break it apart (into a key and the data.) * Now insert this information into the hashmap for the connection so it * can be retrieved and manipulated later. */ static inline int add_header_to_connection(hashmap_t hashofheaders, char *header, size_t len) { char *sep; /* Get rid of the new line and return at the end */ len -= chomp(header, len); sep = strchr(header, ':'); if (!sep) return -1; /* Blank out colons, spaces, and tabs. */ while (*sep == ':' || *sep == ' ' || *sep == '\t') *sep++ = '\0'; /* Calculate the new length of just the data */ len -= sep - header - 1; return hashmap_insert(hashofheaders, header, sep, len); } /* * Read all the headers from the stream */ static int get_all_headers(int fd, hashmap_t hashofheaders) { char *header; ssize_t len; for (;;) { if ((len = readline(fd, &header)) <= 0) { safefree(header); return -1; } /* * If we received just a CR LF on a line, the headers are * finished. */ if (CHECK_CRLF(header, len)) { safefree(header); return 0; } if (add_header_to_connection(hashofheaders, header, len) < 0) { safefree(header); return -1; } safefree(header); } } /* * Extract the headers to remove. These headers were listed in the Connection * and Proxy-Connection headers. */ static int remove_connection_headers(hashmap_t hashofheaders) { static char* headers[] = { "connection", "proxy-connection" }; char *data; char* ptr; ssize_t len; int i; for (i = 0; i < (sizeof(headers) / sizeof(char *)); ++i) { /* Look for the connection header. If it's not found, return. */ len = hashmap_entry_by_key(hashofheaders, headers[i], (void **)&data); if (len <= 0) return 0; /* * Go through the data line and replace any special characters * with a NULL. */ ptr = data; while ((ptr = strpbrk(ptr, "()<>@,;:\\\"/[]?={} \t"))) *ptr++ = '\0'; /* * All the tokens are separated by NULLs. Now go through the * token and remove them from the hashofheaders. */ ptr = data; while (ptr < data + len) { hashmap_remove(hashofheaders, ptr); /* Advance ptr to the next token */ ptr += strlen(ptr) + 1; while (*ptr == '\0') ptr++; } /* Now remove the connection header it self. */ hashmap_remove(hashofheaders, headers[i]); } return 0; } /* * If there is a Content-Length header, then return the value; otherwise, return * a negative number. */ static long get_content_length(hashmap_t hashofheaders) { ssize_t len; char *data; long content_length = -1; len = hashmap_entry_by_key(hashofheaders, "content-length", (void **)&data); if (len > 0) content_length = atol(data); return content_length; } /* * Search for Via head in a hash of headers and either write a new Via header, * or append our information to the end of an existing Via header. * * FIXME: Need to add code to "hide" our internal information for security * purposes. */ static void write_via_header(int fd, hashmap_t hashofheaders, unsigned int major, unsigned int minor) { ssize_t len; char hostname[128]; char *data; gethostname(hostname, sizeof(hostname)); /* * See if there is a "Via" header. If so, again we need to do a bit * of processing. */ len = hashmap_entry_by_key(hashofheaders, "via", (void **)&data); if (len > 0) { write_message(fd, "Via: %s, %hu.%hu %s (%s/%s)\r\n", data, major, minor, hostname, PACKAGE, VERSION); hashmap_remove(hashofheaders, "via"); } else { write_message(fd, "Via: %hu.%hu %s (%s/%s)\r\n", major, minor, hostname, PACKAGE, VERSION); } } /* * Number of buckets to use internally in the hashmap. */ #define HEADER_BUCKETS 32 /* * 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 process_client_headers(struct conn_s *connptr) { static char *skipheaders[] = { "host", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade" }; int i; hashmap_t hashofheaders; hashmap_iter iter; long content_length = -1; char *data, *header; hashofheaders = hashmap_create(HEADER_BUCKETS); if (!hashofheaders) return -1; /* * Get all the headers from the client in a big hash. */ if (get_all_headers(connptr->client_fd, hashofheaders) < 0) { log_message(LOG_WARNING, "Could not retrieve all the headers from the client"); hashmap_delete(hashofheaders); return -1; } /* * Don't send headers if there's already an error, if the request was * a stats request, or if this was a CONNECT method (unless upstream * proxy is in use.) */ if (connptr->server_fd == -1 || connptr->show_stats || (connptr->connect_method && !UPSTREAM_CONFIGURED())) { log_message(LOG_INFO, "Not sending client headers to remote machine"); hashmap_delete(hashofheaders); return 0; } /* * See if there is a "Content-Length" header. If so, again we need * to do a bit of processing. */ content_length = get_content_length(hashofheaders); /* * See if there is a "Connection" header. If so, we need to do a bit * of processing. :) */ remove_connection_headers(hashofheaders); /* * Delete the headers listed in the skipheaders list */ for (i = 0; i < (sizeof(skipheaders) / sizeof(char *)); i++) { hashmap_remove(hashofheaders, skipheaders[i]); } /* Send, or add the Via header */ write_via_header(connptr->server_fd, hashofheaders, connptr->protocol.major, connptr->protocol.minor); /* * Output all the remaining headers to the remote machine. */ iter = hashmap_first(hashofheaders); if (iter >= 0) { for ( ; !hashmap_is_end(hashofheaders, iter); ++iter) { hashmap_return_entry(hashofheaders, iter, &data, (void**)&header); if (!is_anonymous_enabled() || anonymous_search(data) <= 0) { write_message(connptr->server_fd, "%s: %s\r\n", data, header); } } } /* Free the hashofheaders since it's no longer needed */ hashmap_delete(hashofheaders); #if defined(XTINYPROXY_ENABLE) if (config.my_domain) add_xtinyproxy_header(connptr); #endif /* Write the final "blank" line to signify the end of the headers */ safe_write(connptr->server_fd, "\r\n", 2); /* * Spin here pulling the data from the client. */ if (content_length > 0) return pull_client_data(connptr, (unsigned long int) content_length); else return 0; } /* * Loop through all the headers (including the response code) from the * server. */ static int process_server_headers(struct conn_s *connptr) { static char *skipheaders[] = { "keep-alive", "proxy-authenticate", "proxy-authorization", "transfer-encoding", }; char *response_line; hashmap_t hashofheaders; hashmap_iter iter; char *data, *header; ssize_t len; int i; /* FIXME: Remember to handle a "simple_req" type */ /* Get the response line from the remote server. */ if ((len = readline(connptr->server_fd, &response_line)) <= 0) return -1; hashofheaders = hashmap_create(HEADER_BUCKETS); if (!hashofheaders) { safefree(response_line); return -1; } /* * Get all the headers from the remote server in a big hash */ if (get_all_headers(connptr->server_fd, hashofheaders) < 0) { log_message(LOG_WARNING, "Could not retrieve all the headers from the remote server."); hashmap_delete(hashofheaders); safefree(response_line); return -1; } /* Send the saved response line first */ safe_write(connptr->client_fd, response_line, strlen(response_line)); safefree(response_line); /* * If there is a "Content-Length" header, retrieve the information * from it for later use. */ connptr->remote_content_length = get_content_length(hashofheaders); /* * See if there is a connection header. If so, we need to to a bit of * processing. */ remove_connection_headers(hashofheaders); /* * Delete the headers listed in the skipheaders list */ for (i = 0; i < (sizeof(skipheaders) / sizeof(char *)); i++) { hashmap_remove(hashofheaders, skipheaders[i]); } /* Send, or add the Via header */ write_via_header(connptr->client_fd, hashofheaders, connptr->protocol.major, connptr->protocol.minor); /* * Okay, output all the remaining headers to the client. */ iter = hashmap_first(hashofheaders); if (iter >= 0) { for ( ; !hashmap_is_end(hashofheaders, iter); ++iter) { hashmap_return_entry(hashofheaders, iter, &data, (void **)&header); write_message(connptr->client_fd, "%s: %s\r\n", data, header); } } hashmap_delete(hashofheaders); /* Write the final blank line to signify the end of the headers */ safe_write(connptr->client_fd, "\r\n", 2); return 0; } /* * 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) { fd_set rset, wset; struct timeval tv; time_t last_access; int ret; double tdiff; int maxfd = max(connptr->client_fd, connptr->server_fd) + 1; ssize_t bytes_received; 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); ret = select(maxfd, &rset, &wset, NULL, &tv); if (ret == 0) { tdiff = difftime(time(NULL), last_access); if (tdiff > config.idletimeout) { log_message(LOG_INFO, "Idle Timeout (after select) as %g > %u.", tdiff, config.idletimeout); return; } else { continue; } } else if (ret < 0) { log_message(LOG_ERR, "relay_connection: select() error \"%s\". Closing connection (client_fd:%d, server_fd:%d)", strerror(errno), connptr->client_fd, connptr->server_fd); return; } else { /* * Okay, something was actually selected so mark it. */ last_access = time(NULL); } if (FD_ISSET(connptr->server_fd, &rset)) { bytes_received = read_buffer(connptr->server_fd, connptr->sbuffer); if (bytes_received < 0) break; connptr->remote_content_length -= bytes_received; if (connptr->remote_content_length == 0) break; } if (FD_ISSET(connptr->client_fd, &rset) && read_buffer(connptr->client_fd, connptr->cbuffer) < 0) { break; } if (FD_ISSET(connptr->server_fd, &wset) && write_buffer(connptr->server_fd, connptr->cbuffer) < 0) { break; } if (FD_ISSET(connptr->client_fd, &wset) && write_buffer(connptr->client_fd, connptr->sbuffer) < 0) { break; } } /* * 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) { if (write_buffer(connptr->client_fd, connptr->sbuffer) < 0) break; } /* * Try to send any remaining data to the server if we can. */ socket_blocking(connptr->server_fd); while (BUFFER_SIZE(connptr->cbuffer) > 0) { if (write_buffer(connptr->client_fd, connptr->cbuffer) < 0) break; } return; } #ifdef UPSTREAM_SUPPORT /* * Establish a connection to the upstream proxy server. */ static int connect_to_upstream(struct conn_s *connptr, struct request_s *request) { char *combined_string; int len; connptr->server_fd = opensock(config.upstream_name, config.upstream_port); if (connptr->server_fd < 0) { log_message(LOG_WARNING, "Could not connect to upstream proxy."); indicate_http_error(connptr, 404, "Unable to connect to upstream proxy."); return -1; } log_message(LOG_CONN, "Established connection to upstream proxy \"%s\" using file descriptor %d.", config.upstream_name, connptr->server_fd); /* * We need to re-write the "path" part of the request so that we * can reuse the establish_http_connection() function. It expects a * method and path. */ if (connptr->connect_method) { len = strlen(request->host) + 6; combined_string = safemalloc(len + 1); if (!combined_string) { return -1; } snprintf(combined_string, len, "%s:%d", request->host, request->port); } else { len = strlen(request->host) + strlen(request->path) + 14; combined_string = safemalloc(len + 1); if (!combined_string) { return -1; } snprintf(combined_string, len, "http://%s:%d%s", request->host, request->port, request->path); } safefree(request->path); request->path = combined_string; return establish_http_connection(connptr, request); } #endif #ifdef TUNNEL_SUPPORT /* * If tunnel has been configured then redirect any connections to it. */ static int connect_to_tunnel(struct conn_s *connptr) { char *request_buf; ssize_t len; int pos; request_buf = safemalloc(HTTP_LINE_LENGTH); if (request_buf) { len = recv(connptr->client_fd, request_buf, HTTP_LINE_LENGTH - 1, MSG_PEEK); for (pos = 0; pos < len && request_buf[pos] != '\n'; pos++) ; request_buf[pos] = '\0'; log_message(LOG_CONN, "Request: %s", request_buf); safefree(request_buf); } log_message(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_message(LOG_WARNING, "Could not connect to tunnel."); indicate_http_error(connptr, 404, "Unable to connect to tunnel."); return -1; } log_message(LOG_INFO, "Established a connection to the tunnel \"%s\" using file descriptor %d.", config.tunnel_name, connptr->server_fd); return 0; } #endif /* * 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 */ void handle_connection(int fd) { struct conn_s *connptr; struct request_s *request = NULL; char peer_ipaddr[PEER_IP_LENGTH]; char peer_string[PEER_STRING_LENGTH]; log_message(LOG_CONN, "Connect (file descriptor %d): %s [%s]", fd, getpeer_string(fd, peer_string), getpeer_ip(fd, peer_ipaddr)); connptr = initialize_conn(fd); if (!connptr) { close(fd); return; } if (check_acl(fd, peer_ipaddr, peer_string) <= 0) { update_stats(STAT_DENIED); indicate_http_error(connptr, 403, "You do not have authorization for using this service."); goto send_error; } if (TUNNEL_CONFIGURED()) { if (connect_to_tunnel(connptr) < 0) goto internal_proxy; else goto relay_proxy; } internal_proxy: if (read_request_line(connptr) < 0) { update_stats(STAT_BADCONN); destroy_conn(connptr); return; } request = process_request(connptr); if (!request) { if (!connptr->error_string && !connptr->show_stats) { update_stats(STAT_BADCONN); destroy_conn(connptr); return; } goto send_error; } if (UPSTREAM_CONFIGURED()) { if (connect_to_upstream(connptr, request) < 0) goto send_error; } else { connptr->server_fd = opensock(request->host, request->port); if (connptr->server_fd < 0) { indicate_http_error(connptr, 500, HTTP500ERROR); goto send_error; } log_message(LOG_CONN, "Established connection to host \"%s\" using file descriptor %d.", request->host, connptr->server_fd); if (!connptr->connect_method) establish_http_connection(connptr, request); } send_error: free_request_struct(request); if (process_client_headers(connptr) < 0) { update_stats(STAT_BADCONN); if (!connptr->error_string) { destroy_conn(connptr); return; } } if (connptr->error_string) { send_http_error_message(connptr); destroy_conn(connptr); return; } else if (connptr->show_stats) { showstats(connptr); destroy_conn(connptr); return; } if (!connptr->connect_method || UPSTREAM_CONFIGURED()) { if (process_server_headers(connptr) < 0) { update_stats(STAT_BADCONN); destroy_conn(connptr); return; } } else { if (send_ssl_response(connptr) < 0) { log_message(LOG_ERR, "handle_connection: Could not send SSL greeting to client."); update_stats(STAT_BADCONN); destroy_conn(connptr); return; } } relay_proxy: relay_connection(connptr); log_message(LOG_INFO, "Closed connection between local client (fd:%d) and remote client (fd:%d)", connptr->client_fd, connptr->server_fd); /* * All done... close everything and go home... :) */ destroy_conn(connptr); return; }