/* * 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, hashmap_t hashofheaders) { char *url; struct request_s *request; int ret; size_t request_len; /* NULL out all the fields so frees 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", "detail", "Request has an invalid format", "url", url, NULL); 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", "detail", "Request has an empty URL", "url", url, NULL); safefree(url); free_request_struct(request); return NULL; } if (strncasecmp(url, "http://", 7) == 0 || (UPSTREAM_CONFIGURED() && strncasecmp(url, "ftp://", 6) == 0)) { char *skipped_type = strstr(url, "//") + 2; if (extract_http_url(skipped_type, request) < 0) { indicate_http_error(connptr, 400, "Bad Request", "detail", "Could not parse URL", "url", url, NULL); 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", "detail", "Could not parse URL", "url", url, NULL); 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, "Access violation", "detail", "The CONNECT method not allowed " \ "with the port you tried to use.", "url", url, NULL); 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 { #ifdef TRANSPARENT_PROXY /* * This section of code is used for the transparent proxy * option. You will need to configure your firewall to * redirect all connections for HTTP traffic to tinyproxy * for this to work properly. * * This code was written by Petr Lampa <*****@*****.**> */ int length; char *data; length = hashmap_entry_by_key(hashofheaders, "host", (void **)&data); if (length <= 0) { struct sockaddr_in dest_addr; if (getsockname(connptr->client_fd, (struct sockaddr *)&dest_addr, &length) < 0) { log_message(LOG_ERR, "process_request: cannot get destination IP for %d", connptr->client_fd); indicate_http_error(connptr, 400, "Bad Request", "detail", "Unknown destination", "url", url, NULL); safefree(url); free_request_struct(request); return NULL; } request->host = safemalloc(17); strcpy(request->host, inet_ntoa(dest_addr.sin_addr)); request->port = ntohs(dest_addr.sin_port); request->path = safemalloc(strlen(url) + 1); strcpy(request->path, url); safefree(url); build_url(&url, request->host, request->port, request->path); log_message(LOG_INFO, "process_request: trans IP %s %s for %d", request->method, url, connptr->client_fd); } else { request->host = safemalloc(length+1); if (sscanf(data, "%[^:]:%hu", request->host, &request->port) != 2) { strcpy(request->host, data); request->port = HTTP_PORT; } request->path = safemalloc(strlen(url) + 1); strcpy(request->path, url); safefree(url); build_url(&url, request->host, request->port, request->path); log_message(LOG_INFO, "process_request: trans Host %s %s for %d", request->method, url, connptr->client_fd); } if (config.ipAddr && strcmp(request->host, config.ipAddr) == 0) { log_message(LOG_ERR, "process_request: destination IP is localhost %d", connptr->client_fd); indicate_http_error(connptr, 400, "Bad Request", "detail", "You tried to connect to the machine the proxy is running on", "url", url, NULL); safefree(url); free_request_struct(request); return NULL; } #else log_message(LOG_ERR, "process_request: Unknown URL type on file descriptor %d", connptr->client_fd); indicate_http_error(connptr, 400, "Bad Request", "detail", "Unknown URL type", "url", url, NULL); safefree(url); free_request_struct(request); return NULL; #endif } #ifdef FILTER_ENABLE /* * Filter restricted domains/urls */ if (config.filter) { if (config.filter_url) ret = filter_url(url); else ret = filter_domain(request->host); if (ret) { update_stats(STAT_DENIED); if (config.filter_url) log_message(LOG_NOTICE, "Proxying refused on filtered url \"%s\"", url); else log_message(LOG_NOTICE, "Proxying refused on filtered domain \"%s\"", request->host); indicate_http_error(connptr, 403, "Filtered", "detail", "The request you made has been filted", "url", url, NULL); safefree(url); free_request_struct(request); return NULL; } } #endif safefree(url); /* * 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; }
/* * 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; hashmap_t hashofheaders = NULL; char peer_ipaddr[PEER_IP_LENGTH]; char peer_string[PEER_STRING_LENGTH]; getpeer_information(fd, peer_ipaddr, peer_string); log_message(LOG_CONN, "Connect (file descriptor %d): %s [%s]", fd, peer_string, peer_ipaddr); connptr = initialize_conn(fd, peer_ipaddr, peer_string); if (!connptr) { close(fd); return; } if (check_acl(fd, peer_ipaddr, peer_string) <= 0) { update_stats(STAT_DENIED); indicate_http_error(connptr, 403, "Access denied", "detail", "The administrator of this proxy has not configured it to service requests from your host.", NULL); send_http_error_message(connptr); destroy_conn(connptr); return; } if (read_request_line(connptr) < 0) { update_stats(STAT_BADCONN); indicate_http_error(connptr, 408, "Timeout", "detail", "Server timeout waiting for the HTTP request from the client.", NULL); send_http_error_message(connptr); destroy_conn(connptr); return; } /* * The "hashofheaders" store the client's headers. */ if (!(hashofheaders = hashmap_create(HEADER_BUCKETS))) { update_stats(STAT_BADCONN); indicate_http_error(connptr, 503, "Internal error", "detail", "An internal server error occurred while processing your request. Please contact the administrator.", NULL); send_http_error_message(connptr); destroy_conn(connptr); return; } /* * 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); update_stats(STAT_BADCONN); destroy_conn(connptr); return; } request = process_request(connptr, hashofheaders); if (!request) { if (!connptr->error_variables && !connptr->show_stats) { update_stats(STAT_BADCONN); destroy_conn(connptr); hashmap_delete(hashofheaders); return; } goto send_error; } connptr->upstream_proxy = UPSTREAM_HOST(request->host); if (connptr->upstream_proxy != NULL) { 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, "Unable to connect", "detail", PACKAGE " was unable to connect to the remote web server.", "error", strerror(errno), NULL); 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, hashofheaders) < 0) { update_stats(STAT_BADCONN); if (!connptr->error_variables) { hashmap_delete(hashofheaders); destroy_conn(connptr); return; } } hashmap_delete(hashofheaders); if (connptr->error_variables) { send_http_error_message(connptr); destroy_conn(connptr); return; } else if (connptr->show_stats) { showstats(connptr); destroy_conn(connptr); return; } if (!connptr->connect_method || (connptr->upstream_proxy != NULL)) { if (process_server_headers(connptr) < 0) { if (connptr->error_variables) send_http_error_message(connptr); 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_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; }
/* * Establish a connection to the upstream proxy server. */ static int connect_to_upstream(struct conn_s *connptr, struct request_s *request) { #ifndef UPSTREAM_SUPPORT /* * This function does nothing if upstream support was not compiled * into tinyproxy. */ return -1; #else char *combined_string; int len; struct upstream *cur_upstream = connptr->upstream_proxy; if(!cur_upstream) { log_message(LOG_WARNING, "No upstream proxy defined for %s.", request->host); indicate_http_error(connptr, 404, "Unable to connect to upstream proxy."); return -1; } connptr->server_fd = opensock(cur_upstream->host, cur_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", "detail", "A network error occurred while trying to connect to the upstream web proxy.", NULL); return -1; } log_message(LOG_CONN, "Established connection to upstream proxy \"%s\" using file descriptor %d.", cur_upstream->host, 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) + 7; combined_string = safemalloc(len); 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); if (!combined_string) { return -1; } snprintf(combined_string, len, "http://%s:%d%s", request->host, request->port, request->path); } if (request->path) safefree(request->path); request->path = combined_string; return establish_http_connection(connptr, request); #endif }
/* * 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", "proxy-connection", "transfer-encoding", }; char *response_line; hashmap_t hashofheaders; hashmap_iter iter; char *data, *header; ssize_t len; int i; int ret; /* FIXME: Remember to handle a "simple_req" type */ /* Get the response line from the remote server. */ retry: len = readline(connptr->server_fd, &response_line); if (len <= 0) return -1; /* * Strip the new line and character return from the string. */ if (chomp(response_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(response_line); goto retry; } 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); indicate_http_error(connptr, 503, "Could not retrieve all the headers", "detail", PACKAGE " was unable to retrieve and process headers from the remote web server.", NULL); return -1; } /* Send the saved response line first */ ret = write_message(connptr->client_fd, "%s\r\n", response_line); safefree(response_line); if (ret < 0) goto ERROR_EXIT; /* * If there is a "Content-Length" header, retrieve the information * from it for later use. */ connptr->content_length.server = 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 */ ret = write_via_header(connptr->client_fd, hashofheaders, connptr->protocol.major, connptr->protocol.minor); if (ret < 0) goto ERROR_EXIT; /* * All right, 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); ret = write_message(connptr->client_fd, "%s: %s\r\n", data, header); if (ret < 0) goto ERROR_EXIT; } } hashmap_delete(hashofheaders); /* Write the final blank line to signify the end of the headers */ if (safe_write(connptr->client_fd, "\r\n", 2) < 0) return -1; return 0; ERROR_EXIT: hashmap_delete(hashofheaders); return -1; }
/* * 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, hashmap_t hashofheaders) { static char *skipheaders[] = { "host", "keep-alive", "proxy-authenticate", "proxy-authorization", "proxy-connection", "te", "trailers", "transfer-encoding", "upgrade" }; int i; hashmap_iter iter; int ret = 0; char *data, *header; /* * 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 && (connptr->upstream_proxy == NULL))) { log_message(LOG_INFO, "Not sending client headers to remote machine"); return 0; } /* * See if there is a "Content-Length" header. If so, again we need * to do a bit of processing. */ connptr->content_length.client = 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 */ ret = write_via_header(connptr->server_fd, hashofheaders, connptr->protocol.major, connptr->protocol.minor); if (ret < 0) { indicate_http_error(connptr, 503, "Could not send data to remote server", "detail", "A network error occurred while trying to write data to the remote web server.", NULL); goto PULL_CLIENT_DATA; } /* * 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) { ret = write_message(connptr->server_fd, "%s: %s\r\n", data, header); if (ret < 0) { indicate_http_error(connptr, 503, "Could not send data to remote server", "detail", "A network error occurred while trying to write data to the remote web server.", NULL); goto PULL_CLIENT_DATA; } } } } #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 */ if (safe_write(connptr->server_fd, "\r\n", 2) < 0) return -1; /* * Spin here pulling the data from the client. */ PULL_CLIENT_DATA: if (connptr->content_length.client > 0) return pull_client_data(connptr, connptr->content_length.client); else return ret; }
int do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, struct request_s *request, struct config_s *conf, char **url) { socklen_t length; char *data; size_t ulen = strlen (*url); ssize_t i; length = hashmap_entry_by_key (hashofheaders, "host", (void **) &data); if (length <= 0) { struct sockaddr_in dest_addr; if (getsockname (connptr->client_fd, (struct sockaddr *) &dest_addr, &length) < 0) { log_message (LOG_ERR, "process_request: cannot get destination IP for %d", connptr->client_fd); indicate_http_error (connptr, 400, "Bad Request", "detail", "Unknown destination", "url", *url, NULL); return 0; } request->host = (char *) safemalloc (17); strlcpy (request->host, inet_ntoa (dest_addr.sin_addr), 17); request->port = ntohs (dest_addr.sin_port); request->path = (char *) safemalloc (ulen + 1); strlcpy (request->path, *url, ulen + 1); build_url (url, request->host, request->port, request->path); log_message (LOG_INFO, "process_request: trans IP %s %s for %d", request->method, *url, connptr->client_fd); } else { request->host = (char *) safemalloc (length + 1); if (sscanf (data, "%[^:]:%hu", request->host, &request->port) != 2) { strlcpy (request->host, data, length + 1); request->port = HTTP_PORT; } request->path = (char *) safemalloc (ulen + 1); strlcpy (request->path, *url, ulen + 1); build_url (url, request->host, request->port, request->path); log_message (LOG_INFO, "process_request: trans Host %s %s for %d", request->method, *url, connptr->client_fd); } if (conf->listen_addrs == NULL) { return 1; } for (i = 0; i < vector_length(conf->listen_addrs); i++) { const char *addr; addr = (char *)vector_getentry(conf->listen_addrs, i, NULL); if (addr && strcmp(request->host, addr) == 0) { log_message(LOG_ERR, "transparent: destination IP %s is local " "on socket fd %d", request->host, connptr->client_fd); indicate_http_error(connptr, 400, "Bad Request", "detail", "You tried to connect to the " "machine the proxy is running on", "url", *url, NULL); return 0; } } return 1; }