diff options
author | Steven Young <sdyoung@users.sourceforge.net> | 2000-02-16 17:32:49 +0000 |
---|---|---|
committer | Steven Young <sdyoung@users.sourceforge.net> | 2000-02-16 17:32:49 +0000 |
commit | 37e63909c09359ddd5baf7a237387ee5f7219c2d (patch) | |
tree | 118da48d4b5ed947bc4cb9c004d7e10a976be57a /src/reqs.c | |
parent | a094587fb0e92c5638808d7bff91ef802e200112 (diff) | |
download | tinyproxy-37e63909c09359ddd5baf7a237387ee5f7219c2d.tar.gz tinyproxy-37e63909c09359ddd5baf7a237387ee5f7219c2d.zip |
This commit was generated by cvs2svn to compensate for changes in r2,
which included commits to RCS files with non-trunk default branches.
Diffstat (limited to 'src/reqs.c')
-rw-r--r-- | src/reqs.c | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/src/reqs.c b/src/reqs.c new file mode 100644 index 0000000..7d6aa9f --- /dev/null +++ b/src/reqs.c @@ -0,0 +1,832 @@ +/* $Id: reqs.c,v 1.1.1.1 2000-02-16 17:32:23 sdyoung 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. + * + * Copyright (C) 1998 Steven Young + * Copyright (C) 1999 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. + */ + +#ifdef HAVE_CONFIG_H +#include <defines.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/uio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sysexits.h> +#include <assert.h> + +#include "config.h" +#include "tinyproxy.h" +#include "sock.h" +#include "utils.h" +#include "conns.h" +#include "log.h" +#include "reqs.h" +#include "buffer.h" +#include "filter.h" +#include "uri.h" +#include "regexp.h" + +/* chris - for asynchronous DNS */ +#include "dnscache.h" +#include <adns.h> +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 + +#define HTTPPATTERN "^([a-z]+)[ \t]+([^ \t]+)([ \t]+(HTTP/[0-9]+\\.[0-9]+))?" +#define NMATCH 4 +#define METHOD_IND 1 +#define URI_IND 2 +#define VERSION_MARK 3 +#define VERSION_IND 4 + +#define HTTP400ERROR "Unrecognizable request. Only HTTP is allowed." +#define HTTP500ERROR "Unable to connect to remote server." +#define HTTP503ERROR "Internal server error." + +/* + * Parse a client HTTP request and then establish connection. + */ +static int clientreq(struct conn_s *connptr) +{ + URI *uri = NULL; + char *inbuf, *buffer, *request, *port; + char *inbuf_ptr; + + regex_t preg; + regmatch_t pmatch[NMATCH]; + + long len; + int fd, port_no; + + 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; + } + + 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); + } + + if (regcomp(&preg, HTTPPATTERN, REG_EXTENDED | REG_ICASE) != 0) { + log("ERROR clientreq: regcomp"); + return 0; + } + if (regexec(&preg, inbuf, NMATCH, pmatch, 0) != 0) { + log("ERROR clientreq: regexec"); + regfree(&preg); + return -1; + } + regfree(&preg); + + if (pmatch[VERSION_MARK].rm_so == -1) + 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)", + peer_ipaddr, inbuf); + httperr(connptr, 400, HTTP400ERROR); + 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", + peer_ipaddr); + httperr(connptr, 503, HTTP503ERROR); + 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"); + httperr(connptr, 503, HTTP503ERROR); + goto COMMON_EXIT; + } + safefree(buffer); + + if (strcasecmp(uri->scheme, "http") != 0) { + char *error_string = xmalloc(strlen(uri->scheme) + 64); + sprintf(error_string, "Invalid scheme (%s). Only HTTP is allowed.", + uri->scheme); + httperr(connptr, 400, error_string); + safefree(error_string); + goto COMMON_EXIT; + } + + if ((strlen(config.stathost) > 0) && + strcasecmp(uri->authority, config.stathost) == 0) { + showstats(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)", + peer_ipaddr); + httperr(connptr, 404, + "Unable to connect to filtered host."); + goto COMMON_EXIT; + } + } +#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", + peer_ipaddr); + httperr(connptr, 503, HTTP503ERROR); + 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, " "); + strcat(request, uri->path); + if (uri->query) { + strcat(request, "?"); + strcat(request, uri->query); + } + 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) == 0) { + 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); + } +#endif + + /* Add the rewritten request to the buffer */ + unshift_buffer(connptr->cbuffer, request, strlen(request)); + + /* + * HACK HACK HACK: When we're sending a POST there is no restriction + * on the length of the header. If we don't let all the header lines + * through, the POST will not work. This _definitely_ needs to be + * fixed. - rjkaes + */ + if (!xstrstr(inbuf, "POST ", 5, FALSE)) { + connptr->clientheader = TRUE; + } + + 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); + + 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; + } + + return 0; +} + +/* + * Finish the client request + */ +static int clientreq_finish(struct conn_s *connptr) +{ + int sockopt, len = sizeof(sockopt); + + assert(connptr); + + if (getsockopt + (connptr->server_fd, SOL_SOCKET, SO_ERROR, &sockopt, &len) < 0) { + log("ERROR clientreq_finish: getsockopt error (%s)", + strerror(errno)); + return -1; + } + + 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; + } + } + + connptr->type = RELAYCONN; + stats.num_opens++; + return 0; +} + +/* + * 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) +{ + struct allowedhdr_s *allowedptr = allowedhdrs; + + assert(line); + assert(allowedhdrs); + + if (!xstrstr(line, "GET ", 4, FALSE) + || !xstrstr(line, "POST ", 5, FALSE) + || !xstrstr(line, "HEAD ", 5, FALSE)) + return 1; + + for (allowedptr = allowedhdrs; allowedptr; + allowedptr = allowedptr->next) { + if (!strncasecmp + (line, allowedptr->hdrname, strlen(allowedptr->hdrname))) { + return 1; + } + } + return 0; +} + +/* + * Used to read in the lines from the header (client side) when we're doing + * the anonymous header reduction. + */ +static int readanonconn(struct conn_s *connptr) +{ + char *line = NULL; + int retv; + + assert(connptr); + + if ((retv = readline(connptr->client_fd, connptr->cbuffer, &line)) <= + 0) { + return retv; + } + + 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; +} + +/* + * Read in the bytes from the socket + */ +static int readconn(int fd, struct buffer_s *buffptr) +{ + int bytesin; + + assert(fd >= 0); + assert(buffptr); + + if ((bytesin = readbuff(fd, buffptr)) < 0) { + return -1; + } +#ifdef __DEBUG__ + log("readconn [%d]: %d", fd, bytesin); +#endif + + stats.num_rx += bytesin; + return bytesin; +} + +/* + * Write the bytes from the buffer to the socket + */ +static int writeconn(int fd, struct buffer_s *buffptr) +{ + int bytessent; + + assert(fd >= 0); + assert(buffptr); + + if ((bytessent = writebuff(fd, buffptr)) < 0) { + return -1; + } + + stats.num_tx += bytessent; + return bytessent; +} + +/* + * 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; + } + } 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); + } + + return 0; +} + +/* + * 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; + return -1; + } + connptr->actiontime = time(NULL); + } + + 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. + */ + +inline static void newconn_req(struct conn_s *connptr, fd_set * readfds) +{ + assert(connptr); + assert(readfds); + + if (FD_ISSET(connptr->client_fd, readfds)) { + if (clientreq(connptr) < 0) { + shutdown(connptr->client_fd, 2); + connptr->type = FINISHCONN; + return; + } + + if (!connptr) + abort(); + + connptr->actiontime = time(NULL); + } +} + +inline static void waitconn_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) + || FD_ISSET(connptr->server_fd, writefds)) { + if (clientreq_finish(connptr) < 0) { + shutdown(connptr->server_fd, 2); + shutdown(connptr->client_fd, 2); + connptr->type = FINISHCONN; + 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; + return; + } + } 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"); + return; + } else if (retv == 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)); + } + 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; + return; + } + connptr->actiontime = time(NULL); + } +} + +inline static void closingconn_req(struct conn_s *connptr, fd_set * writefds) +{ + assert(connptr); + assert(writefds); + + write_to_client(connptr, writefds); +} + +/* + * Check against the valid subnet to see if we should allow the access + */ +static int validuser(int fd) +{ + char ipaddr[PEER_IP_LENGTH]; + + assert(fd >= 0); + + if (config.subnet == NULL) + return 1; + + if (!strncmp(config.subnet, getpeer_ip(fd, ipaddr), + strlen(config.subnet))) { + return 1; + } else { + return 0; + } +} + +/* + * 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. + */ +int getreqs(void) +{ + 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]; + + if (setup_fd < 0) { + log("ERROR getreqs: setup_fd not a socket"); + return -1; + } + + /* Garbage collect the dead connections and close any idle ones */ + if (garbc++ >= GARBCOLL_INTERVAL) { + garbcoll(); + garbc = 0; + } + 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); + + 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; + + default: + break; + } + } + + /* 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); + } + + if (select(FD_SETSIZE, &readfds, &writefds, &exceptfds, &tv) < 0) { +#ifdef __DEBUG__ + log("select error: %s", strerror(errno)); +#endif + return 0; + } + + /* 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 + } + } + } + + /* 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)); + } + } + + /* + * 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; + } + } + + return 0; +} |