diff options
57 files changed, 1882 insertions, 1815 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..4354107 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: ./autogen.sh + - run: ./configure + - run: make + - run: make test + test-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: brew install automake + - run: ./autogen.sh + - run: ./configure + - run: make + valgrind-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: install valgrind + run: sudo apt-get install --assume-yes valgrind + - run: ./autogen.sh + - run: ./configure --enable-debug --enable-transparent --enable-reverse + - run: make + - run: make test + - run: make valgrind-test diff --git a/configure.ac b/configure.ac index 7de6087..3849383 100644 --- a/configure.ac +++ b/configure.ac @@ -141,7 +141,7 @@ AC_HEADER_STDC AC_HEADER_TIME AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([sys/ioctl.h alloca.h memory.h malloc.h sysexits.h \ - values.h]) + values.h poll.h]) dnl Checks for libary functions AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK @@ -204,6 +204,22 @@ fi #manpage_support_enabled AM_CONDITIONAL(HAVE_POD2MAN, test "x$POD2MAN" != "x" -a "x$POD2MAN" != "xno") +AC_PATH_PROG(GPERF, gperf, no) +AM_CONDITIONAL(HAVE_GPERF, test "x$GPERF" != "x" -a "x$GPERF" != "xno") +AH_TEMPLATE([HAVE_GPERF], + [Whether you have gperf installed for faster config parsing.]) + +if test "x$GPERF" != "x" -a "x$GPERF" != "xno" ; then + AS_ECHO_N(["checking whether gperf is recent enough... "]) + if "$GPERF" < src/conf-tokens.gperf >/dev/null 2>&1 ; then + AS_ECHO("yes") + AC_DEFINE(HAVE_GPERF) + else + AM_CONDITIONAL(HAVE_GPERF, false) + AS_ECHO("no") + fi +fi + AC_CONFIG_FILES([ Makefile src/Makefile diff --git a/docs/man5/tinyproxy.conf.txt.in b/docs/man5/tinyproxy.conf.txt.in index 8a18d55..758382c 100644 --- a/docs/man5/tinyproxy.conf.txt.in +++ b/docs/man5/tinyproxy.conf.txt.in @@ -58,6 +58,8 @@ only on one specific address. This allows you to specify which address Tinyproxy will bind to for outgoing connections to web servers or upstream proxies. +This parameter may be specified multiple times, then Tinyproxy +will try all the specified addresses in order. =item B<BindSame> @@ -291,6 +293,11 @@ If this boolean option is set to `Yes` or `On`, filtering is performed for URLs rather than for domains. The default is to filter based on domains. +Note that filtering for URLs works only in plain HTTP scenarios. +Since HTTPS has become ubiquitous during the last years, this +will only work on a tiny fraction of websites, so it is +recommended not to use this option. + =item B<FilterExtended> If this boolean option is set to `Yes`, then extended POSIX @@ -309,6 +316,8 @@ The default filtering policy is to allow everything that is not matched by a filtering rule. Setting `FilterDefaultDeny` to `Yes` changes the policy do deny everything but the domains or URLs matched by the filtering rules. +In other words, if set to `No` the Filter list acts as a +blacklist, if set to `Yes` as a whitelist. =item B<Anonymous> diff --git a/etc/tinyproxy.conf.in b/etc/tinyproxy.conf.in index 06f5480..5a170c2 100644 --- a/etc/tinyproxy.conf.in +++ b/etc/tinyproxy.conf.in @@ -43,6 +43,13 @@ Port 8888 #BindSame yes # +# BindIPv4Mapped: If enabled, tinyproxy will bind the outgoing IPv6 +# connection to the IPv4 address of the incoming connection using +# the mapping. +# +#BindIPv4Mapped 2001:0db8::ffff:0:0 + +# # Timeout: The maximum number of seconds of inactivity a connection is # allowed to have before it is closed by tinyproxy. # diff --git a/scripts/version.sh b/scripts/version.sh index f3948bc..03fb3aa 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -5,11 +5,11 @@ GIT_DIR="${SCRIPT_DIR}/../.git" if test -d "${GIT_DIR}" ; then if type git >/dev/null 2>&1 ; then - gitstr=$(git describe --match '[0-9]*.[0-9]*.[0-9]*' 2>/dev/null) + gitstr=$(git describe --match '[0-9]*.[0-9]*.*' 2>/dev/null) if test "x$?" != x0 ; then sed 's/$/-git/' < VERSION else - printf "%s\n" "$gitstr" | sed -e 's/-/-git-/' + printf "%s\n" "$gitstr" | sed -e 's/-g/-git-/' fi else sed 's/$/-git/' < VERSION diff --git a/src/Makefile.am b/src/Makefile.am index 50e645b..d132a75 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,15 +24,16 @@ AM_CPPFLAGS = \ -DLOCALSTATEDIR=\"${localstatedir}\" tinyproxy_SOURCES = \ + hostspec.c hostspec.h \ acl.c acl.h \ anonymous.c anonymous.h \ buffer.c buffer.h \ child.c child.h \ common.h \ + conf-tokens.c conf-tokens.h \ conf.c conf.h \ conns.c conns.h \ daemon.c daemon.h \ - hashmap.c hashmap.h \ heap.c heap.h \ html-error.c html-error.h \ http-message.c http-message.h \ @@ -44,12 +45,14 @@ tinyproxy_SOURCES = \ text.c text.h \ main.c main.h \ utils.c utils.h \ - vector.c vector.h \ upstream.c upstream.h \ basicauth.c basicauth.h \ base64.c base64.h \ sblist.c sblist.h \ + hsearch.c hsearch.h \ + orderedmap.c orderedmap.h \ loop.c loop.h \ + mypoll.c mypoll.h \ connect-ports.c connect-ports.h EXTRA_tinyproxy_SOURCES = filter.c filter.h \ @@ -57,3 +60,12 @@ EXTRA_tinyproxy_SOURCES = filter.c filter.h \ transparent-proxy.c transparent-proxy.h tinyproxy_DEPENDENCIES = @ADDITIONAL_OBJECTS@ tinyproxy_LDADD = @ADDITIONAL_OBJECTS@ -lpthread + +if HAVE_GPERF +conf-tokens.c: conf-tokens-gperf.inc +conf-tokens-gperf.inc: conf-tokens.gperf + $(GPERF) $< > $@ +endif + +EXTRA_DIST = conf-tokens.gperf + @@ -29,16 +29,7 @@ #include "network.h" #include "sock.h" #include "sblist.h" - -#include <limits.h> - -/* Define how long an IPv6 address is in bytes (128 bits, 16 bytes) */ -#define IPV6_LEN 16 - -enum acl_type { - ACL_STRING, - ACL_NUMERIC -}; +#include "hostspec.h" /* * Hold the information about a particular access control. We store @@ -47,66 +38,9 @@ enum acl_type { */ struct acl_s { acl_access_t access; - enum acl_type type; - union { - char *string; - struct { - unsigned char network[IPV6_LEN]; - unsigned char mask[IPV6_LEN]; - } ip; - } address; + struct hostspec h; }; -/* - * Fills in the netmask array given a numeric value. - * - * Returns: - * 0 on success - * -1 on failure (invalid mask value) - * - */ -static int -fill_netmask_array (char *bitmask_string, int v6, - unsigned char array[], size_t len) -{ - unsigned int i; - unsigned long int mask; - char *endptr; - - errno = 0; /* to distinguish success/failure after call */ - mask = strtoul (bitmask_string, &endptr, 10); - - /* check for various conversion errors */ - if ((errno == ERANGE && mask == ULONG_MAX) - || (errno != 0 && mask == 0) || (endptr == bitmask_string)) - return -1; - - if (v6 == 0) { - /* The mask comparison is done as an IPv6 address, so - * convert to a longer mask in the case of IPv4 - * addresses. */ - mask += 12 * 8; - } - - /* check valid range for a bit mask */ - if (mask > (8 * len)) - return -1; - - /* we have a valid range to fill in the array */ - for (i = 0; i != len; ++i) { - if (mask >= 8) { - array[i] = 0xff; - mask -= 8; - } else if (mask > 0) { - array[i] = (unsigned char) (0xff << (8 - mask)); - mask = 0; - } else { - array[i] = 0; - } - } - - return 0; -} /** * If the access list has not been set up, create it. @@ -138,74 +72,19 @@ int insert_acl (char *location, acl_access_t access_type, acl_list_t *access_list) { struct acl_s acl; - int ret; - char *p, ip_dst[IPV6_LEN]; assert (location != NULL); - ret = init_access_list(access_list); - if (ret != 0) { + if (init_access_list(access_list) != 0) return -1; - } /* * Start populating the access control structure. */ memset (&acl, 0, sizeof (struct acl_s)); acl.access = access_type; - - /* - * Check for a valid IP address (the simplest case) first. - */ - if (full_inet_pton (location, ip_dst) > 0) { - acl.type = ACL_NUMERIC; - memcpy (acl.address.ip.network, ip_dst, IPV6_LEN); - memset (acl.address.ip.mask, 0xff, IPV6_LEN); - } else { - int i; - - /* - * At this point we're either a hostname or an - * IP address with a slash. - */ - p = strchr (location, '/'); - if (p != NULL) { - char dst[sizeof(struct in6_addr)]; - int v6; - - /* - * We have a slash, so it's intended to be an - * IP address with mask - */ - *p = '\0'; - if (full_inet_pton (location, ip_dst) <= 0) - return -1; - - acl.type = ACL_NUMERIC; - - /* Check if the IP address before the netmask is - * an IPv6 address */ - if (inet_pton(AF_INET6, location, dst) > 0) - v6 = 1; - else - v6 = 0; - - if (fill_netmask_array - (p + 1, v6, &(acl.address.ip.mask[0]), IPV6_LEN) - < 0) - return -1; - - for (i = 0; i < IPV6_LEN; i++) - acl.address.ip.network[i] = ip_dst[i] & - acl.address.ip.mask[i]; - } else { - /* In all likelihood a string */ - acl.type = ACL_STRING; - acl.address.string = safestrdup (location); - if (!acl.address.string) - return -1; - } - } + if(hostspec_parse(location, &acl.h) || acl.h.type == HST_NONE) + return -1; if(!sblist_add(*access_list, &acl)) return -1; return 0; @@ -229,7 +108,7 @@ acl_string_processing (struct acl_s *acl, const char *ip_address, size_t test_length, match_length; char ipbuf[512]; - assert (acl && acl->type == ACL_STRING); + assert (acl && acl->h.type == HST_STRING); assert (ip_address && strlen (ip_address) > 0); /* @@ -237,11 +116,11 @@ acl_string_processing (struct acl_s *acl, const char *ip_address, * do a string based test only; otherwise, we can do a reverse * lookup test as well. */ - if (acl->address.string[0] != '.') { + if (acl->h.address.string[0] != '.') { memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - if (getaddrinfo (acl->address.string, NULL, &hints, &res) != 0) + if (getaddrinfo (acl->h.address.string, NULL, &hints, &res) != 0) goto STRING_TEST; ressave = res; @@ -275,7 +154,7 @@ STRING_TEST: } test_length = strlen (string_addr); - match_length = strlen (acl->address.string); + match_length = strlen (acl->h.address.string); /* * If the string length is shorter than AC string, return a -1 so @@ -286,7 +165,7 @@ STRING_TEST: if (strcasecmp (string_addr + (test_length - match_length), - acl->address.string) == 0) { + acl->h.address.string) == 0) { if (acl->access == ACL_DENY) return 0; else @@ -310,11 +189,11 @@ static int check_numeric_acl (const struct acl_s *acl, uint8_t addr[IPV6_LEN]) uint8_t x, y; int i; - assert (acl && acl->type == ACL_NUMERIC); + assert (acl && acl->h.type == HST_NUMERIC); for (i = 0; i != IPV6_LEN; ++i) { - x = addr[i] & acl->address.ip.mask[i]; - y = acl->address.ip.network[i]; + x = addr[i] & acl->h.address.ip.mask[i]; + y = acl->h.address.ip.network[i]; /* If x and y don't match, the IP addresses don't match */ if (x != y) @@ -355,12 +234,12 @@ int check_acl (const char *ip, union sockaddr_union *addr, acl_list_t access_lis for (i = 0; i < sblist_getsize (access_list); ++i) { acl = sblist_get (access_list, i); - switch (acl->type) { - case ACL_STRING: + switch (acl->h.type) { + case HST_STRING: perm = acl_string_processing (acl, ip, addr, string_addr); break; - case ACL_NUMERIC: + case HST_NUMERIC: if (ip[0] == '\0') continue; @@ -368,6 +247,10 @@ int check_acl (const char *ip, union sockaddr_union *addr, acl_list_t access_lis ? check_numeric_acl (acl, numeric_addr) : -1; break; + + case HST_NONE: + perm = -1; + break; } /* @@ -404,8 +287,8 @@ void flush_access_list (acl_list_t access_list) */ for (i = 0; i < sblist_getsize (access_list); ++i) { acl = sblist_get (access_list, i); - if (acl->type == ACL_STRING) { - safefree (acl->address.string); + if (acl->h.type == HST_STRING) { + safefree (acl->h.address.string); } } diff --git a/src/anonymous.c b/src/anonymous.c index f38fd44..91e490c 100644 --- a/src/anonymous.c +++ b/src/anonymous.c @@ -23,7 +23,7 @@ #include "main.h" #include "anonymous.h" -#include "hashmap.h" +#include "hsearch.h" #include "heap.h" #include "log.h" #include "conf.h" @@ -42,7 +42,7 @@ int anonymous_search (struct config_s *conf, const char *s) assert (s != NULL); assert (conf->anonymous_map != NULL); - return hashmap_search (conf->anonymous_map, s); + return !!htab_find (conf->anonymous_map, s); } /* @@ -51,23 +51,21 @@ int anonymous_search (struct config_s *conf, const char *s) * Return -1 if there is an error, otherwise a 0 is returned if the insert was * successful. */ -int anonymous_insert (struct config_s *conf, const char *s) +int anonymous_insert (struct config_s *conf, char *s) { - char data = 1; - assert (s != NULL); if (!conf->anonymous_map) { - conf->anonymous_map = hashmap_create (32); + conf->anonymous_map = htab_create (32); if (!conf->anonymous_map) return -1; } - if (hashmap_search (conf->anonymous_map, s) > 0) { - /* The key was already found, so return a positive number. */ + if (htab_find (conf->anonymous_map, s)) { + /* The key was already found. */ return 0; } /* Insert the new key */ - return hashmap_insert (conf->anonymous_map, s, &data, sizeof (data)); + return htab_insert (conf->anonymous_map, s, HTV_N(1)) ? 0 : -1; } diff --git a/src/anonymous.h b/src/anonymous.h index 6fc4518..78ce771 100644 --- a/src/anonymous.h +++ b/src/anonymous.h @@ -23,6 +23,6 @@ extern short int is_anonymous_enabled (struct config_s *conf); extern int anonymous_search (struct config_s *conf, const char *s); -extern int anonymous_insert (struct config_s *conf, const char *s); +extern int anonymous_insert (struct config_s *conf, char *s); #endif diff --git a/src/basicauth.c b/src/basicauth.c index d6c2420..ed0553b 100644 --- a/src/basicauth.c +++ b/src/basicauth.c @@ -48,10 +48,10 @@ ssize_t basicauth_string(const char *user, const char *pass, /* * Add entry to the basicauth list */ -void basicauth_add (vector_t authlist, +void basicauth_add (sblist *authlist, const char *user, const char *pass) { - char b[BASE64ENC_BYTES((256+2)-1) + 1]; + char b[BASE64ENC_BYTES((256+2)-1) + 1], *s; ssize_t ret; ret = basicauth_string(user, pass, b, sizeof b); @@ -65,7 +65,8 @@ void basicauth_add (vector_t authlist, return; } - if (vector_append(authlist, b, ret + 1) == -ENOMEM) { + if (!(s = safestrdup(b)) || !sblist_add(authlist, &s)) { + safefree(s); log_message (LOG_ERR, "Unable to allocate memory in basicauth_add()"); return; @@ -80,18 +81,16 @@ void basicauth_add (vector_t authlist, * is in the basicauth list. * return 1 on success, 0 on failure. */ -int basicauth_check (vector_t authlist, const char *authstring) +int basicauth_check (sblist *authlist, const char *authstring) { - ssize_t vl, i; - size_t el; - const char* entry; + size_t i; + char** entry; - vl = vector_length (authlist); - if (vl == -EINVAL) return 0; + if (!authlist) return 0; - for (i = 0; i < vl; i++) { - entry = vector_getentry (authlist, i, &el); - if (strcmp (authstring, entry) == 0) + for (i = 0; i < sblist_getsize(authlist); i++) { + entry = sblist_get (authlist, i); + if (entry && strcmp (authstring, *entry) == 0) return 1; } return 0; diff --git a/src/basicauth.h b/src/basicauth.h index 61dc5c3..ef25b66 100644 --- a/src/basicauth.h +++ b/src/basicauth.h @@ -22,14 +22,14 @@ #define TINYPROXY_BASICAUTH_H #include <stddef.h> -#include "vector.h" +#include "sblist.h" extern ssize_t basicauth_string(const char *user, const char *pass, char *buf, size_t bufsize); -extern void basicauth_add (vector_t authlist, +extern void basicauth_add (sblist *authlist, const char *user, const char *pass); -extern int basicauth_check (vector_t authlist, const char *authstring); +extern int basicauth_check (sblist *authlist, const char *authstring); #endif diff --git a/src/child.c b/src/child.c index 15d4fe8..985357d 100644 --- a/src/child.c +++ b/src/child.c @@ -33,25 +33,27 @@ #include "conf.h" #include "sblist.h" #include "loop.h" +#include "conns.h" +#include "mypoll.h" #include <pthread.h> -static vector_t listen_fds; +static sblist* listen_fds; struct client { union sockaddr_union addr; - int fd; }; struct child { pthread_t thread; struct client client; + struct conn_s conn; volatile int done; }; static void* child_thread(void* data) { struct child *c = data; - handle_connection (c->client.fd, &c->client.addr); + handle_connection (&c->conn, &c->client.addr); c->done = 1; return NULL; } @@ -80,19 +82,24 @@ void child_main_loop (void) union sockaddr_union cliaddr_storage; struct sockaddr *cliaddr = (void*) &cliaddr_storage; socklen_t clilen = sizeof(cliaddr_storage); - fd_set rfds; + int nfds = sblist_getsize(listen_fds); + pollfd_struct *fds = safecalloc(nfds, sizeof *fds); ssize_t i; - int ret, listenfd, maxfd, was_full = 0; + int ret, listenfd, was_full = 0; pthread_attr_t *attrp, attr; struct child *child; childs = sblist_new(sizeof (struct child*), config->maxclients); - loop_records_init(); + for (i = 0; i < nfds; i++) { + int *fd = sblist_get(listen_fds, i); + fds[i].fd = *fd; + fds[i].events |= MYPOLL_READ; + } /* * We have to wait for connections on multiple fds, - * so use select. + * so use select/poll/whatever. */ while (!config->quit) { @@ -110,7 +117,6 @@ void child_main_loop (void) was_full = 0; listenfd = -1; - maxfd = 0; /* Handle log rotation if it was requested */ if (received_sighup) { @@ -124,62 +130,35 @@ void child_main_loop (void) received_sighup = FALSE; } + ret = mypoll(fds, nfds, -1); - FD_ZERO(&rfds); - - for (i = 0; i < vector_length(listen_fds); i++) { - int *fd = (int *) vector_getentry(listen_fds, i, NULL); - - ret = socket_nonblocking(*fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the listening " - "socket %d to non-blocking: %s", - fd, strerror(errno)); - continue; - } - - FD_SET(*fd, &rfds); - maxfd = max(maxfd, *fd); - } - - ret = select(maxfd + 1, &rfds, NULL, NULL, NULL); if (ret == -1) { if (errno == EINTR) { continue; } - log_message (LOG_ERR, "error calling select: %s", + log_message (LOG_ERR, "error calling " SELECT_OR_POLL ": %s", strerror(errno)); continue; } else if (ret == 0) { - log_message (LOG_WARNING, "Strange: select returned 0 " + log_message (LOG_WARNING, "Strange: " SELECT_OR_POLL " returned 0 " "but we did not specify a timeout..."); continue; } - for (i = 0; i < vector_length(listen_fds); i++) { - int *fd = (int *) vector_getentry(listen_fds, i, NULL); - - if (FD_ISSET(*fd, &rfds)) { + for (i = 0; i < nfds; i++) { + if (fds[i].revents & MYPOLL_READ) { /* * only accept the connection on the first * fd that we find readable. - fair? */ - listenfd = *fd; + listenfd = fds[i].fd; break; } } if (listenfd == -1) { log_message(LOG_WARNING, "Strange: None of our listen " - "fds was readable after select"); - continue; - } - - ret = socket_blocking(listenfd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set listening " - "socket %d to blocking for accept: %s", - listenfd, strerror(errno)); + "fds was readable after " SELECT_OR_POLL); continue; } @@ -201,7 +180,7 @@ void child_main_loop (void) continue; } - child = safemalloc(sizeof(struct child)); + child = safecalloc(1, sizeof(struct child)); if (!child) { oom: close(connfd); @@ -218,7 +197,9 @@ oom: goto oom; } - child->client.fd = connfd; + conn_struct_init(&child->conn); + child->conn.client_fd = connfd; + memcpy(&child->client.addr, &cliaddr_storage, sizeof(cliaddr_storage)); attrp = 0; @@ -233,6 +214,7 @@ oom: goto oom; } } + safefree(fds); } /* @@ -240,40 +222,48 @@ oom: */ void child_kill_children (int sig) { - size_t i; + size_t i, tries = 0; if (sig != SIGTERM) return; + log_message (LOG_INFO, + "trying to bring down %zu threads...", + sblist_getsize(childs) + ); + +again: for (i = 0; i < sblist_getsize(childs); i++) { struct child *c = *((struct child**)sblist_get(childs, i)); - if (!c->done) { - /* interrupt blocking operations. - this should cause the threads to shutdown orderly. */ - close(c->client.fd); - } + if (!c->done) pthread_kill(c->thread, SIGCHLD); } - usleep(16); + usleep(8192); collect_threads(); if (sblist_getsize(childs) != 0) + if(tries++ < 8) goto again; + if (sblist_getsize(childs) != 0) log_message (LOG_CRIT, "child_kill_children: %zu threads still alive!", sblist_getsize(childs) ); } +void child_free_children(void) { + sblist_free(childs); + childs = 0; +} /** * Listen on the various configured interfaces */ -int child_listening_sockets(vector_t listen_addrs, uint16_t port) +int child_listening_sockets(sblist *listen_addrs, uint16_t port) { int ret; - ssize_t i; + size_t i; assert (port > 0); if (listen_fds == NULL) { - listen_fds = vector_create(); + listen_fds = sblist_new(sizeof(int), 16); if (listen_fds == NULL) { log_message (LOG_ERR, "Could not create the list " "of listening fds"); @@ -281,8 +271,7 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port) } } - if ((listen_addrs == NULL) || - (vector_length(listen_addrs) == 0)) + if (!listen_addrs || !sblist_getsize(listen_addrs)) { /* * no Listen directive: @@ -292,17 +281,17 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port) return ret; } - for (i = 0; i < vector_length(listen_addrs); i++) { - const char *addr; + for (i = 0; i < sblist_getsize(listen_addrs); i++) { + char **addr; - addr = (char *)vector_getentry(listen_addrs, i, NULL); - if (addr == NULL) { + addr = sblist_get(listen_addrs, i); + if (!addr || !*addr) { log_message(LOG_WARNING, "got NULL from listen_addrs - skipping"); continue; } - ret = listen_sock(addr, port, listen_fds); + ret = listen_sock(*addr, port, listen_fds); if (ret != 0) { return ret; } @@ -313,14 +302,14 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port) void child_close_sock (void) { - ssize_t i; + size_t i; - for (i = 0; i < vector_length(listen_fds); i++) { - int *fd = (int *) vector_getentry(listen_fds, i, NULL); + for (i = 0; i < sblist_getsize(listen_fds); i++) { + int *fd = sblist_get(listen_fds, i); close (*fd); } - vector_delete(listen_fds); + sblist_free(listen_fds); listen_fds = NULL; } diff --git a/src/child.h b/src/child.h index 70f52b7..ffcd9d0 100644 --- a/src/child.h +++ b/src/child.h @@ -21,7 +21,7 @@ #ifndef TINYPROXY_CHILD_H #define TINYPROXY_CHILD_H -#include "vector.h" +#include "sblist.h" typedef enum { CHILD_MAXCLIENTS, @@ -32,10 +32,11 @@ typedef enum { } child_config_t; extern short int child_pool_create (void); -extern int child_listening_sockets (vector_t listen_addrs, uint16_t port); +extern int child_listening_sockets (sblist *listen_addrs, uint16_t port); extern void child_close_sock (void); extern void child_main_loop (void); extern void child_kill_children (int sig); +extern void child_free_children(void); extern short int child_configure (child_config_t type, unsigned int val); diff --git a/src/common.h b/src/common.h index 47a1ed1..a492582 100644 --- a/src/common.h +++ b/src/common.h @@ -68,7 +68,7 @@ # include <arpa/inet.h> # include <grp.h> # include <pwd.h> -# include <regex.h> +# include <limits.h> /* rest - some oddball headers */ #ifdef HAVE_VALUES_H diff --git a/src/conf-tokens.c b/src/conf-tokens.c new file mode 100644 index 0000000..edfcedf --- /dev/null +++ b/src/conf-tokens.c @@ -0,0 +1,72 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <stdlib.h> +#include "conf-tokens.h" + +#ifdef HAVE_GPERF +#include "conf-tokens-gperf.inc" +#else + +#include <strings.h> + +const struct config_directive_entry * +config_directive_find (register const char *str, register size_t len) +{ + size_t i; + static const struct config_directive_entry wordlist[] = + { + {"",CD_NIL}, {"",CD_NIL}, + {"allow", CD_allow}, + {"stathost", CD_stathost}, + {"listen", CD_listen}, + {"timeout", CD_timeout}, + {"statfile", CD_statfile}, + {"pidfile", CD_pidfile}, + {"bindsame", CD_bindsame}, + {"reversebaseurl", CD_reversebaseurl}, + {"viaproxyname", CD_viaproxyname}, + {"upstream", CD_upstream}, + {"anonymous", CD_anonymous}, + {"group", CD_group}, + {"defaulterrorfile", CD_defaulterrorfile}, + {"startservers", CD_startservers}, + {"filtercasesensitive", CD_filtercasesensitive}, + {"filterurls", CD_filterurls}, + {"filter", CD_filter}, + {"reversemagic", CD_reversemagic}, + {"errorfile", CD_errorfile}, + {"minspareservers", CD_minspareservers}, + {"user", CD_user}, + {"disableviaheader", CD_disableviaheader}, + {"deny", CD_deny}, + {"xtinyproxy", CD_xtinyproxy}, + {"reversepath", CD_reversepath}, + {"bind", CD_bind}, + {"maxclients", CD_maxclients}, + {"reverseonly", CD_reverseonly}, + {"port", CD_port}, + {"maxspareservers", CD_maxspareservers}, + {"syslog", CD_syslog}, + {"filterdefaultdeny", CD_filterdefaultdeny}, + {"loglevel", CD_loglevel}, + {"filterextended", CD_filterextended}, + {"connectport", CD_connectport}, + {"logfile", CD_logfile}, + {"basicauth", CD_basicauth}, + {"addheader", CD_addheader}, + {"maxrequestsperchild", CD_maxrequestsperchild}, + {"bindipv4mapped", CD_bindipv4mapped}, + {"bindipv6mapped", CD_bindipv6mapped}, + }; + + for(i=0;i<sizeof(wordlist)/sizeof(wordlist[0]);++i) { + if(!strcasecmp(str, wordlist[i].name)) + return &wordlist[i]; + } + return 0; +} + +#endif diff --git a/src/conf-tokens.gperf b/src/conf-tokens.gperf new file mode 100644 index 0000000..ef93245 --- /dev/null +++ b/src/conf-tokens.gperf @@ -0,0 +1,61 @@ +%{ +#include <string.h> +#include <stdlib.h> +#include "conf-tokens.h" +%} + +struct config_directive_entry { const char* name; enum config_directive value; }; + +%struct-type +%define slot-name name +%define initializer-suffix ,CD_NIL +%define lookup-function-name config_directive_find +%ignore-case +%7bit +%compare-lengths +%readonly-tables +%define constants-prefix CDS_ +%omit-struct-type + +%% +logfile, CD_logfile +pidfile, CD_pidfile +anonymous, CD_anonymous +viaproxyname, CD_viaproxyname +defaulterrorfile, CD_defaulterrorfile +statfile, CD_statfile +stathost, CD_stathost +xtinyproxy, CD_xtinyproxy +syslog, CD_syslog +bindsame, CD_bindsame +disableviaheader, CD_disableviaheader +port, CD_port +maxclients, CD_maxclients +maxspareservers, CD_maxspareservers +minspareservers, CD_minspareservers +startservers, CD_startservers +maxrequestsperchild, CD_maxrequestsperchild +timeout, CD_timeout +connectport, CD_connectport +user, CD_user +group, CD_group +listen, CD_listen +allow, CD_allow +deny, CD_deny +bind, CD_bind +basicauth, CD_basicauth +errorfile, CD_errorfile +addheader, CD_addheader +filter, CD_filter +filterurls, CD_filterurls +filterextended, CD_filterextended +filterdefaultdeny, CD_filterdefaultdeny +filtercasesensitive, CD_filtercasesensitive +reversebaseurl, CD_reversebaseurl +reverseonly, CD_reverseonly +reversemagic, CD_reversemagic +reversepath, CD_reversepath +upstream, CD_upstream +loglevel, CD_loglevel +%% + diff --git a/src/conf-tokens.h b/src/conf-tokens.h new file mode 100644 index 0000000..e99f0de --- /dev/null +++ b/src/conf-tokens.h @@ -0,0 +1,55 @@ +#ifndef CONF_TOKENS_H +#define CONF_TOKENS_H + +enum config_directive { +CD_NIL = 0, +CD_logfile, +CD_pidfile, +CD_anonymous, +CD_viaproxyname, +CD_defaulterrorfile, +CD_statfile, +CD_stathost, +CD_xtinyproxy, +CD_syslog, +CD_bindsame, +CD_disableviaheader, +CD_port, +CD_maxclients, +CD_maxspareservers, +CD_minspareservers, +CD_startservers, +CD_maxrequestsperchild, +CD_timeout, +CD_connectport, +CD_user, +CD_group, +CD_listen, +CD_allow, +CD_deny, +CD_bind, +CD_basicauth, +CD_errorfile, +CD_addheader, +CD_filter, +CD_filterurls, +CD_filterextended, +CD_filterdefaultdeny, +CD_filtercasesensitive, +CD_reversebaseurl, +CD_reverseonly, +CD_reversemagic, +CD_reversepath, +CD_upstream, +CD_loglevel, +CD_bindipv4mapped, +CD_bindipv6mapped, +}; + +struct config_directive_entry { const char* name; enum config_directive value; }; + +const struct config_directive_entry * +config_directive_find (register const char *str, register size_t len); + +#endif + @@ -23,6 +23,8 @@ * add new directives to. Who knows if I'm right though. */ +#include "common.h" +#include <regex.h> #include "conf.h" #include "acl.h" @@ -36,6 +38,7 @@ #include "upstream.h" #include "connect-ports.h" #include "basicauth.h" +#include "conf-tokens.h" /* * The configuration directives are defined in the structure below. Each @@ -46,46 +49,35 @@ * can (and likely should) be used when building the regex for the * given directive. */ -#define WS "[[:space:]]+" +#define DIGIT "[0-9]" +#define SPACE "[ \t]" +#define WS SPACE "+" #define STR "\"([^\"]+)\"" #define BOOL "(yes|on|no|off)" -#define INT "((0x)?[[:digit:]]+)" +#define INT "(()" DIGIT "+)" #define ALNUM "([-a-z0-9._]+)" #define USERNAME "([^:]*)" #define PASSWORD "([^@]*)" #define IP "((([0-9]{1,3})\\.){3}[0-9]{1,3})" -#define IPMASK "(" IP "(/[[:digit:]]+)?)" +#define IPMASK "(" IP "(/" DIGIT "+)?)" #define IPV6 "(" \ - "(([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,6})|" \ - "(([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5})|" \ - "(([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4})|" \ - "(([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3})|" \ - "(([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2})|" \ - "(([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4}){1,1})|" \ - "((([0-9a-f]{1,4}:){1,7}|:):)|" \ - "(:(:[0-9a-f]{1,4}){1,7})|" \ - "([0-9a-f]{1,4}(:[0-9a-f]{1,4}){1,7})|" \ - "(((([0-9a-f]{1,4}:){6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}))|" \ - "((([0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}))|" \ - "(([0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,3}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,2}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,1}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "((([0-9a-f]{1,4}:){1,5}|:):(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(:(:[0-9a-f]{1,4}){1,5}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})" \ + "(([0-9a-f:]{2,39}))|" \ + "(([0-9a-f:]{0,29}:" IP "))" \ ")" -#define IPV6MASK "(" IPV6 "(/[[:digit:]]+)?)" -#define BEGIN "^[[:space:]]*" -#define END "[[:space:]]*$" +#define IPV6MASK "(" IPV6 "(/" DIGIT "+)?)" +#define BEGIN "^" SPACE "*" +#define END SPACE "*$" /* * Limit the maximum number of substring matches to a reasonably high * number. Given the usual structure of the configuration file, sixteen * substring matches should be plenty. */ -#define RE_MAX_MATCHES 16 +#define RE_MAX_MATCHES 24 + +#define CP_WARN(FMT, ...) \ + log_message (LOG_WARNING, "line %lu: " FMT, lineno, __VA_ARGS__) /* * All configuration handling functions are REQUIRED to be defined @@ -113,15 +105,20 @@ typedef int (*CONFFILE_HANDLER) (struct config_s *, const char *, * List all the handling functions. These are defined later, but they need * to be in-scope before the big structure below. */ -static HANDLE_FUNC (handle_nop) +static HANDLE_FUNC (handle_disabled_feature) { - return 0; -} /* do nothing function */ + fprintf (stderr, "ERROR: accessing feature that was disabled at compiletime on line %lu\n", + lineno); + + return -1; +} static HANDLE_FUNC (handle_allow); static HANDLE_FUNC (handle_basicauth); static HANDLE_FUNC (handle_anonymous); static HANDLE_FUNC (handle_bind); +static HANDLE_FUNC (handle_bindipv4mapped); +static HANDLE_FUNC (handle_bindipv6mapped); static HANDLE_FUNC (handle_bindsame); static HANDLE_FUNC (handle_connectport); static HANDLE_FUNC (handle_defaulterrorfile); @@ -161,7 +158,6 @@ static HANDLE_FUNC (handle_xtinyproxy); #ifdef UPSTREAM_SUPPORT static HANDLE_FUNC (handle_upstream); -static HANDLE_FUNC (handle_upstream_no); #endif static void config_free_regex (void); @@ -177,7 +173,7 @@ static void config_free_regex (void); * do not follow the pattern above. This macro is for convenience * only. */ -#define STDCONF(d, re, func) { BEGIN "(" d ")" WS re END, func, NULL } +#define STDCONF(d, re, func) [CD_ ## d] = { BEGIN "()" WS re END, func, NULL } /* * Holds the regular expression used to match the configuration directive, @@ -190,109 +186,118 @@ struct { CONFFILE_HANDLER handler; regex_t *cre; } directives[] = { - /* comments */ - { - BEGIN "#", handle_nop, NULL - }, - /* blank lines */ - { - "^[[:space:]]+$", handle_nop, NULL - }, /* string arguments */ - STDCONF ("logfile", STR, handle_logfile), - STDCONF ("pidfile", STR, handle_pidfile), - STDCONF ("anonymous", STR, handle_anonymous), - STDCONF ("viaproxyname", STR, handle_viaproxyname), - STDCONF ("defaulterrorfile", STR, handle_defaulterrorfile), - STDCONF ("statfile", STR, handle_statfile), - STDCONF ("stathost", STR, handle_stathost), - STDCONF ("xtinyproxy", BOOL, handle_xtinyproxy), + STDCONF (logfile, STR, handle_logfile), + STDCONF (pidfile, STR, handle_pidfile), + STDCONF (anonymous, STR, handle_anonymous), + STDCONF (viaproxyname, STR, handle_viaproxyname), + STDCONF (defaulterrorfile, STR, handle_defaulterrorfile), + STDCONF (statfile, STR, handle_statfile), + STDCONF (stathost, STR, handle_stathost), + STDCONF (xtinyproxy, BOOL, handle_xtinyproxy), /* boolean arguments */ - STDCONF ("syslog", BOOL, handle_syslog), - STDCONF ("bindsame", BOOL, handle_bindsame), - STDCONF ("disableviaheader", BOOL, handle_disableviaheader), + STDCONF (syslog, BOOL, handle_syslog), + STDCONF (bindsame, BOOL, handle_bindsame), + STDCONF (disableviaheader, BOOL, handle_disableviaheader), /* integer arguments */ - STDCONF ("port", INT, handle_port), - STDCONF ("maxclients", INT, handle_maxclients), - STDCONF ("maxspareservers", INT, handle_obsolete), - STDCONF ("minspareservers", INT, handle_obsolete), - STDCONF ("startservers", INT, handle_obsolete), - STDCONF ("maxrequestsperchild", INT, handle_obsolete), - STDCONF ("timeout", INT, handle_timeout), - STDCONF ("connectport", INT, handle_connectport), + STDCONF (port, INT, handle_port), + STDCONF (maxclients, INT, handle_maxclients), + STDCONF (maxspareservers, INT, handle_obsolete), + STDCONF (minspareservers, INT, handle_obsolete), + STDCONF (startservers, INT, handle_obsolete), + STDCONF (maxrequestsperchild, INT, handle_obsolete), + STDCONF (timeout, INT, handle_timeout), + STDCONF (connectport, INT, handle_connectport), /* alphanumeric arguments */ - STDCONF ("user", ALNUM, handle_user), - STDCONF ("group", ALNUM, handle_group), + STDCONF (user, ALNUM, handle_user), + STDCONF (group, ALNUM, handle_group), /* ip arguments */ - STDCONF ("listen", "(" IP "|" IPV6 ")", handle_listen), - STDCONF ("allow", "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", + STDCONF (listen, "(" IP "|" IPV6 ")", handle_listen), + STDCONF (allow, "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", handle_allow), - STDCONF ("deny", "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", + STDCONF (deny, "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", handle_deny), - STDCONF ("bind", "(" IP "|" IPV6 ")", handle_bind), + STDCONF (bind, "(" IP "|" IPV6 ")", handle_bind), + STDCONF (bindipv4mapped, "(" IPV6 ")", handle_bindipv4mapped), + STDCONF (bindipv6mapped, "(" IP ")", handle_bindipv6mapped), /* other */ - STDCONF ("basicauth", ALNUM WS ALNUM, handle_basicauth), - STDCONF ("errorfile", INT WS STR, handle_errorfile), - STDCONF ("addheader", STR WS STR, handle_addheader), + STDCONF (basicauth, ALNUM WS ALNUM, handle_basicauth), + STDCONF (errorfile, INT WS STR, handle_errorfile), + STDCONF (addheader, STR WS STR, handle_addheader), #ifdef FILTER_ENABLE /* filtering */ - STDCONF ("filter", STR, handle_filter), - STDCONF ("filterurls", BOOL, handle_filterurls), - STDCONF ("filterextended", BOOL, handle_filterextended), - STDCONF ("filterdefaultdeny", BOOL, handle_filterdefaultdeny), - STDCONF ("filtercasesensitive", BOOL, handle_filtercasesensitive), + STDCONF (filter, STR, handle_filter), + STDCONF (filterurls, BOOL, handle_filterurls), + STDCONF (filterextended, BOOL, handle_filterextended), + STDCONF (filterdefaultdeny, BOOL, handle_filterdefaultdeny), + STDCONF (filtercasesensitive, BOOL, handle_filtercasesensitive), #endif #ifdef REVERSE_SUPPORT /* Reverse proxy arguments */ - STDCONF ("reversebaseurl", STR, handle_reversebaseurl), - STDCONF ("reverseonly", BOOL, handle_reverseonly), - STDCONF ("reversemagic", BOOL, handle_reversemagic), - STDCONF ("reversepath", STR "(" WS STR ")?", handle_reversepath), + STDCONF (reversebaseurl, STR, handle_reversebaseurl), + STDCONF (reverseonly, BOOL, handle_reverseonly), + STDCONF (reversemagic, BOOL, handle_reversemagic), + STDCONF (reversepath, STR "(" WS STR ")?", handle_reversepath), #endif #ifdef UPSTREAM_SUPPORT - { - BEGIN "(upstream)" WS "(none)" WS STR END, handle_upstream_no, NULL - }, - { - BEGIN "(upstream)" WS "(http|socks4|socks5)" WS - "(" USERNAME /*username*/ ":" PASSWORD /*password*/ "@" ")?" - "(" IP "|" ALNUM ")" - ":" INT "(" WS STR ")?" - END, handle_upstream, NULL - }, + STDCONF (upstream, + "(" "(none)" WS STR ")|" \ + "(" "(http|socks4|socks5)" WS \ + "(" USERNAME /*username*/ ":" PASSWORD /*password*/ "@" ")?" + "(" IP "|" ALNUM ")" + ":" INT "(" WS STR ")?" ")", handle_upstream), #endif /* loglevel */ - STDCONF ("loglevel", "(critical|error|warning|notice|connect|info)", + STDCONF (loglevel, "(critical|error|warning|notice|connect|info)", handle_loglevel) }; const unsigned int ndirectives = sizeof (directives) / sizeof (directives[0]); static void -free_added_headers (vector_t add_headers) +free_added_headers (sblist* add_headers) { - ssize_t i; + size_t i; - for (i = 0; i < vector_length (add_headers); i++) { - http_header_t *header = (http_header_t *) - vector_getentry (add_headers, i, NULL); + if (!add_headers) return; + + for (i = 0; i < sblist_getsize (add_headers); i++) { + http_header_t *header = sblist_get (add_headers, i); safefree (header->name); safefree (header->value); } - vector_delete (add_headers); + sblist_free (add_headers); +} + +static void stringlist_free(sblist *sl) { + size_t i; + char **s; + if(sl) { + for(i = 0; i < sblist_getsize(sl); i++) { + s = sblist_get(sl, i); + if(s) safefree(*s); + } + sblist_free(sl); + } } -static void free_config (struct config_s *conf) +void free_config (struct config_s *conf) { + char *k; + htab_value *v; + size_t it; safefree (conf->logf_name); safefree (conf->stathost); safefree (conf->user); safefree (conf->group); - vector_delete(conf->listen_addrs); - vector_delete(conf->basicauth_list); + safefree (conf->bind_ipv4mapped); + safefree (conf->bind_ipv6mapped); + stringlist_free(conf->basicauth_list); + stringlist_free(conf->listen_addrs); + stringlist_free(conf->bind_addrs); #ifdef FILTER_ENABLE safefree (conf->filter); #endif /* FILTER_ENABLE */ @@ -304,34 +309,50 @@ static void free_config (struct config_s *conf) free_upstream_list (conf->upstream_list); #endif /* UPSTREAM_SUPPORT */ safefree (conf->pidpath); - safefree (conf->bind_address); safefree (conf->via_proxy_name); - hashmap_delete (conf->errorpages); + if (conf->errorpages) { + it = 0; + while((it = htab_next(conf->errorpages, it, &k, &v))) { + safefree(k); + safefree(v->p); + } + htab_destroy (conf->errorpages); + } free_added_headers (conf->add_headers); safefree (conf->errorpage_undef); safefree (conf->statpage); flush_access_list (conf->access_list); free_connect_ports_list (conf->connect_ports); - hashmap_delete (conf->anonymous_map); + if (conf->anonymous_map) { + it = 0; + while((it = htab_next(conf->anonymous_map, it, &k, &v))) + safefree(k); + htab_destroy (conf->anonymous_map); + } memset (conf, 0, sizeof(*conf)); } /* + * Initializes Config parser. Currently this means: * Compiles the regular expressions used by the configuration file. This * routine MUST be called before trying to parse the configuration file. * * Returns 0 on success; negative upon failure. */ int -config_compile_regex (void) +config_init (void) { unsigned int i, r; for (i = 0; i != ndirectives; ++i) { - assert (directives[i].handler); assert (!directives[i].cre); + if (!directives[i].handler) { + directives[i].handler = handle_disabled_feature; + continue; + } + directives[i].cre = (regex_t *) safemalloc (sizeof (regex_t)); if (!directives[i].cre) return -1; @@ -375,20 +396,17 @@ config_free_regex (void) * a negative number is returned. */ static int check_match (struct config_s *conf, const char *line, - unsigned long lineno) + unsigned long lineno, enum config_directive cd) { regmatch_t match[RE_MAX_MATCHES]; - unsigned int i; + unsigned int i = cd; - assert (ndirectives > 0); - - for (i = 0; i != ndirectives; ++i) { - assert (directives[i].cre); - if (!regexec - (directives[i].cre, line, RE_MAX_MATCHES, match, 0)) - return (*directives[i].handler) (conf, line, lineno, match); - } + if (!directives[i].cre) + return (*directives[i].handler) (conf, line, lineno, match); + if (!regexec + (directives[i].cre, line, RE_MAX_MATCHES, match, 0)) + return (*directives[i].handler) (conf, line, lineno, match); return -1; } @@ -397,15 +415,25 @@ static int check_match (struct config_s *conf, const char *line, */ static int config_parse (struct config_s *conf, FILE * f) { - char buffer[LINE_MAX]; + char buffer[LINE_MAX], *p, *q, c; + const struct config_directive_entry *e; unsigned long lineno = 1; - while (fgets (buffer, sizeof (buffer), f)) { - if (check_match (conf, buffer, lineno)) { - printf ("Syntax error on line %ld\n", lineno); + for (;fgets (buffer, sizeof (buffer), f);++lineno) { + if(buffer[0] == '#') continue; + p = buffer; + while(isspace(*p))p++; + if(!*p) continue; + q = p; + while(!isspace(*q))q++; + c = *q; + *q = 0; + e = config_directive_find(p, strlen(p)); + *q = c; + if (!e || e->value == CD_NIL || check_match (conf, q, lineno, e->value)) { + fprintf (stderr, "ERROR: Syntax error on line %lu\n", lineno); return 1; } - ++lineno; } return 0; } @@ -464,10 +492,6 @@ int reload_config_file (const char *config_fname, struct config_s *conf) { int ret; - log_message (LOG_NOTICE, "Reloading config file"); - - free_config (conf); - initialize_config_defaults (conf); ret = load_config_file (config_fname, conf); @@ -487,7 +511,7 @@ int reload_config_file (const char *config_fname, struct config_s *conf) goto done; } - if (!conf->user) { + if (!conf->user && !geteuid()) { log_message (LOG_WARNING, "You SHOULD set a UserName in the " "config file. Using current user instead."); } @@ -624,8 +648,12 @@ static HANDLE_FUNC (handle_anonymous) if (!arg) return -1; - anonymous_insert (conf, arg); - safefree (arg); + if(anonymous_insert (conf, arg) < 0) { + CP_WARN ("anonymous_insert() failed: '%s'", arg); + safefree(arg); + return -1; + } + return 0; } @@ -748,11 +776,16 @@ static HANDLE_FUNC (handle_group) return set_string_arg (&conf->group, line, &match[2]); } +static void warn_invalid_address(char *arg, unsigned long lineno) { + CP_WARN ("Invalid address %s", arg); +} + static HANDLE_FUNC (handle_allow) { char *arg = get_string_arg (line, &match[2]); - insert_acl (arg, ACL_ALLOW, &conf->access_list); + if(insert_acl (arg, ACL_ALLOW, &conf->access_list) < 0) + warn_invalid_address (arg, lineno); safefree (arg); return 0; } @@ -761,19 +794,35 @@ static HANDLE_FUNC (handle_deny) { char *arg = get_string_arg (line, &match[2]); - insert_acl (arg, ACL_DENY, &conf->access_list); + if(insert_acl (arg, ACL_DENY, &conf->access_list) < 0) + warn_invalid_address (arg, lineno); safefree (arg); return 0; } static HANDLE_FUNC (handle_bind) { - int r = set_string_arg (&conf->bind_address, line, &match[2]); + char *arg = get_string_arg (line, &match[2]); + + if (arg == NULL) { + return -1; + } + + if (conf->bind_addrs == NULL) { + conf->bind_addrs = sblist_new(sizeof(char*), 16); + if (conf->bind_addrs == NULL) { + CP_WARN ("Could not create a list " + "of bind addresses.", ""); + safefree(arg); + return -1; + } + } + + sblist_add (conf->bind_addrs, &arg); - if (r) - return r; log_message (LOG_INFO, - "Outgoing connections bound to IP %s", conf->bind_address); + "Added bind address [%s] for outgoing connections.", arg); + return 0; } @@ -786,23 +835,32 @@ static HANDLE_FUNC (handle_listen) } if (conf->listen_addrs == NULL) { - conf->listen_addrs = vector_create(); + conf->listen_addrs = sblist_new(sizeof(char*), 16); if (conf->listen_addrs == NULL) { - log_message(LOG_WARNING, "Could not create a list " - "of listen addresses."); + CP_WARN ("Could not create a list " + "of listen addresses.", ""); safefree(arg); return -1; } } - vector_append (conf->listen_addrs, arg, strlen(arg) + 1); + sblist_add (conf->listen_addrs, &arg); log_message(LOG_INFO, "Added address [%s] to listen addresses.", arg); - safefree (arg); return 0; } +static HANDLE_FUNC (handle_bindipv4mapped) +{ + return set_string_arg(&conf->bind_ipv4mapped, line, &match[2]); +} + +static HANDLE_FUNC (handle_bindipv6mapped) +{ + return set_string_arg(&conf->bind_ipv6mapped, line, &match[2]); +} + static HANDLE_FUNC (handle_errorfile) { /* @@ -815,8 +873,10 @@ static HANDLE_FUNC (handle_errorfile) unsigned long int err = get_long_arg (line, &match[2]); char *page = get_string_arg (line, &match[4]); - add_new_errorpage (page, err); - safefree (page); + if(add_new_errorpage (conf, page, err) < 0) { + CP_WARN ("add_new_errorpage() failed: '%s'", page); + safefree (page); + } return 0; } @@ -824,19 +884,16 @@ static HANDLE_FUNC (handle_addheader) { char *name = get_string_arg (line, &match[2]); char *value = get_string_arg (line, &match[3]); - http_header_t *header; + http_header_t header; if (!conf->add_headers) { - conf->add_headers = vector_create (); + conf->add_headers = sblist_new (sizeof(http_header_t), 16); } - header = (http_header_t *) safemalloc (sizeof (http_header_t)); - header->name = name; - header->value = value; - - vector_prepend (conf->add_headers, header, sizeof *header); + header.name = name; + header.value = value; - safefree (header); + sblist_add (conf->add_headers, &header); /* Don't free name or value here, as they are referenced in the * struct inserted into the vector. */ @@ -893,7 +950,7 @@ static HANDLE_FUNC (handle_basicauth) return -1; } if (!conf->basicauth_list) { - conf->basicauth_list = vector_create (); + conf->basicauth_list = sblist_new (sizeof(char*), 16); } basicauth_add (conf->basicauth_list, user, pass); @@ -997,9 +1054,26 @@ static enum proxy_type pt_from_string(const char *s) static HANDLE_FUNC (handle_upstream) { char *ip; - int port, mi = 2; + int port, mi; char *domain = 0, *user = 0, *pass = 0, *tmp; enum proxy_type pt; + enum upstream_build_error ube; + + if (match[3].rm_so != -1) { + tmp = get_string_arg (line, &match[3]); + if(!strcmp(tmp, "none")) { + safefree(tmp); + if (match[4].rm_so == -1) return -1; + domain = get_string_arg (line, &match[4]); + if (!domain) + return -1; + ube = upstream_add (NULL, 0, domain, 0, 0, PT_NONE, &conf->upstream_list); + safefree (domain); + goto check_err; + } + } + + mi = 6; tmp = get_string_arg (line, &match[mi]); pt = pt_from_string(tmp); @@ -1025,27 +1099,17 @@ static HANDLE_FUNC (handle_upstream) if (match[mi].rm_so != -1) domain = get_string_arg (line, &match[mi]); - upstream_add (ip, port, domain, user, pass, pt, &conf->upstream_list); + ube = upstream_add (ip, port, domain, user, pass, pt, &conf->upstream_list); safefree (user); safefree (pass); safefree (domain); safefree (ip); +check_err:; + if(ube != UBE_SUCCESS) + CP_WARN("%s", upstream_build_error_string(ube)); return 0; } -static HANDLE_FUNC (handle_upstream_no) -{ - char *domain; - - domain = get_string_arg (line, &match[3]); - if (!domain) - return -1; - - upstream_add (NULL, 0, domain, 0, 0, PT_NONE, &conf->upstream_list); - safefree (domain); - - return 0; -} #endif @@ -22,8 +22,8 @@ #ifndef TINYPROXY_CONF_H #define TINYPROXY_CONF_H -#include "hashmap.h" -#include "vector.h" +#include "hsearch.h" +#include "sblist.h" #include "acl.h" /* @@ -38,7 +38,7 @@ typedef struct { * Hold all the configuration time information. */ struct config_s { - vector_t basicauth_list; + sblist *basicauth_list; char *logf_name; unsigned int syslog; /* boolean */ unsigned int port; @@ -47,7 +47,7 @@ struct config_s { unsigned int maxclients; char *user; char *group; - vector_t listen_addrs; + sblist *listen_addrs; #ifdef FILTER_ENABLE char *filter; unsigned int filter_url; /* boolean */ @@ -68,8 +68,10 @@ struct config_s { #endif /* UPSTREAM_SUPPORT */ char *pidpath; unsigned int idletimeout; - char *bind_address; + sblist *bind_addrs; unsigned int bindsame; + char *bind_ipv4mapped; + char *bind_ipv6mapped; /* * The configured name to use in the HTTP "Via" header field. @@ -81,7 +83,7 @@ struct config_s { /* * Error page support. Map error numbers to file paths. */ - hashmap_t errorpages; + struct htab *errorpages; /* * Error page to be displayed if appropriate page cannot be located @@ -99,22 +101,23 @@ struct config_s { /* * Store the list of port allowed by CONNECT. */ - vector_t connect_ports; + sblist *connect_ports; /* * Map of headers which should be let through when the * anonymous feature is turned on. */ - hashmap_t anonymous_map; + struct htab *anonymous_map; /* * Extra headers to be added to outgoing HTTP requests. */ - vector_t add_headers; + sblist* add_headers; }; extern int reload_config_file (const char *config_fname, struct config_s *conf); -int config_compile_regex (void); +int config_init (void); +void free_config (struct config_s *conf); #endif diff --git a/src/connect-ports.c b/src/connect-ports.c index 41b4e3d..6070e92 100644 --- a/src/connect-ports.c +++ b/src/connect-ports.c @@ -25,10 +25,10 @@ * 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, vector_t *connect_ports) +void add_connect_port_allowed (int port, sblist **connect_ports) { if (!*connect_ports) { - *connect_ports = vector_create (); + *connect_ports = sblist_new (sizeof(int), 16); if (!*connect_ports) { log_message (LOG_WARNING, "Could not create a list of allowed CONNECT ports"); @@ -38,7 +38,7 @@ void add_connect_port_allowed (int port, vector_t *connect_ports) log_message (LOG_INFO, "Adding Port [%d] to the list allowed by CONNECT", port); - vector_append (*connect_ports, &port, sizeof (port)); + sblist_add (*connect_ports, &port); } /* @@ -47,7 +47,7 @@ void add_connect_port_allowed (int port, vector_t *connect_ports) * Returns: 1 if allowed * 0 if denied */ -int check_allowed_connect_ports (int port, vector_t connect_ports) +int check_allowed_connect_ports (int port, sblist *connect_ports) { size_t i; int *data; @@ -59,8 +59,8 @@ int check_allowed_connect_ports (int port, vector_t connect_ports) if (!connect_ports) return 1; - for (i = 0; i != (size_t) vector_length (connect_ports); ++i) { - data = (int *) vector_getentry (connect_ports, i, NULL); + for (i = 0; i < sblist_getsize (connect_ports); ++i) { + data = sblist_get (connect_ports, i); if (data && *data == port) return 1; } @@ -71,7 +71,7 @@ int check_allowed_connect_ports (int port, vector_t connect_ports) /** * Free a connect_ports list. */ -void free_connect_ports_list (vector_t connect_ports) +void free_connect_ports_list (sblist *connect_ports) { - vector_delete (connect_ports); + sblist_free (connect_ports); } diff --git a/src/connect-ports.h b/src/connect-ports.h index 4b3aaf7..38f511c 100644 --- a/src/connect-ports.h +++ b/src/connect-ports.h @@ -22,10 +22,10 @@ #define _TINYPROXY_CONNECT_PORTS_H_ #include "common.h" -#include "vector.h" +#include "sblist.h" -extern void add_connect_port_allowed (int port, vector_t *connect_ports); -int check_allowed_connect_ports (int port, vector_t connect_ports); -void free_connect_ports_list (vector_t connect_ports); +extern void add_connect_port_allowed (int port, sblist **connect_ports); +int check_allowed_connect_ports (int port, sblist *connect_ports); +void free_connect_ports_list (sblist *connect_ports); #endif /* _TINYPROXY_CONNECT_PORTS_ */ diff --git a/src/conns.c b/src/conns.c index 505b5c4..feac522 100644 --- a/src/conns.c +++ b/src/conns.c @@ -30,13 +30,21 @@ #include "log.h" #include "stats.h" -struct conn_s *initialize_conn (int client_fd, const char *ipaddr, - const char *sock_ipaddr) +void conn_struct_init(struct conn_s *connptr) { + connptr->error_number = -1; + connptr->client_fd = -1; + connptr->server_fd = -1; + /* There is _no_ content length initially */ + connptr->content_length.server = connptr->content_length.client = -1; +} + +int conn_init_contents (struct conn_s *connptr, const char *ipaddr, + const char *sock_ipaddr, + const char *sock_ipaddr_alt) { - struct conn_s *connptr; struct buffer_s *cbuffer, *sbuffer; - assert (client_fd >= 0); + assert (connptr->client_fd >= 0); /* * Allocate the memory for all the internal components @@ -47,47 +55,18 @@ struct conn_s *initialize_conn (int client_fd, const char *ipaddr, if (!cbuffer || !sbuffer) goto error_exit; - /* - * Allocate the space for the conn_s structure itself. - */ - connptr = (struct conn_s *) safemalloc (sizeof (struct conn_s)); - if (!connptr) - goto error_exit; - - connptr->client_fd = client_fd; - connptr->server_fd = -1; - connptr->cbuffer = cbuffer; connptr->sbuffer = sbuffer; - connptr->request_line = NULL; - - /* These store any error strings */ - connptr->error_variables = NULL; - connptr->error_string = NULL; - connptr->error_number = -1; - - connptr->connect_method = FALSE; - connptr->show_stats = FALSE; - - connptr->protocol.major = connptr->protocol.minor = 0; - - /* There is _no_ content length initially */ - connptr->content_length.server = connptr->content_length.client = -1; - connptr->server_ip_addr = (sock_ipaddr ? safestrdup (sock_ipaddr) : NULL); + connptr->server_ip_addr_alt = (sock_ipaddr_alt ? + safestrdup (sock_ipaddr_alt) : NULL); connptr->client_ip_addr = safestrdup (ipaddr); - connptr->upstream_proxy = NULL; - update_stats (STAT_OPEN); -#ifdef REVERSE_SUPPORT - connptr->reversepath = NULL; -#endif - - return connptr; + return 1; error_exit: /* @@ -98,10 +77,10 @@ error_exit: if (sbuffer) delete_buffer (sbuffer); - return NULL; + return 0; } -void destroy_conn (struct conn_s *connptr) +void conn_destroy_contents (struct conn_s *connptr) { assert (connptr != NULL); @@ -109,10 +88,12 @@ void destroy_conn (struct conn_s *connptr) if (close (connptr->client_fd) < 0) log_message (LOG_INFO, "Client (%d) close message: %s", connptr->client_fd, strerror (errno)); + connptr->client_fd = -1; if (connptr->server_fd != -1) if (close (connptr->server_fd) < 0) log_message (LOG_INFO, "Server (%d) close message: %s", connptr->server_fd, strerror (errno)); + connptr->server_fd = -1; if (connptr->cbuffer) delete_buffer (connptr->cbuffer); @@ -122,8 +103,16 @@ void destroy_conn (struct conn_s *connptr) if (connptr->request_line) safefree (connptr->request_line); - if (connptr->error_variables) - hashmap_delete (connptr->error_variables); + if (connptr->error_variables) { + char *k; + htab_value *v; + size_t it = 0; + while((it = htab_next(connptr->error_variables, it, &k, &v))) { + safefree(v->p); + safefree(k); + } + htab_destroy (connptr->error_variables); + } if (connptr->error_string) safefree (connptr->error_string); @@ -138,7 +127,5 @@ void destroy_conn (struct conn_s *connptr) safefree (connptr->reversepath); #endif - safefree (connptr); - update_stats (STAT_CLOSE); } diff --git a/src/conns.h b/src/conns.h index 393e5d4..36082f2 100644 --- a/src/conns.h +++ b/src/conns.h @@ -22,7 +22,7 @@ #define TINYPROXY_CONNS_H #include "main.h" -#include "hashmap.h" +#include "hsearch.h" /* * Connection Definition @@ -45,7 +45,7 @@ struct conn_s { * This structure stores key -> value mappings for substitution * in the error HTML files. */ - hashmap_t error_variables; + struct htab *error_variables; int error_number; char *error_string; @@ -62,6 +62,11 @@ struct conn_s { char *server_ip_addr; /* + * Store the server's alternative IP (for BindIPv4/6Mapped) + */ + char *server_ip_addr_alt; + + /* * Store the client's IP information */ char *client_ip_addr; @@ -87,11 +92,14 @@ struct conn_s { struct upstream *upstream_proxy; }; -/* - * Functions for the creation and destruction of a connection structure. - */ -extern struct conn_s *initialize_conn (int client_fd, const char *ipaddr, - const char *sock_ipaddr); -extern void destroy_conn (struct conn_s *connptr); +/* expects pointer to zero-initialized struct, set up struct + with default values for initial use */ +extern void conn_struct_init(struct conn_s *connptr); + +/* second stage initializiation, sets up buffers and connection details */ +extern int conn_init_contents (struct conn_s *connptr, const char *ipaddr, + const char *sock_ipaddr, + const char *sock_ipaddr_alt); +extern void conn_destroy_contents (struct conn_s *connptr); #endif diff --git a/src/filter.c b/src/filter.c index 8a0b085..b9b5066 100644 --- a/src/filter.c +++ b/src/filter.c @@ -24,6 +24,7 @@ #include "main.h" +#include <regex.h> #include "filter.h" #include "heap.h" #include "log.h" @@ -60,7 +61,8 @@ void filter_init (void) fd = fopen (config->filter, "r"); if (!fd) { - return; + perror ("filter file"); + exit (EX_DATAERR); } cflags = REG_NEWLINE | REG_NOSUB; diff --git a/src/hashmap.c b/src/hashmap.c deleted file mode 100644 index 7793d08..0000000 --- a/src/hashmap.c +++ /dev/null @@ -1,516 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes <rjkaes@users.sourceforge.net> - * - * 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 of the License, 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* A hashmap implementation. The keys are case-insensitive NULL terminated - * strings, and the data is arbitrary lumps of data. Copies of both the - * key and the data in the hashmap itself, so you must free the original - * key and data to avoid a memory leak. The hashmap returns a pointer - * to the data when a key is searched for, so take care in modifying the - * data as it's modifying the data stored in the hashmap. (In other words, - * don't try to free the data, or realloc the memory. :) - */ - -#include "main.h" - -#include "hashmap.h" -#include "heap.h" - -/* - * These structures are the storage for the hashmap. Entries are stored in - * struct hashentry_s (the key, data, and length), and all the "buckets" are - * grouped together in hashmap_s. The hashmap_s.size member is for - * internal use. It stores the number of buckets the hashmap was created - * with. - */ -struct hashentry_s { - char *key; - void *data; - size_t len; - - struct hashentry_s *prev, *next; -}; - -struct hashbucket_s { - struct hashentry_s *head, *tail; -}; - -struct hashmap_s { - uint32_t seed; - unsigned int size; - hashmap_iter end_iterator; - - struct hashbucket_s *buckets; -}; - -/* - * A NULL terminated string is passed to this function and a "hash" value - * is produced within the range of [0 .. size) (In other words, 0 to one - * less than size.) - * The contents of the key are converted to lowercase, so this function - * is not case-sensitive. - * - * This is Dan Bernstein's hash function as described, for example, here: - * http://www.cse.yorku.ca/~oz/hash.html - * - * If any of the arguments are invalid a negative number is returned. - */ -static int hashfunc (const char *key, unsigned int size, uint32_t seed) -{ - uint32_t hash; - - if (key == NULL) - return -EINVAL; - if (size == 0) - return -ERANGE; - - for (hash = seed; *key != '\0'; key++) { - hash = ((hash << 5) + hash) ^ tolower (*key); - } - - /* Keep the hash within the table limits */ - return hash % size; -} - -/* - * Create a hashmap with the requested number of buckets. If "nbuckets" is - * not greater than zero a NULL is returned; otherwise, a _token_ to the - * hashmap is returned. - * - * NULLs are also returned if memory could not be allocated for hashmap. - */ -hashmap_t hashmap_create (unsigned int nbuckets) -{ - struct hashmap_s *ptr; - - if (nbuckets == 0) - return NULL; - - ptr = (struct hashmap_s *) safecalloc (1, sizeof (struct hashmap_s)); - if (!ptr) - return NULL; - - ptr->seed = (uint32_t)rand(); - ptr->size = nbuckets; - ptr->buckets = (struct hashbucket_s *) safecalloc (nbuckets, - sizeof (struct - hashbucket_s)); - if (!ptr->buckets) { - safefree (ptr); - return NULL; - } - - /* This points to "one" past the end of the hashmap. */ - ptr->end_iterator = 0; - - return ptr; -} - -/* - * Follow the chain of hashentries and delete them (including the data and - * the key.) - * - * Returns: 0 if the function completed successfully - * negative number is returned if "entry" was NULL - */ -static int delete_hashbucket (struct hashbucket_s *bucket) -{ - struct hashentry_s *nextptr; - struct hashentry_s *ptr; - - if (bucket == NULL || bucket->head == NULL) - return -EINVAL; - - ptr = bucket->head; - while (ptr) { - nextptr = ptr->next; - - safefree (ptr->key); - safefree (ptr->data); - safefree (ptr); - - ptr = nextptr; - } - - return 0; -} - -/* - * Deletes a hashmap. All the key/data pairs are also deleted. - * - * Returns: 0 on success - * negative if a NULL "map" was supplied - */ -int hashmap_delete (hashmap_t map) -{ - unsigned int i; - - if (map == NULL) - return -EINVAL; - - for (i = 0; i != map->size; i++) { - if (map->buckets[i].head != NULL) { - delete_hashbucket (&map->buckets[i]); - } - } - - safefree (map->buckets); - safefree (map); - - return 0; -} - -/* - * Inserts a NULL terminated string (as the key), plus any arbitrary "data" - * of "len" bytes. Both the key and the data are copied, so the original - * key/data must be freed to avoid a memory leak. - * The "data" must be non-NULL and "len" must be greater than zero. You - * cannot insert NULL data in association with the key. - * - * Returns: 0 on success - * negative number if there are errors - */ -int -hashmap_insert (hashmap_t map, const char *key, const void *data, size_t len) -{ - struct hashentry_s *ptr; - int hash; - char *key_copy; - void *data_copy; - - assert (map != NULL); - assert (key != NULL); - assert (data != NULL); - assert (len > 0); - - if (map == NULL || key == NULL) - return -EINVAL; - if (!data || len < 1) - return -ERANGE; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - /* - * First make copies of the key and data in case there is a memory - * problem later. - */ - key_copy = safestrdup (key); - if (!key_copy) - return -ENOMEM; - - data_copy = safemalloc (len); - if (!data_copy) { - safefree (key_copy); - return -ENOMEM; - } - memcpy (data_copy, data, len); - - ptr = (struct hashentry_s *) safemalloc (sizeof (struct hashentry_s)); - if (!ptr) { - safefree (key_copy); - safefree (data_copy); - return -ENOMEM; - } - - ptr->key = key_copy; - ptr->data = data_copy; - ptr->len = len; - - /* - * Now add the entry to the end of the bucket chain. - */ - ptr->next = NULL; - ptr->prev = map->buckets[hash].tail; - if (map->buckets[hash].tail) - map->buckets[hash].tail->next = ptr; - - map->buckets[hash].tail = ptr; - if (!map->buckets[hash].head) - map->buckets[hash].head = ptr; - - map->end_iterator++; - return 0; -} - -/* - * Get an iterator to the first entry. - * - * Returns: an negative value upon error. - */ -hashmap_iter hashmap_first (hashmap_t map) -{ - assert (map != NULL); - - if (!map) - return -EINVAL; - - if (map->end_iterator == 0) - return -1; - else - return 0; -} - -/* - * Checks to see if the iterator is pointing at the "end" of the entries. - * - * Returns: 1 if it is the end - * 0 otherwise - */ -int hashmap_is_end (hashmap_t map, hashmap_iter iter) -{ - assert (map != NULL); - assert (iter >= 0); - - if (!map || iter < 0) - return -EINVAL; - - if (iter == map->end_iterator) - return 1; - else - return 0; -} - -/* - * Return a "pointer" to the first instance of the particular key. It can - * be tested against hashmap_is_end() to see if the key was not found. - * - * Returns: negative upon an error - * an "iterator" pointing at the first key - * an "end-iterator" if the key wasn't found - */ -hashmap_iter hashmap_find (hashmap_t map, const char *key) -{ - unsigned int i; - hashmap_iter iter = 0; - struct hashentry_s *ptr; - - assert (map != NULL); - assert (key != NULL); - - if (!map || !key) - return -EINVAL; - - /* - * Loop through all the keys and look for the first occurrence - * of a particular key. - */ - for (i = 0; i != map->size; i++) { - ptr = map->buckets[i].head; - - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) { - /* Found it, so return the current count */ - return iter; - } - - iter++; - ptr = ptr->next; - } - } - - return iter; -} - -/* - * Retrieve the data associated with a particular iterator. - * - * Returns: the length of the data block upon success - * negative upon error - */ -ssize_t -hashmap_return_entry (hashmap_t map, hashmap_iter iter, char **key, void **data) -{ - unsigned int i; - struct hashentry_s *ptr; - hashmap_iter count = 0; - - assert (map != NULL); - assert (iter >= 0); - assert (iter != map->end_iterator); - assert (key != NULL); - assert (data != NULL); - - if (!map || iter < 0 || !key || !data) - return -EINVAL; - - for (i = 0; i != map->size; i++) { - ptr = map->buckets[i].head; - while (ptr) { - if (count == iter) { - /* This is the data so return it */ - *key = ptr->key; - *data = ptr->data; - return ptr->len; - } - - ptr = ptr->next; - count++; - } - } - - return -EFAULT; -} - -/* - * Searches for _any_ occurrences of "key" within the hashmap. - * - * Returns: negative upon an error - * zero if no key is found - * count found - */ -ssize_t hashmap_search (hashmap_t map, const char *key) -{ - int hash; - struct hashentry_s *ptr; - ssize_t count = 0; - - if (map == NULL || key == NULL) - return -EINVAL; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - ptr = map->buckets[hash].head; - - /* All right, there is an entry here, now see if it's the one we want */ - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) - ++count; - - /* This entry didn't contain the key; move to the next one */ - ptr = ptr->next; - } - - return count; -} - -/* - * Get the first entry (assuming there is more than one) for a particular - * key. The data MUST be non-NULL. - * - * Returns: negative upon error - * zero if no entry is found - * length of data for the entry - */ -ssize_t hashmap_entry_by_key (hashmap_t map, const char *key, void **data) -{ - int hash; - struct hashentry_s *ptr; - - if (!map || !key || !data) - return -EINVAL; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - ptr = map->buckets[hash].head; - - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) { - *data = ptr->data; - return ptr->len; - } - - ptr = ptr->next; - } - - return 0; -} - -/* - * Go through the hashmap and remove the particular key. - * NOTE: This will invalidate any iterators which have been created. - * - * Remove: negative upon error - * 0 if the key was not found - * positive count of entries deleted - */ -ssize_t hashmap_remove (hashmap_t map, const char *key) -{ - int hash; - struct hashentry_s *ptr, *next; - short int deleted = 0; - - if (map == NULL || key == NULL) - return -EINVAL; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - ptr = map->buckets[hash].head; - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) { - /* - * Found the data, now need to remove everything - * and update the hashmap. - */ - next = ptr->next; - - if (ptr->prev) - ptr->prev->next = ptr->next; - if (ptr->next) - ptr->next->prev = ptr->prev; - - if (map->buckets[hash].head == ptr) - map->buckets[hash].head = ptr->next; - if (map->buckets[hash].tail == ptr) - map->buckets[hash].tail = ptr->prev; - - safefree (ptr->key); - safefree (ptr->data); - safefree (ptr); - - ++deleted; - --map->end_iterator; - - ptr = next; - continue; - } - - /* This entry didn't contain the key; move to the next one */ - ptr = ptr->next; - } - - /* The key was not found, so return 0 */ - return deleted; -} - -/* - * Look up the value for a variable. - */ -char *lookup_variable (hashmap_t map, const char *varname) -{ - hashmap_iter result_iter; - char *key; - char *data; - - result_iter = hashmap_find (map, varname); - - if (hashmap_is_end (map, result_iter)) - return (NULL); - - if (hashmap_return_entry (map, result_iter, - &key, (void **) &data) < 0) - return (NULL); - - return (data); -} diff --git a/src/hashmap.h b/src/hashmap.h deleted file mode 100644 index 9206737..0000000 --- a/src/hashmap.h +++ /dev/null @@ -1,125 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes <rjkaes@users.sourceforge.net> - * - * 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 of the License, 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* See 'hashmap.c' for detailed information. */ - -#ifndef _HASHMAP_H -#define _HASHMAP_H - -#include "common.h" - -/* - * We're using a typedef here to "hide" the implementation details of the - * hash map. Sure, it's a pointer, but the struct is hidden in the C file. - * So, just use the hashmap_t like it's a cookie. :) - */ -typedef struct hashmap_s *hashmap_t; -typedef int hashmap_iter; - -/* - * hashmap_create() takes one argument, which is the number of buckets to - * use internally. hashmap_delete() is self explanatory. - */ -extern hashmap_t hashmap_create (unsigned int nbuckets); -extern int hashmap_delete (hashmap_t map); - -/* - * When the you insert a key/data pair into the hashmap it will the key - * and data are duplicated, so you must free your copy if it was created - * on the heap. The key must be a NULL terminated string. "data" must be - * non-NULL and length must be greater than zero. - * - * Returns: negative on error - * 0 upon successful insert - */ -extern int hashmap_insert (hashmap_t map, const char *key, - const void *data, size_t len); - -/* - * Get an iterator to the first entry. - * - * Returns: an negative value upon error. - */ -extern hashmap_iter hashmap_first (hashmap_t map); - -/* - * Checks to see if the iterator is pointing at the "end" of the entries. - * - * Returns: 1 if it is the end - * 0 otherwise - */ -extern int hashmap_is_end (hashmap_t map, hashmap_iter iter); - -/* - * Return a "pointer" to the first instance of the particular key. It can - * be tested against hashmap_is_end() to see if the key was not found. - * - * Returns: negative upon an error - * an "iterator" pointing at the first key - * an "end-iterator" if the key wasn't found - */ -extern hashmap_iter hashmap_find (hashmap_t map, const char *key); - -/* - * Retrieve the key/data associated with a particular iterator. - * NOTE: These are pointers to the actual data, so don't mess around with them - * too much. - * - * Returns: the length of the data block upon success - * negative upon error - */ -extern ssize_t hashmap_return_entry (hashmap_t map, hashmap_iter iter, - char **key, void **data); - -/* - * Get the first entry (assuming there is more than one) for a particular - * key. The data MUST be non-NULL. - * - * Returns: negative upon error - * zero if no entry is found - * length of data for the entry - */ -extern ssize_t hashmap_entry_by_key (hashmap_t map, const char *key, - void **data); - -/* - * Searches for _any_ occurrances of "key" within the hashmap and returns the - * number of matching entries. - * - * Returns: negative upon an error - * zero if no key is found - * count found (positive value) - */ -extern ssize_t hashmap_search (hashmap_t map, const char *key); - -/* - * Go through the hashmap and remove the particular key. - * NOTE: This will invalidate any iterators which have been created. - * - * Remove: negative upon error - * 0 if the key was not found - * positive count of entries deleted - */ -extern ssize_t hashmap_remove (hashmap_t map, const char *key); - -/* - * Look up the value for a variable. - */ -extern char *lookup_variable (hashmap_t map, const char *varname); - -#endif /* _HASHMAP_H */ diff --git a/src/hostspec.c b/src/hostspec.c new file mode 100644 index 0000000..adbad53 --- /dev/null +++ b/src/hostspec.c @@ -0,0 +1,166 @@ +#include "common.h" +#include "hostspec.h" +#include "heap.h" +#include "network.h" + +/* + * Fills in the netmask array given a numeric value. + * + * Returns: + * 0 on success + * -1 on failure (invalid mask value) + * + */ +static int +fill_netmask_array (char *bitmask_string, int v6, + unsigned char array[], size_t len) +{ + unsigned int i; + unsigned long int mask; + char *endptr; + + errno = 0; /* to distinguish success/failure after call */ + mask = strtoul (bitmask_string, &endptr, 10); + + /* check for various conversion errors */ + if ((errno == ERANGE && mask == ULONG_MAX) + || (errno != 0 && mask == 0) || (endptr == bitmask_string)) + return -1; + + if (v6 == 0) { + /* The mask comparison is done as an IPv6 address, so + * convert to a longer mask in the case of IPv4 + * addresses. */ + mask += 12 * 8; + } + + /* check valid range for a bit mask */ + if (mask > (8 * len)) + return -1; + + /* we have a valid range to fill in the array */ + for (i = 0; i != len; ++i) { + if (mask >= 8) { + array[i] = 0xff; + mask -= 8; + } else if (mask > 0) { + array[i] = (unsigned char) (0xff << (8 - mask)); + mask = 0; + } else { + array[i] = 0; + } + } + + return 0; +} + + +/* parse a location string containing either an ipv4/ipv4 + hostmask tuple + or a dnsname into a struct hostspec. + returns 0 on success, non-0 on error (might be memory allocation, bogus + ip address or mask). +*/ +int hostspec_parse(char *location, struct hostspec *h) { + char *mask, ip_dst[IPV6_LEN]; + + h->type = HST_NONE; + if(!location) return 0; + + memset(h, 0, sizeof(*h)); + if ((mask = strrchr(location, '/'))) + *(mask++) = 0; + + /* + * Check for a valid IP address (the simplest case) first. + */ + if (full_inet_pton (location, ip_dst) > 0) { + h->type = HST_NUMERIC; + memcpy (h->address.ip.network, ip_dst, IPV6_LEN); + if(!mask) memset (h->address.ip.mask, 0xff, IPV6_LEN); + else { + char dst[sizeof(struct in6_addr)]; + int v6, i; + /* Check if the IP address before the netmask is + * an IPv6 address */ + if (inet_pton(AF_INET6, location, dst) > 0) + v6 = 1; + else + v6 = 0; + + if (fill_netmask_array + (mask, v6, &(h->address.ip.mask[0]), IPV6_LEN) + < 0) + goto err; + + for (i = 0; i < IPV6_LEN; i++) + h->address.ip.network[i] = ip_dst[i] & + h->address.ip.mask[i]; + } + } else { + /* either bogus IP or hostname */ + /* bogus ipv6 ? */ + if (mask || strchr (location, ':')) + goto err; + + /* In all likelihood a string */ + h->type = HST_STRING; + h->address.string = safestrdup (location); + if (!h->address.string) + goto err; + } + /* restore mask */ + if(mask) *(--mask) = '/'; + return 0; +err:; + if(mask) *(--mask) = '/'; + return -1; +} + +static int string_match(const char *ip, const char *addrspec) +{ + size_t test_length, match_length; + if(!strcasecmp(ip, addrspec)) return 1; + if(addrspec[0] != '.') return 0; + test_length = strlen (ip); + match_length = strlen (addrspec); + if (test_length < match_length) return 0; + return (strcasecmp + (ip + (test_length - match_length), + addrspec) == 0); +} + +static int numeric_match(const uint8_t addr[], const struct hostspec *h) +{ + uint8_t x, y; + int i; + + for (i = 0; i != IPV6_LEN; ++i) { + x = addr[i] & h->address.ip.mask[i]; + y = h->address.ip.network[i]; + + /* If x and y don't match, the IP addresses don't match */ + if (x != y) + return 0; + } + + return 1; +} + +/* check whether ip matches hostspec. + return 1 on match, 0 on non-match */ +int hostspec_match(const char *ip, const struct hostspec *h) { + int is_numeric_addr; + uint8_t numeric_addr[IPV6_LEN]; + if (ip[0] == '\0') return 0; + is_numeric_addr = (full_inet_pton (ip, &numeric_addr) > 0); + switch (h->type) { + case HST_STRING: + if(is_numeric_addr) return 0; + return string_match (ip, h->address.string); + case HST_NUMERIC: + return numeric_match (numeric_addr, h); + case HST_NONE: + return 0; + } + return 0; +} diff --git a/src/hostspec.h b/src/hostspec.h new file mode 100644 index 0000000..9d1d7bf --- /dev/null +++ b/src/hostspec.h @@ -0,0 +1,26 @@ +#ifndef HOSTSPEC_H +#define HOSTSPEC_H + +#define IPV6_LEN 16 + +enum hostspec_type { + HST_NONE, + HST_STRING, + HST_NUMERIC, +}; + +struct hostspec { + enum hostspec_type type; + union { + char *string; + struct { + unsigned char network[IPV6_LEN]; + unsigned char mask[IPV6_LEN]; + } ip; + } address; +}; + +int hostspec_parse(char *domain, struct hostspec *h); +int hostspec_match(const char *ip, const struct hostspec *h); + +#endif diff --git a/src/hsearch.c b/src/hsearch.c new file mode 100644 index 0000000..be0434c --- /dev/null +++ b/src/hsearch.c @@ -0,0 +1,219 @@ +/* +musl license, hsearch.c originally written by Szabolcs Nagy + +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <stdlib.h> +#include <string.h> +#include "hsearch.h" + +/* +open addressing hash table with 2^n table size +quadratic probing is used in case of hash collision +tab indices and hash are size_t +after resize fails with ENOMEM the state of tab is still usable +*/ + +typedef struct htab_entry { + char *key; + htab_value data; +} htab_entry; + +struct elem { + htab_entry item; + size_t hash; +}; + +struct htab { + struct elem *elems; + size_t mask; + size_t used; + size_t seed; + size_t dead; +}; + +#define MINSIZE 8 +#define MAXSIZE ((size_t)-1/2 + 1) + +#define CASE_INSENSITIVE +#ifdef CASE_INSENSITIVE +#include <ctype.h> +#include <strings.h> +#define LOWER_OR_NOT(X) tolower(X) +#define STRCMP(X, Y) strcasecmp(X, Y) +#else +#define LOWER_OR_NOT(X) X +#define STRCMP(X, Y) strcmp(X, Y) +#endif + +static size_t keyhash(const char *k, size_t seed) +{ + const unsigned char *p = (const void *)k; + size_t h = seed; + + while (*p) + h = 31*h + LOWER_OR_NOT(*p++); + return h; +} + +static int resize(struct htab *htab, size_t nel) +{ + size_t newsize; + size_t i, j; + struct elem *e, *newe; + struct elem *oldtab = htab->elems; + struct elem *oldend = htab->elems + htab->mask + 1; + + if (nel > MAXSIZE) + nel = MAXSIZE; + for (newsize = MINSIZE; newsize < nel; newsize *= 2); + htab->elems = calloc(newsize, sizeof *htab->elems); + if (!htab->elems) { + htab->elems = oldtab; + return 0; + } + htab->mask = newsize - 1; + if (!oldtab) + return 1; + for (e = oldtab; e < oldend; e++) + if (e->item.key) { + for (i=e->hash,j=1; ; i+=j++) { + newe = htab->elems + (i & htab->mask); + if (!newe->item.key) + break; + } + *newe = *e; + } + free(oldtab); + return 1; +} + +static struct elem *lookup(struct htab *htab, const char *key, size_t hash, size_t dead) +{ + size_t i, j; + struct elem *e; + + for (i=hash,j=1; ; i+=j++) { + e = htab->elems + (i & htab->mask); + if ((!e->item.key && (!e->hash || e->hash == dead)) || + (e->hash==hash && STRCMP(e->item.key, key)==0)) + break; + } + return e; +} + +struct htab *htab_create(size_t nel) +{ + struct htab *r = calloc(1, sizeof *r); + if(r && !resize(r, nel)) { + free(r); + r = 0; + } + r->seed = rand(); + return r; +} + +void htab_destroy(struct htab *htab) +{ + free(htab->elems); + free(htab); +} + +static struct elem *htab_find_elem(struct htab *htab, const char* key) +{ + size_t hash = keyhash(key, htab->seed); + struct elem *e = lookup(htab, key, hash, 0); + + if (e->item.key) { + return e; + } + return 0; +} + +htab_value* htab_find(struct htab *htab, const char* key) +{ + struct elem *e = htab_find_elem(htab, key); + if(!e) return 0; + return &e->item.data; +} + +htab_value* htab_find2(struct htab *htab, const char* key, char **saved_key) +{ + struct elem *e = htab_find_elem(htab, key); + if(!e) return 0; + *saved_key = e->item.key; + return &e->item.data; +} + +int htab_delete(struct htab *htab, const char* key) +{ + struct elem *e = htab_find_elem(htab, key); + if(!e) return 0; + e->item.key = 0; + e->hash = 0xdeadc0de; + --htab->used; + ++htab->dead; + return 1; +} + +int htab_insert(struct htab *htab, char* key, htab_value value) +{ + size_t hash = keyhash(key, htab->seed), oh; + struct elem *e = lookup(htab, key, hash, 0xdeadc0de); + if(e->item.key) { + /* it's not allowed to overwrite existing data */ + return 0; + } + + oh = e->hash; /* save old hash in case it's tombstone marker */ + e->item.key = key; + e->item.data = value; + e->hash = hash; + if (++htab->used + htab->dead > htab->mask - htab->mask/4) { + if (!resize(htab, 2*htab->used)) { + htab->used--; + e->item.key = 0; + e->hash = oh; + return 0; + } + htab->dead = 0; + } else if (oh == 0xdeadc0de) { + /* re-used tomb */ + --htab->dead; + } + return 1; +} + +size_t htab_next(struct htab *htab, size_t iterator, char** key, htab_value **v) +{ + size_t i; + for(i=iterator;i<htab->mask+1;++i) { + struct elem *e = htab->elems + i; + if(e->item.key) { + *key = e->item.key; + *v = &e->item.data; + return i+1; + } + } + return 0; +} diff --git a/src/hsearch.h b/src/hsearch.h new file mode 100644 index 0000000..7e9d770 --- /dev/null +++ b/src/hsearch.h @@ -0,0 +1,23 @@ +#ifndef HSEARCH_H +#define HSEARCH_H + +#include <stdlib.h> + +typedef union htab_value { + void *p; + size_t n; +} htab_value; + +#define HTV_N(N) (htab_value) {.n = N} +#define HTV_P(P) (htab_value) {.p = P} + +struct htab * htab_create(size_t); +void htab_destroy(struct htab *); +htab_value* htab_find(struct htab *, const char* key); +/* same as htab_find, but can retrieve the saved key (for freeing) */ +htab_value* htab_find2(struct htab *htab, const char* key, char **saved_key); +int htab_insert(struct htab *, char*, htab_value); +int htab_delete(struct htab *htab, const char* key); +size_t htab_next(struct htab *, size_t iterator, char** key, htab_value **v); + +#endif diff --git a/src/html-error.c b/src/html-error.c index 95ee9cf..998a6ee 100644 --- a/src/html-error.c +++ b/src/html-error.c @@ -20,9 +20,9 @@ * HTML error pages with variable substitution. */ +#include "common.h" #include "main.h" -#include "common.h" #include "buffer.h" #include "conns.h" #include "heap.h" @@ -31,25 +31,33 @@ #include "utils.h" #include "conf.h" +#include <regex.h> + /* * Add an error number -> filename mapping to the errorpages list. */ #define ERRORNUM_BUFSIZE 8 /* this is more than required */ #define ERRPAGES_BUCKETCOUNT 16 -int add_new_errorpage (char *filepath, unsigned int errornum) +int add_new_errorpage (struct config_s *conf, char *filepath, + unsigned int errornum) { - char errornbuf[ERRORNUM_BUFSIZE]; + char errornbuf[ERRORNUM_BUFSIZE], *k; - config->errorpages = hashmap_create (ERRPAGES_BUCKETCOUNT); - if (!config->errorpages) + if (!conf->errorpages) + conf->errorpages = htab_create (ERRPAGES_BUCKETCOUNT); + if (!conf->errorpages) return (-1); snprintf (errornbuf, ERRORNUM_BUFSIZE, "%u", errornum); - if (hashmap_insert (config->errorpages, errornbuf, - filepath, strlen (filepath) + 1) < 0) + k = safestrdup(errornbuf); + if (!k) return -1; + + if (!htab_insert (conf->errorpages, k, HTV_P(filepath))) { + safefree(k); return (-1); + } return (0); } @@ -59,10 +67,8 @@ int add_new_errorpage (char *filepath, unsigned int errornum) */ static char *get_html_file (unsigned int errornum) { - hashmap_iter result_iter; char errornbuf[ERRORNUM_BUFSIZE]; - char *key; - char *val; + htab_value *hv; assert (errornum >= 100 && errornum < 1000); @@ -71,16 +77,15 @@ static char *get_html_file (unsigned int errornum) snprintf (errornbuf, ERRORNUM_BUFSIZE, "%u", errornum); - result_iter = hashmap_find (config->errorpages, errornbuf); - - if (hashmap_is_end (config->errorpages, result_iter)) - return (config->errorpage_undef); - - if (hashmap_return_entry (config->errorpages, result_iter, - &key, (void **) &val) < 0) - return (config->errorpage_undef); + hv = htab_find (config->errorpages, errornbuf); + if (!hv) return (config->errorpage_undef); + return hv->p; +} - return (val); +static char *lookup_variable (struct htab *map, const char *varname) { + htab_value *v; + v = htab_find(map, varname); + return v ? v->p : 0; } static void varsubst_sendline(struct conn_s *connptr, regex_t *re, char *p) { @@ -128,7 +133,9 @@ send_html_file (FILE *infile, struct conn_s *connptr) return 1; } -int send_http_headers (struct conn_s *connptr, int code, const char *message) +int send_http_headers ( + struct conn_s *connptr, int code, + const char *message, const char *extra) { const char headers[] = "HTTP/1.%u %d %s\r\n" @@ -137,22 +144,10 @@ int send_http_headers (struct conn_s *connptr, int code, const char *message) "%s" "Connection: close\r\n" "\r\n"; - const char p_auth_str[] = - "Proxy-Authenticate: Basic realm=\"" - PACKAGE_NAME "\"\r\n"; - - const char w_auth_str[] = - "WWW-Authenticate: Basic realm=\"" - PACKAGE_NAME "\"\r\n"; - - /* according to rfc7235, the 407 error must be accompanied by - a Proxy-Authenticate header field. */ - const char *add = code == 407 ? p_auth_str : (code == 401 ? w_auth_str : ""); - return (write_message (connptr->client_fd, headers, connptr->protocol.major != 1 ? 0 : connptr->protocol.minor, code, message, PACKAGE, VERSION, - add)); + extra)); } /* @@ -176,8 +171,21 @@ int send_http_error_message (struct conn_s *connptr) "<p><em>Generated by %s version %s.</em></p>\n" "</body>\n" "</html>\n"; + const char p_auth_str[] = + "Proxy-Authenticate: Basic realm=\"" + PACKAGE_NAME "\"\r\n"; + + const char w_auth_str[] = + "WWW-Authenticate: Basic realm=\"" + PACKAGE_NAME "\"\r\n"; + + /* according to rfc7235, the 407 error must be accompanied by + a Proxy-Authenticate header field. */ + const char *add = connptr->error_number == 407 ? p_auth_str : + (connptr->error_number == 401 ? w_auth_str : ""); + send_http_headers (connptr, connptr->error_number, - connptr->error_string); + connptr->error_string, add); error_file = get_html_file (connptr->error_number); if (!(infile = fopen (error_file, "r"))) { @@ -203,14 +211,25 @@ int send_http_error_message (struct conn_s *connptr) int add_error_variable (struct conn_s *connptr, const char *key, const char *val) { + char *k, *v; + if (!connptr->error_variables) if (! (connptr->error_variables = - hashmap_create (ERRVAR_BUCKETCOUNT))) + htab_create (ERRVAR_BUCKETCOUNT))) return (-1); - return hashmap_insert (connptr->error_variables, key, val, - strlen (val) + 1); + k = safestrdup(key); + v = safestrdup(val); + + if (!v || !k) goto oom; + + if(htab_insert (connptr->error_variables, k, HTV_P(v))) + return 1; +oom:; + safefree(k); + safefree(v); + return -1; } #define ADD_VAR_RET(x, y) \ @@ -229,6 +248,7 @@ int add_standard_vars (struct conn_s *connptr) char errnobuf[16]; char timebuf[30]; time_t global_time; + struct tm tm_buf; snprintf (errnobuf, sizeof errnobuf, "%d", connptr->error_number); ADD_VAR_RET ("errno", errnobuf); @@ -244,7 +264,7 @@ int add_standard_vars (struct conn_s *connptr) global_time = time (NULL); strftime (timebuf, sizeof (timebuf), "%a, %d %b %Y %H:%M:%S GMT", - gmtime (&global_time)); + gmtime_r (&global_time, &tm_buf)); add_error_variable (connptr, "date", timebuf); add_error_variable (connptr, "website", diff --git a/src/html-error.h b/src/html-error.h index 03cec98..bc9b7ce 100644 --- a/src/html-error.h +++ b/src/html-error.h @@ -23,8 +23,9 @@ /* Forward declaration */ struct conn_s; +struct config_s; -extern int add_new_errorpage (char *filepath, unsigned int errornum); +extern int add_new_errorpage (struct config_s *, char *filepath, unsigned int errornum); extern int send_http_error_message (struct conn_s *connptr); extern int indicate_http_error (struct conn_s *connptr, int number, const char *message, ...); @@ -32,7 +33,7 @@ extern int add_error_variable (struct conn_s *connptr, const char *key, const char *val); extern int send_html_file (FILE * infile, struct conn_s *connptr); extern int send_http_headers (struct conn_s *connptr, int code, - const char *message); + const char *message, const char *extra); extern int add_standard_vars (struct conn_s *connptr); #endif /* !TINYPROXY_HTML_ERROR_H */ diff --git a/src/http-message.c b/src/http-message.c index 8b94f19..4ff37ae 100644 --- a/src/http-message.c +++ b/src/http-message.c @@ -232,6 +232,7 @@ int http_message_send (http_message_t msg, int fd) char timebuf[30]; time_t global_time; unsigned int i; + struct tm tm_buf; assert (is_http_message_valid (msg)); @@ -254,11 +255,11 @@ int http_message_send (http_message_t msg, int fd) /* Output the date */ global_time = time (NULL); strftime (timebuf, sizeof (timebuf), "%a, %d %b %Y %H:%M:%S GMT", - gmtime (&global_time)); + gmtime_r (&global_time, &tm_buf)); write_message (fd, "Date: %s\r\n", timebuf); /* Output the content-length */ - write_message (fd, "Content-length: %u\r\n", msg->body.length); + write_message (fd, "Content-length: %lu\r\n", (unsigned long) msg->body.length); /* Write the separator between the headers and body */ safe_write (fd, "\r\n", 2); @@ -27,7 +27,7 @@ #include "heap.h" #include "log.h" #include "utils.h" -#include "vector.h" +#include "sblist.h" #include "conf.h" #include <pthread.h> @@ -64,7 +64,7 @@ static int log_level = LOG_INFO; * The key is the actual messages (already filled in full), and the value * is the log level. */ -static vector_t log_message_storage; +static sblist *log_message_storage; static unsigned int logging_initialized = FALSE; /* boolean */ @@ -108,7 +108,8 @@ void set_log_level (int level) void log_message (int level, const char *fmt, ...) { va_list args; - time_t nowtime; + struct timespec nowtime; + struct tm tm_buf; char time_string[TIME_LENGTH]; char str[STRING_LENGTH]; @@ -142,7 +143,7 @@ void log_message (int level, const char *fmt, ...) char *entry_buffer; if (!log_message_storage) { - log_message_storage = vector_create (); + log_message_storage = sblist_new (sizeof(char*), 64); if (!log_message_storage) goto out; } @@ -154,10 +155,8 @@ void log_message (int level, const char *fmt, ...) goto out; sprintf (entry_buffer, "%d %s", level, str); - vector_append (log_message_storage, entry_buffer, - strlen (entry_buffer) + 1); - - safefree (entry_buffer); + if(!sblist_add (log_message_storage, &entry_buffer)) + safefree (entry_buffer); goto out; } @@ -176,13 +175,14 @@ void log_message (int level, const char *fmt, ...) } else { char *p; - nowtime = time (NULL); + clock_gettime(CLOCK_REALTIME, &nowtime); /* Format is month day hour:minute:second (24 time) */ strftime (time_string, TIME_LENGTH, "%b %d %H:%M:%S", - localtime (&nowtime)); + localtime_r (&nowtime.tv_sec, &tm_buf)); - snprintf (str, STRING_LENGTH, "%-9s %s [%ld]: ", + snprintf (str, STRING_LENGTH, "%-9s %s.%03lu [%ld]: ", syslog_level[level], time_string, + (unsigned long) nowtime.tv_nsec/1000000ul, (long int) getpid ()); /* @@ -227,7 +227,7 @@ out: */ static void send_stored_logs (void) { - char *string; + char **string; char *ptr; int level; size_t i; @@ -237,12 +237,12 @@ static void send_stored_logs (void) log_message(LOG_DEBUG, "sending stored logs"); - for (i = 0; (ssize_t) i != vector_length (log_message_storage); ++i) { - string = - (char *) vector_getentry (log_message_storage, i, NULL); + for (i = 0; i < sblist_getsize (log_message_storage); ++i) { + string = sblist_get (log_message_storage, i); + if (!string || !*string) continue; - ptr = strchr (string, ' ') + 1; - level = atoi (string); + ptr = strchr (*string, ' ') + 1; + level = atoi (*string); #ifdef NDEBUG if (log_level == LOG_CONN && level == LOG_INFO) @@ -255,9 +255,10 @@ static void send_stored_logs (void) #endif log_message (level, "%s", ptr); + safefree(*string); } - vector_delete (log_message_storage); + sblist_free (log_message_storage); log_message_storage = NULL; log_message(LOG_DEBUG, "done sending stored logs"); @@ -18,6 +18,11 @@ void loop_records_init(void) { loop_records = sblist_new(sizeof (struct loop_record), 32); } +void loop_records_destroy(void) { + sblist_free(loop_records); + loop_records = 0; +} + #if 0 static void su_to_str(union sockaddr_union *addr, char *buf) { int af = addr->v4.sin_family; @@ -4,6 +4,7 @@ #include "sock.h" void loop_records_init(void); +void loop_records_destroy(void); void loop_records_add(union sockaddr_union *addr); int connection_loops (union sockaddr_union *addr); @@ -38,6 +38,7 @@ #include "heap.h" #include "filter.h" #include "child.h" +#include "loop.h" #include "log.h" #include "reqs.h" #include "sock.h" @@ -256,6 +257,8 @@ int reload_config (int reload_logging) int ret; struct config_s *c_next = get_next_config(); + log_message (LOG_NOTICE, "Reloading config file"); + if (reload_logging) shutdown_logging (); ret = reload_config_file (config_file, c_next); @@ -264,9 +267,11 @@ int reload_config (int reload_logging) goto done; } + if(config) free_config (config); config = c_next; if (reload_logging) ret = setup_logging (); + log_message (LOG_NOTICE, "Reloading config file finished"); done: return ret; @@ -295,7 +300,8 @@ main (int argc, char **argv) log_message (LOG_NOTICE, "Initializing " PACKAGE " ..."); - if (config_compile_regex()) { + if (config_init()) { + fprintf(stderr, "ERROR: config_init() failed\n"); exit (EX_SOFTWARE); } @@ -336,8 +342,8 @@ main (int argc, char **argv) * HTTP/1.0 request. Also add the Content-Type header since it * goes hand in hand with Content-Length. */ if (is_anonymous_enabled (config)) { - anonymous_insert (config, "Content-Length"); - anonymous_insert (config, "Content-Type"); + anonymous_insert (config, safestrdup("Content-Length")); + anonymous_insert (config, safestrdup("Content-Type")); } if (daemonized == TRUE) { @@ -375,7 +381,7 @@ main (int argc, char **argv) if (geteuid () == 0) change_user (argv[0]); else - log_message (LOG_WARNING, + log_message (LOG_INFO, "Not running as root, so not changing UID/GID."); /* Create log file after we drop privileges */ @@ -388,9 +394,12 @@ main (int argc, char **argv) setup_sig (SIGCHLD, takesig, "SIGCHLD", argv[0]); setup_sig (SIGTERM, takesig, "SIGTERM", argv[0]); + setup_sig (SIGINT, takesig, "SIGINT", argv[0]); if (daemonized) setup_sig (SIGHUP, takesig, "SIGHUP", argv[0]); setup_sig (SIGUSR1, takesig, "SIGUSR1", argv[0]); + loop_records_init(); + /* Start the main loop */ log_message (LOG_INFO, "Starting main loop. Accepting connections."); @@ -400,6 +409,9 @@ main (int argc, char **argv) child_kill_children (SIGTERM); child_close_sock (); + child_free_children(); + + loop_records_destroy(); /* Remove the PID file */ if (config->pidpath != NULL && unlink (config->pidpath) < 0) { @@ -413,6 +425,8 @@ main (int argc, char **argv) filter_destroy (); #endif /* FILTER_ENABLE */ + free_config (config); + shutdown_logging (); return EXIT_SUCCESS; diff --git a/src/mypoll.c b/src/mypoll.c new file mode 100644 index 0000000..495e2c3 --- /dev/null +++ b/src/mypoll.c @@ -0,0 +1,48 @@ +#include "mypoll.h" + +#ifdef HAVE_POLL_H +int mypoll(pollfd_struct* fds, int nfds, int timeout) { + int i, ret; + for(i=0; i<nfds; ++i) if(!fds[i].events) fds[i].fd=~fds[i].fd; + ret = poll(fds, nfds, timeout <= 0 ? timeout : timeout*1000); + for(i=0; i<nfds; ++i) if(!fds[i].events) fds[i].fd=~fds[i].fd; + return ret; +} +#else +int mypoll(pollfd_struct* fds, int nfds, int timeout) { + fd_set rset, wset, *r=0, *w=0; + int i, ret, maxfd=-1; + struct timeval tv = {0}, *t = 0; + + for(i=0; i<nfds; ++i) { + if(fds[i].events & MYPOLL_READ) r = &rset; + if(fds[i].events & MYPOLL_WRITE) w = &wset; + if(r && w) break; + } + if(r) FD_ZERO(r); + if(w) FD_ZERO(w); + for(i=0; i<nfds; ++i) { + if(fds[i].fd > maxfd) maxfd = fds[i].fd; + if(fds[i].events & MYPOLL_READ) FD_SET(fds[i].fd, r); + if(fds[i].events & MYPOLL_WRITE) FD_SET(fds[i].fd, w); + } + + if(timeout >= 0) t = &tv; + if(timeout > 0) tv.tv_sec = timeout; + + ret = select(maxfd+1, r, w, 0, t); + + switch(ret) { + case -1: + case 0: + return ret; + } + + for(i=0; i<nfds; ++i) { + fds[i].revents = 0; + if(r && FD_ISSET(fds[i].fd, r)) fds[i].revents |= MYPOLL_READ; + if(w && FD_ISSET(fds[i].fd, w)) fds[i].revents |= MYPOLL_WRITE; + } + return ret; +} +#endif diff --git a/src/mypoll.h b/src/mypoll.h new file mode 100644 index 0000000..8736015 --- /dev/null +++ b/src/mypoll.h @@ -0,0 +1,31 @@ +#ifndef MYPOLL_H +#define MYPOLL_H + +#include "config.h" + +#ifdef HAVE_POLL_H +#define SELECT_OR_POLL "poll" + +#include <poll.h> +typedef struct pollfd pollfd_struct; + +#define MYPOLL_READ POLLIN +#define MYPOLL_WRITE POLLOUT + +#else + +#define SELECT_OR_POLL "select" +#include <sys/select.h> +typedef struct mypollfd { + int fd; + short events; + short revents; +} pollfd_struct; + +#define MYPOLL_READ (1<<1) +#define MYPOLL_WRITE (1<<2) +#endif + +int mypoll(pollfd_struct* fds, int nfds, int timeout); + +#endif diff --git a/src/orderedmap.c b/src/orderedmap.c new file mode 100644 index 0000000..1818e27 --- /dev/null +++ b/src/orderedmap.c @@ -0,0 +1,115 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#else +# define _ALL_SOURCE +# define _GNU_SOURCE +#endif +#include <string.h> +#include "sblist.h" +#include "orderedmap.h" + +static void orderedmap_destroy_contents(struct orderedmap *o) { + char **p, *q; + size_t i; + htab_value *v; + if(!o) return; + if(o->values) { + while(sblist_getsize(o->values)) { + p = sblist_get(o->values, 0); + if(p) free(*p); + sblist_delete(o->values, 0); + } + sblist_free(o->values); + } + if(o->map) { + i = 0; + while((i = htab_next(o->map, i, &q, &v))) + free(q); + htab_destroy(o->map); + } +} + +struct orderedmap *orderedmap_create(size_t nbuckets) { + struct orderedmap o = {0}, *new; + o.values = sblist_new(sizeof(void*), 32); + if(!o.values) goto oom; + o.map = htab_create(nbuckets); + if(!o.map) goto oom; + new = malloc(sizeof o); + if(!new) goto oom; + memcpy(new, &o, sizeof o); + return new; + oom:; + orderedmap_destroy_contents(&o); + return 0; +} + +void* orderedmap_destroy(struct orderedmap *o) { + orderedmap_destroy_contents(o); + free(o); + return 0; +} + +int orderedmap_append(struct orderedmap *o, const char *key, char *value) { + size_t index; + char *nk, *nv; + nk = nv = 0; + nk = strdup(key); + nv = strdup(value); + if(!nk || !nv) goto oom; + index = sblist_getsize(o->values); + if(!sblist_add(o->values, &nv)) goto oom; + if(!htab_insert(o->map, nk, HTV_N(index))) { + sblist_delete(o->values, index); + goto oom; + } + return 1; +oom:; + free(nk); + free(nv); + return 0; +} + +char* orderedmap_find(struct orderedmap *o, const char *key) { + char **p; + htab_value *v = htab_find(o->map, key); + if(!v) return 0; + p = sblist_get(o->values, v->n); + return p?*p:0; +} + +int orderedmap_remove(struct orderedmap *o, const char *key) { + size_t i; + char *lk; + char *sk; + char **sv; + htab_value *lv, *v = htab_find2(o->map, key, &sk); + if(!v) return 0; + sv = sblist_get(o->values, v->n); + free(*sv); + sblist_delete(o->values, v->n); + i = 0; + while((i = htab_next(o->map, i, &lk, &lv))) { + if(lv->n > v->n) lv->n--; + } + htab_delete(o->map, key); + free(sk); + return 1; +} + +size_t orderedmap_next(struct orderedmap *o, size_t iter, char** key, char** value) { + size_t h_iter; + htab_value* hval; + char **p; + if(iter < sblist_getsize(o->values)) { + h_iter = 0; + while((h_iter = htab_next(o->map, h_iter, key, &hval))) { + if(hval->n == iter) { + p = sblist_get(o->values, iter); + *value = p?*p:0; + return iter+1; + } + } + } + return 0; +} diff --git a/src/orderedmap.h b/src/orderedmap.h new file mode 100644 index 0000000..e3f4b0e --- /dev/null +++ b/src/orderedmap.h @@ -0,0 +1,20 @@ +#ifndef ORDEREDMAP_H +#define ORDEREDMAP_H + +#include <stdlib.h> +#include "sblist.h" +#include "hsearch.h" + +typedef struct orderedmap { + sblist* values; + struct htab *map; +} *orderedmap; + +struct orderedmap *orderedmap_create(size_t nbuckets); +void* orderedmap_destroy(struct orderedmap *o); +int orderedmap_append(struct orderedmap *o, const char *key, char *value ); +char* orderedmap_find(struct orderedmap *o, const char *key); +int orderedmap_remove(struct orderedmap *o, const char *key); +size_t orderedmap_next(struct orderedmap *o, size_t iter, char** key, char** value); + +#endif @@ -32,7 +32,8 @@ #include "buffer.h" #include "conns.h" #include "filter.h" -#include "hashmap.h" +#include "hsearch.h" +#include "orderedmap.h" #include "heap.h" #include "html-error.h" #include "log.h" @@ -42,7 +43,7 @@ #include "stats.h" #include "text.h" #include "utils.h" -#include "vector.h" +#include "sblist.h" #include "reverse-proxy.h" #include "transparent-proxy.h" #include "upstream.h" @@ -50,6 +51,7 @@ #include "conf.h" #include "basicauth.h" #include "loop.h" +#include "mypoll.h" /* * Maximum length of a HTTP line @@ -321,7 +323,7 @@ static int send_ssl_response (struct conn_s *connptr) * build a new request line. Finally connect to the remote server. */ static struct request_s *process_request (struct conn_s *connptr, - hashmap_t hashofheaders) + orderedmap hashofheaders) { char *url; struct request_s *request; @@ -387,10 +389,17 @@ BAD_REQUEST_ERROR: * we'll be closing anyway. */ char *reverse_url; + int reverse_status; - reverse_url = reverse_rewrite_url (connptr, hashofheaders, url); + reverse_url = reverse_rewrite_url (connptr, hashofheaders, url, &reverse_status); if (reverse_url != NULL) { + if (reverse_status == 301) { + char buf[PATH_MAX]; + snprintf (buf, sizeof buf, "Location: %s\r\n", reverse_url); + send_http_headers (connptr, 301, "Moved Permanently", buf); + goto fail; + } safefree (url); url = reverse_url; } else if (config->reverseonly) { @@ -602,7 +611,7 @@ static int add_xtinyproxy_header (struct conn_s *connptr) * can be retrieved and manipulated later. */ static int -add_header_to_connection (hashmap_t hashofheaders, char *header, size_t len) +add_header_to_connection (orderedmap hashofheaders, char *header, size_t len) { char *sep; @@ -620,7 +629,7 @@ add_header_to_connection (hashmap_t hashofheaders, char *header, size_t len) /* Calculate the new length of just the data */ len -= sep - header - 1; - return hashmap_insert (hashofheaders, header, sep, len); + return orderedmap_append (hashofheaders, header, sep); } /* @@ -633,7 +642,7 @@ add_header_to_connection (hashmap_t hashofheaders, char *header, size_t len) /* * Read all the headers from the stream */ -static int get_all_headers (int fd, hashmap_t hashofheaders) +static int get_all_headers (int fd, orderedmap hashofheaders) { char *line = NULL; char *header = NULL; @@ -726,7 +735,7 @@ static int get_all_headers (int fd, hashmap_t hashofheaders) * 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 int remove_connection_headers (orderedmap hashofheaders) { static const char *headers[] = { "connection", @@ -740,12 +749,13 @@ static int remove_connection_headers (hashmap_t hashofheaders) 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) + data = orderedmap_find(hashofheaders, headers[i]); + + if (!data) return 0; + len = strlen(data); + /* * Go through the data line and replace any special characters * with a NULL. @@ -760,7 +770,7 @@ static int remove_connection_headers (hashmap_t hashofheaders) */ ptr = data; while (ptr < data + len) { - hashmap_remove (hashofheaders, ptr); + orderedmap_remove (hashofheaders, ptr); /* Advance ptr to the next token */ ptr += strlen (ptr) + 1; @@ -769,7 +779,7 @@ static int remove_connection_headers (hashmap_t hashofheaders) } /* Now remove the connection header it self. */ - hashmap_remove (hashofheaders, headers[i]); + orderedmap_remove (hashofheaders, headers[i]); } return 0; @@ -779,16 +789,14 @@ static int remove_connection_headers (hashmap_t hashofheaders) * If there is a Content-Length header, then return the value; otherwise, return * a negative number. */ -static long get_content_length (hashmap_t hashofheaders) +static long get_content_length (orderedmap hashofheaders) { - ssize_t len; char *data; long content_length = -1; - len = - hashmap_entry_by_key (hashofheaders, "content-length", - (void **) &data); - if (len > 0) + data = orderedmap_find (hashofheaders, "content-length"); + + if (data) content_length = atol (data); return content_length; @@ -802,10 +810,9 @@ static long get_content_length (hashmap_t hashofheaders) * purposes. */ static int -write_via_header (int fd, hashmap_t hashofheaders, +write_via_header (int fd, orderedmap hashofheaders, unsigned int major, unsigned int minor) { - ssize_t len; char hostname[512]; char *data; int ret; @@ -825,14 +832,14 @@ write_via_header (int fd, hashmap_t hashofheaders, * 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) { + data = orderedmap_find (hashofheaders, "via"); + if (data) { ret = write_message (fd, "Via: %s, %hu.%hu %s (%s/%s)\r\n", data, major, minor, hostname, PACKAGE, VERSION); - hashmap_remove (hashofheaders, "via"); + orderedmap_remove (hashofheaders, "via"); } else { ret = write_message (fd, "Via: %hu.%hu %s (%s/%s)\r\n", @@ -846,7 +853,7 @@ done: /* * Number of buckets to use internally in the hashmap. */ -#define HEADER_BUCKETS 256 +#define HEADER_BUCKETS 32 /* * Here we loop through all the headers the client is sending. If we @@ -855,7 +862,7 @@ done: * - rjkaes */ static int -process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) +process_client_headers (struct conn_s *connptr, orderedmap hashofheaders) { static const char *skipheaders[] = { "host", @@ -866,7 +873,7 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) "upgrade" }; int i; - hashmap_iter iter; + size_t iter; int ret = 0; char *data, *header; @@ -899,7 +906,7 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) * Delete the headers listed in the skipheaders list */ for (i = 0; i != (sizeof (skipheaders) / sizeof (char *)); i++) { - hashmap_remove (hashofheaders, skipheaders[i]); + orderedmap_remove (hashofheaders, skipheaders[i]); } /* Send, or add the Via header */ @@ -919,27 +926,22 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) /* * 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 (config) - || anonymous_search (config, 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; - } + iter = 0; + while((iter = orderedmap_next(hashofheaders, iter, &data, &header))) { + if (!is_anonymous_enabled (config) + || anonymous_search (config, 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; } } } @@ -979,8 +981,8 @@ static int process_server_headers (struct conn_s *connptr) char *response_line; - hashmap_t hashofheaders; - hashmap_iter iter; + orderedmap hashofheaders; + size_t iter; char *data, *header; ssize_t len; int i; @@ -1009,7 +1011,7 @@ retry: goto retry; } - hashofheaders = hashmap_create (HEADER_BUCKETS); + hashofheaders = orderedmap_create (HEADER_BUCKETS); if (!hashofheaders) { safefree (response_line); return -1; @@ -1021,7 +1023,7 @@ retry: 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); + orderedmap_destroy (hashofheaders); safefree (response_line); indicate_http_error (connptr, 503, @@ -1040,7 +1042,7 @@ retry: * Instead we'll free all the memory and return. */ if (connptr->protocol.major < 1) { - hashmap_delete (hashofheaders); + orderedmap_destroy (hashofheaders); safefree (response_line); return 0; } @@ -1067,7 +1069,7 @@ retry: * Delete the headers listed in the skipheaders list */ for (i = 0; i != (sizeof (skipheaders) / sizeof (char *)); i++) { - hashmap_remove (hashofheaders, skipheaders[i]); + orderedmap_remove (hashofheaders, skipheaders[i]); } /* Send, or add the Via header */ @@ -1089,8 +1091,7 @@ retry: /* Rewrite the HTTP redirect if needed */ if (config->reversebaseurl && - hashmap_entry_by_key (hashofheaders, "location", - (void **) &header) > 0) { + (header = orderedmap_find (hashofheaders, "location"))) { /* Look for a matching entry in the reversepath list */ while (reverse) { @@ -1115,7 +1116,7 @@ retry: "Rewriting HTTP redirect: %s -> %s%s%s", header, config->reversebaseurl, (reverse->path + 1), (header + len)); - hashmap_remove (hashofheaders, "location"); + orderedmap_remove (hashofheaders, "location"); } } #endif @@ -1123,19 +1124,15 @@ retry: /* * 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; - } + iter = 0; + while ((iter = orderedmap_next(hashofheaders, iter, &data, &header))) { + + ret = write_message (connptr->client_fd, + "%s: %s\r\n", data, header); + if (ret < 0) + goto ERROR_EXIT; } - hashmap_delete (hashofheaders); + orderedmap_destroy (hashofheaders); /* Write the final blank line to signify the end of the headers */ if (safe_write (connptr->client_fd, "\r\n", 2) < 0) @@ -1144,7 +1141,7 @@ retry: return 0; ERROR_EXIT: - hashmap_delete (hashofheaders); + orderedmap_destroy (hashofheaders); return -1; } @@ -1158,74 +1155,39 @@ ERROR_EXIT: */ 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; - ret = socket_nonblocking (connptr->client_fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the client socket " - "to non-blocking: %s", strerror(errno)); - return; - } - - ret = socket_nonblocking (connptr->server_fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the server socket " - "to non-blocking: %s", strerror(errno)); - return; - } - - 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; + pollfd_struct fds[2] = {0}; + fds[0].fd = connptr->client_fd; + fds[1].fd = connptr->server_fd; if (buffer_size (connptr->sbuffer) > 0) - FD_SET (connptr->client_fd, &wset); + fds[0].events |= MYPOLL_WRITE; if (buffer_size (connptr->cbuffer) > 0) - FD_SET (connptr->server_fd, &wset); + fds[1].events |= MYPOLL_WRITE; if (buffer_size (connptr->sbuffer) < MAXBUFFSIZE) - FD_SET (connptr->server_fd, &rset); + fds[1].events |= MYPOLL_READ; if (buffer_size (connptr->cbuffer) < MAXBUFFSIZE) - FD_SET (connptr->client_fd, &rset); + fds[0].events |= MYPOLL_READ; - ret = select (maxfd, &rset, &wset, NULL, &tv); + ret = mypoll(fds, 2, config->idletimeout); 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); + log_message (LOG_INFO, + "Idle Timeout (after " SELECT_OR_POLL ")"); return; - } else { - continue; - } } else if (ret < 0) { log_message (LOG_ERR, - "relay_connection: select() error \"%s\". " + "relay_connection: " SELECT_OR_POLL "() error \"%s\". " "Closing connection (client_fd:%d, server_fd:%d)", strerror (errno), connptr->client_fd, connptr->server_fd); return; - } else { - /* - * All right, something was actually selected so mark it. - */ - last_access = time (NULL); } - if (FD_ISSET (connptr->server_fd, &rset)) { + if (fds[1].revents & MYPOLL_READ) { bytes_received = read_buffer (connptr->server_fd, connptr->sbuffer); if (bytes_received < 0) @@ -1235,32 +1197,20 @@ static void relay_connection (struct conn_s *connptr) if (connptr->content_length.server == 0) break; } - if (FD_ISSET (connptr->client_fd, &rset) + if ((fds[0].revents & MYPOLL_READ) && read_buffer (connptr->client_fd, connptr->cbuffer) < 0) { break; } - if (FD_ISSET (connptr->server_fd, &wset) + if ((fds[1].revents & MYPOLL_WRITE) && write_buffer (connptr->server_fd, connptr->cbuffer) < 0) { break; } - if (FD_ISSET (connptr->client_fd, &wset) + if ((fds[0].revents & MYPOLL_WRITE) && 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. - */ - ret = socket_blocking (connptr->client_fd); - if (ret != 0) { - log_message(LOG_ERR, - "Failed to set client socket to blocking: %s", - strerror(errno)); - return; - } - while (buffer_size (connptr->sbuffer) > 0) { if (write_buffer (connptr->client_fd, connptr->sbuffer) < 0) break; @@ -1431,7 +1381,7 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request) connptr->server_fd = opensock (cur_upstream->host, cur_upstream->port, - connptr->server_ip_addr); + connptr->server_ip_addr, NULL); if (connptr->server_fd < 0) { log_message (LOG_WARNING, @@ -1487,27 +1437,27 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request) #endif } +/* this function "drains" remaining bytes in the read pipe from + the client. it's usually only called on error before displaying + an error code/page. */ static int get_request_entity(struct conn_s *connptr) { int ret; - fd_set rset, wset; - struct timeval tv; + pollfd_struct fds[1] = {0}; - FD_ZERO (&rset); - FD_SET (connptr->client_fd, &rset); - memcpy(&wset, &rset, sizeof wset); - tv.tv_sec = config->idletimeout; - tv.tv_usec = 0; - ret = select (connptr->client_fd + 1, &rset, &wset, NULL, &tv); + fds[0].fd = connptr->client_fd; + fds[0].events |= MYPOLL_READ; + + ret = mypoll(fds, 1, config->idletimeout); if (ret == -1) { log_message (LOG_ERR, - "Error calling select on client fd %d: %s", + "Error calling " SELECT_OR_POLL " on client fd %d: %s", connptr->client_fd, strerror(errno)); } else if (ret == 0) { log_message (LOG_INFO, "no entity"); - } else if (ret == 1 && FD_ISSET (connptr->client_fd, &rset)) { + } else if (ret == 1 && (fds[0].revents & MYPOLL_READ)) { ssize_t nread; nread = read_buffer (connptr->client_fd, connptr->cbuffer); if (nread < 0) { @@ -1517,14 +1467,12 @@ get_request_entity(struct conn_s *connptr) ret = -1; } else { log_message (LOG_INFO, - "Read request entity of %d bytes", - nread); + "Read request entity of %ld bytes", + (long) nread); ret = 0; } - } else if (ret == 1 && FD_ISSET (connptr->client_fd, &wset) && connptr->connect_method) { - ret = 0; } else { - log_message (LOG_ERR, "strange situation after select: " + log_message (LOG_ERR, "strange situation after " SELECT_OR_POLL ": " "ret = %d, but client_fd (%d) is not readable...", ret, connptr->client_fd); ret = -1; @@ -1533,6 +1481,31 @@ get_request_entity(struct conn_s *connptr) return ret; } +static void handle_connection_failure(struct conn_s *connptr, int got_headers) +{ + /* + * First, get the body if there is one. + * If we don't read all there is from the socket first, + * it is still marked for reading and we won't be able + * to send our data properly. + */ + if (!got_headers && get_request_entity (connptr) < 0) { + log_message (LOG_WARNING, + "Could not retrieve request entity"); + indicate_http_error (connptr, 400, "Bad Request", + "detail", + "Could not retrieve the request entity " + "the client.", NULL); + update_stats (STAT_BADCONN); + } + + if (connptr->error_variables) { + send_http_error_message (connptr); + } else if (connptr->show_stats) { + showstats (connptr); + } +} + /* * This is the main drive for each connection. As you can tell, for the @@ -1543,15 +1516,21 @@ get_request_entity(struct conn_s *connptr) * tinyproxy code, which was confusing, redundant. Hail progress. * - rjkaes */ -void handle_connection (int fd, union sockaddr_union* addr) +void handle_connection (struct conn_s *connptr, union sockaddr_union* addr) { - ssize_t i; - struct conn_s *connptr; + +#define HC_FAIL() \ + do {handle_connection_failure(connptr, got_headers); goto done;} \ + while(0) + + int got_headers = 0, fd = connptr->client_fd; + size_t i; struct request_s *request = NULL; struct timeval tv; - hashmap_t hashofheaders = NULL; + orderedmap hashofheaders = NULL; char sock_ipaddr[IP_LENGTH]; + char sock_ipaddr_alt[IP_LENGTH]=""; char peer_ipaddr[IP_LENGTH]; getpeer_information (addr, peer_ipaddr, sizeof(peer_ipaddr)); @@ -1559,14 +1538,25 @@ void handle_connection (int fd, union sockaddr_union* addr) if (config->bindsame) getsock_ip (fd, sock_ipaddr); + switch (SOCKADDR_UNION_AF(addr)) { + case AF_INET: + if (config->bind_ipv4mapped) + getmapped_ipv6 (config->bind_ipv4mapped, sock_ipaddr, sock_ipaddr_alt); + break; + case AF_INET6: + if (config->bind_ipv6mapped) + getmapped_ipv4 (config->bind_ipv6mapped, sock_ipaddr, sock_ipaddr_alt); + break; + } + log_message (LOG_CONN, config->bindsame ? "Connect (file descriptor %d): %s at [%s]" : "Connect (file descriptor %d): %s", fd, peer_ipaddr, sock_ipaddr); - connptr = initialize_conn (fd, peer_ipaddr, - config->bindsame ? sock_ipaddr : NULL); - if (!connptr) { + if(!conn_init_contents (connptr, peer_ipaddr, + config->bindsame ? sock_ipaddr : NULL, + sock_ipaddr_alt[0] ? sock_ipaddr_alt : NULL)) { close (fd); return; } @@ -1588,7 +1578,7 @@ void handle_connection (int fd, union sockaddr_union* addr) "You tried to connect to the " "machine the proxy is running on", NULL); - goto fail; + HC_FAIL(); } @@ -1599,7 +1589,7 @@ void handle_connection (int fd, union sockaddr_union* addr) "The administrator of this proxy has not configured " "it to service requests from your host.", NULL); - goto fail; + HC_FAIL(); } if (read_request_line (connptr) < 0) { @@ -1608,13 +1598,13 @@ void handle_connection (int fd, union sockaddr_union* addr) "detail", "Server timeout waiting for the HTTP request " "from the client.", NULL); - goto fail; + HC_FAIL(); } /* * The "hashofheaders" store the client's headers. */ - hashofheaders = hashmap_create (HEADER_BUCKETS); + hashofheaders = orderedmap_create (HEADER_BUCKETS); if (hashofheaders == NULL) { update_stats (STAT_BADCONN); indicate_http_error (connptr, 503, "Internal error", @@ -1622,7 +1612,7 @@ void handle_connection (int fd, union sockaddr_union* addr) "An internal server error occurred while processing " "your request. Please contact the administrator.", NULL); - goto fail; + HC_FAIL(); } /* @@ -1636,34 +1626,31 @@ void handle_connection (int fd, union sockaddr_union* addr) "Could not retrieve all the headers from " "the client.", NULL); update_stats (STAT_BADCONN); - goto fail; + HC_FAIL(); } + got_headers = 1; if (config->basicauth_list != NULL) { - ssize_t len; char *authstring; int failure = 1, stathost_connect = 0; - len = hashmap_entry_by_key (hashofheaders, "proxy-authorization", - (void **) &authstring); - - if (len == 0 && config->stathost) { - len = hashmap_entry_by_key (hashofheaders, "host", - (void **) &authstring); - if (len && !strncmp(authstring, config->stathost, strlen(config->stathost))) { - len = hashmap_entry_by_key (hashofheaders, "authorization", - (void **) &authstring); + authstring = orderedmap_find (hashofheaders, "proxy-authorization"); + + if (!authstring && config->stathost) { + authstring = orderedmap_find (hashofheaders, "host"); + if (authstring && !strncmp(authstring, config->stathost, strlen(config->stathost))) { + authstring = orderedmap_find (hashofheaders, "authorization"); stathost_connect = 1; - } else len = 0; + } else authstring = 0; } - if (len == 0) { + if (!authstring) { if (stathost_connect) goto e401; update_stats (STAT_DENIED); indicate_http_error (connptr, 407, "Proxy Authentication Required", "detail", "This proxy requires authentication.", NULL); - goto fail; + HC_FAIL(); } if ( /* currently only "basic" auth supported */ (strncmp(authstring, "Basic ", 6) == 0 || @@ -1678,22 +1665,20 @@ e401: "The administrator of this proxy has not configured " "it to service requests from you.", NULL); - goto fail; + HC_FAIL(); } - hashmap_remove (hashofheaders, "proxy-authorization"); + orderedmap_remove (hashofheaders, "proxy-authorization"); } /* * Add any user-specified headers (AddHeader directive) to the * outgoing HTTP request. */ - for (i = 0; i < vector_length (config->add_headers); i++) { - http_header_t *header = (http_header_t *) - vector_getentry (config->add_headers, i, NULL); + if (config->add_headers) + for (i = 0; i < sblist_getsize (config->add_headers); i++) { + http_header_t *header = sblist_get (config->add_headers, i); - hashmap_insert (hashofheaders, - header->name, - header->value, strlen (header->value) + 1); + orderedmap_append (hashofheaders, header->name, header->value); } request = process_request (connptr, hashofheaders); @@ -1701,24 +1686,25 @@ e401: if (!connptr->show_stats) { update_stats (STAT_BADCONN); } - goto fail; + HC_FAIL(); } connptr->upstream_proxy = UPSTREAM_HOST (request->host); if (connptr->upstream_proxy != NULL) { if (connect_to_upstream (connptr, request) < 0) { - goto fail; + HC_FAIL(); } } else { connptr->server_fd = opensock (request->host, request->port, - connptr->server_ip_addr); + connptr->server_ip_addr, + connptr->server_ip_addr_alt); if (connptr->server_fd < 0) { indicate_http_error (connptr, 500, "Unable to connect", "detail", PACKAGE_NAME " " "was unable to connect to the remote web server.", "error", strerror (errno), NULL); - goto fail; + HC_FAIL(); } log_message (LOG_CONN, @@ -1732,13 +1718,25 @@ e401: if (process_client_headers (connptr, hashofheaders) < 0) { update_stats (STAT_BADCONN); - goto fail; + log_message (LOG_INFO, + "process_client_headers failed: %s. host \"%s\" using " + "file descriptor %d.", strerror(errno), + request->host, + connptr->server_fd); + + HC_FAIL(); } if (!connptr->connect_method || UPSTREAM_IS_HTTP(connptr)) { if (process_server_headers (connptr) < 0) { update_stats (STAT_BADCONN); - goto fail; + log_message (LOG_INFO, + "process_server_headers failed: %s. host \"%s\" using " + "file descriptor %d.", strerror(errno), + request->host, + connptr->server_fd); + + HC_FAIL(); } } else { if (send_ssl_response (connptr) < 0) { @@ -1746,7 +1744,7 @@ e401: "handle_connection: Could not send SSL greeting " "to client."); update_stats (STAT_BADCONN); - goto fail; + HC_FAIL(); } } @@ -1757,34 +1755,10 @@ e401: "and remote client (fd:%d)", connptr->client_fd, connptr->server_fd); - goto done; - -fail: - /* - * First, get the body if there is one. - * If we don't read all there is from the socket first, - * it is still marked for reading and we won't be able - * to send our data properly. - */ - if (get_request_entity (connptr) < 0) { - log_message (LOG_WARNING, - "Could not retrieve request entity"); - indicate_http_error (connptr, 400, "Bad Request", - "detail", - "Could not retrieve the request entity " - "the client.", NULL); - update_stats (STAT_BADCONN); - } - - if (connptr->error_variables) { - send_http_error_message (connptr); - } else if (connptr->show_stats) { - showstats (connptr); - } - done: free_request_struct (request); - hashmap_delete (hashofheaders); - destroy_conn (connptr); + orderedmap_destroy (hashofheaders); + conn_destroy_contents (connptr); return; +#undef HC_FAIL } @@ -24,6 +24,7 @@ #include "common.h" #include "sock.h" +#include "conns.h" /* * Port constants for HTTP (80) and SSL (443) @@ -44,6 +45,6 @@ struct request_s { char *path; }; -extern void handle_connection (int fd, union sockaddr_union* addr); +extern void handle_connection (struct conn_s *, union sockaddr_union* addr); #endif diff --git a/src/reverse-proxy.c b/src/reverse-proxy.c index 32bd2a7..68f04a6 100644 --- a/src/reverse-proxy.c +++ b/src/reverse-proxy.c @@ -34,6 +34,7 @@ void reversepath_add (const char *path, const char *url, struct reversepath **reversepath_list) { struct reversepath *reverse; + size_t l; if (url == NULL) { log_message (LOG_WARNING, @@ -65,8 +66,17 @@ void reversepath_add (const char *path, const char *url, if (!path) reverse->path = safestrdup ("/"); - else - reverse->path = safestrdup (path); + else { + l = strlen (path); + if (l && path[l-1] == '/') + reverse->path = safestrdup (path); + else { + reverse->path = safemalloc (l + 2); + memcpy (reverse->path, path, l); + reverse->path[l] = '/'; + reverse->path[l+1] = 0; + } + } reverse->url = safestrdup (url); @@ -83,10 +93,16 @@ void reversepath_add (const char *path, const char *url, */ struct reversepath *reversepath_get (char *url, struct reversepath *reverse) { + size_t l, lu, lp; while (reverse) { - if (strstr (url, reverse->path) == url) + lu = strlen (url); + lp = strlen (reverse->path); + if (( + (l = lu) == lp-1 || + (l = lp) <= lu + ) && + !memcmp(url, reverse->path, l)) return reverse; - reverse = reverse->next; } @@ -111,28 +127,34 @@ void free_reversepath_list (struct reversepath *reverse) /* * Rewrite the URL for reverse proxying. */ -char *reverse_rewrite_url (struct conn_s *connptr, hashmap_t hashofheaders, - char *url) +char *reverse_rewrite_url (struct conn_s *connptr, orderedmap hashofheaders, + char *url, int *status) { char *rewrite_url = NULL; char *cookie = NULL; char *cookieval; struct reversepath *reverse = NULL; + *status = 0; + /* Reverse requests always start with a slash */ if (*url == '/') { /* First try locating the reverse mapping by request url */ reverse = reversepath_get (url, config->reversepath_list); if (reverse) { - rewrite_url = (char *) - safemalloc (strlen (url) + strlen (reverse->url) + - 1); - strcpy (rewrite_url, reverse->url); - strcat (rewrite_url, url + strlen (reverse->path)); + size_t lu = strlen (url); + size_t lrp = strlen (reverse->path); + if (lrp > lu) { + rewrite_url = safestrdup (reverse->path); + *status = 301; + } else { + rewrite_url = safemalloc ( + strlen (reverse->url) + lu + 1); + sprintf (rewrite_url, "%s%s", reverse->url, url + lrp); + } } else if (config->reversemagic - && hashmap_entry_by_key (hashofheaders, - "cookie", - (void **) &cookie) > 0) { + && (cookie = orderedmap_find (hashofheaders, + "cookie"))) { /* No match - try the magical tracking cookie next */ if ((cookieval = strstr (cookie, REVERSE_COOKIE "=")) diff --git a/src/reverse-proxy.h b/src/reverse-proxy.h index 64b4acd..a9a5bdd 100644 --- a/src/reverse-proxy.h +++ b/src/reverse-proxy.h @@ -22,6 +22,7 @@ #define TINYPROXY_REVERSE_PROXY_H #include "conns.h" +#include "orderedmap.h" struct reversepath { struct reversepath *next; @@ -37,6 +38,7 @@ extern struct reversepath *reversepath_get (char *url, struct reversepath *reverse); void free_reversepath_list (struct reversepath *reverse); extern char *reverse_rewrite_url (struct conn_s *connptr, - hashmap_t hashofheaders, char *url); + orderedmap hashofheaders, char *url, + int *status); #endif @@ -34,6 +34,7 @@ #include "text.h" #include "conf.h" #include "loop.h" +#include "sblist.h" /* * Return a human readable error for getaddrinfo() and getnameinfo(). @@ -68,7 +69,7 @@ bind_socket (int sockfd, const char *addr, int family) n = getaddrinfo (addr, NULL, &hints, &res); if (n != 0) { log_message (LOG_INFO, - "bind_socket: getaddrinfo failed for %s: ", addr, get_gai_error (n)); + "bind_socket: getaddrinfo failed for %s: %s", addr, get_gai_error (n)); return -1; } @@ -87,12 +88,32 @@ bind_socket (int sockfd, const char *addr, int family) return sockfd; } +/** + * Try binding the given socket to supplied addresses, stopping when one succeeds. + */ +static int +bind_socket_list (int sockfd, sblist *addresses, int family) +{ + size_t nb_addresses = sblist_getsize(addresses); + size_t i; + + for (i = 0; i < nb_addresses; i++) { + const char *address = *(const char **)sblist_get(addresses, i); + if (bind_socket(sockfd, address, family) >= 0) { + log_message(LOG_INFO, "Bound to %s", address); + return 0; + } + } + + return -1; +} + /* * Open a connection to a remote host. It's been re-written to use * the getaddrinfo() library function, which allows for a protocol * independent implementation (mostly for IPv4 and IPv6 addresses.) */ -int opensock (const char *host, int port, const char *bind_to) +int opensock (const char *host, int port, const char *bind_to, const char *bind_to_alt) { int sockfd, n; struct addrinfo hints, *res, *ressave; @@ -103,6 +124,10 @@ int opensock (const char *host, int port, const char *bind_to) log_message(LOG_INFO, "opensock: opening connection to %s:%d", host, port); + if (bind_to) { + log_message(LOG_INFO, + "opensock: bind to %s or %s", bind_to, bind_to_alt); + } memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; @@ -130,13 +155,16 @@ int opensock (const char *host, int port, const char *bind_to) /* Bind to the specified address */ if (bind_to) { if (bind_socket (sockfd, bind_to, - res->ai_family) < 0) { + res->ai_family) < 0 && + (!bind_to_alt || + bind_socket (sockfd, bind_to_alt, + res->ai_family) < 0)) { close (sockfd); continue; /* can't bind, so try again */ } - } else if (config->bind_address) { - if (bind_socket (sockfd, config->bind_address, - res->ai_family) < 0) { + } else if (config->bind_addrs) { + if (bind_socket_list (sockfd, config->bind_addrs, + res->ai_family) < 0) { close (sockfd); continue; /* can't bind, so try again */ } @@ -277,7 +305,7 @@ static int listen_on_one_socket(struct addrinfo *ad) * Upon success, the listen-fds are added to the listen_fds list * and 0 is returned. Upon error, -1 is returned. */ -int listen_sock (const char *addr, uint16_t port, vector_t listen_fds) +int listen_sock (const char *addr, uint16_t port, sblist* listen_fds) { struct addrinfo hints, *result, *rp; char portstr[6]; @@ -315,7 +343,7 @@ int listen_sock (const char *addr, uint16_t port, vector_t listen_fds) continue; } - vector_append (listen_fds, &listenfd, sizeof(int)); + sblist_add (listen_fds, &listenfd); /* success */ ret = 0; @@ -361,6 +389,28 @@ int getsock_ip (int fd, char *ipaddr) return 0; } +int getmapped_ipv4 (const char *ipv6mapped_conf, const char *ipaddr, char *ipaddr_mapped) +{ + struct in6_addr addr6; + struct in_addr addr; + + memset(&addr6, 0, sizeof(addr6)); + if ( inet_pton(AF_INET6, ipaddr, &addr6) < 0 ) + return -1; + + memcpy(&addr, addr6.s6_addr + 12, sizeof(addr)); + return inet_ntop(AF_INET, &addr, ipaddr_mapped, IP_LENGTH) == NULL ? -1 : 0; +} + +int getmapped_ipv6 (const char *ipv4mapped_conf, const char *ipaddr, char *ipaddr_mapped) +{ + memset(ipaddr_mapped, 0, IP_LENGTH); + strncpy(ipaddr_mapped, ipv4mapped_conf, IP_LENGTH-1); + strncat(ipaddr_mapped, ipaddr, IP_LENGTH-1-strlen(ipaddr_mapped)); + return 0; +} + + /* * Return the peer's socket information. */ @@ -29,20 +29,36 @@ #define MAXLINE (1024 * 4) #include "common.h" -#include "vector.h" +#include "sblist.h" + +#define SOCKADDR_UNION_AF(PTR) (PTR)->v4.sin_family + +#define SOCKADDR_UNION_LENGTH(PTR) ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? sizeof((PTR)->v4) : ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? sizeof((PTR)->v6) : 0 ) ) + +#define SOCKADDR_UNION_ADDRESS(PTR) ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (void*) &(PTR)->v4.sin_addr : ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (void*) &(PTR)->v6.sin6_addr : (void*) 0 ) ) + +#define SOCKADDR_UNION_PORT(PTR) ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (PTR)->v4.sin_port : ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (PTR)->v6.sin6_port : 0 ) ) union sockaddr_union { struct sockaddr_in v4; struct sockaddr_in6 v6; }; -extern int opensock (const char *host, int port, const char *bind_to); -extern int listen_sock (const char *addr, uint16_t port, vector_t listen_fds); +extern int opensock (const char *host, int port, const char *bind_to, const char *bind_to_alt); +extern int listen_sock (const char *addr, uint16_t port, sblist* listen_fds); extern int socket_nonblocking (int sock); extern int socket_blocking (int sock); extern int getsock_ip (int fd, char *ipaddr); +extern int getmapped_ipv6 (const char *ipv4mapped_conf, const char *ipaddr, char *ipaddr_mapped); +extern int getmapped_ipv4 (const char *ipv6mapped_conf, const char *ipaddr, char *ipaddr_mapped); extern void getpeer_information (union sockaddr_union *addr, char *ipaddr, size_t ipaddr_len); #endif diff --git a/src/stats.c b/src/stats.c index 9f4acc3..1228aa3 100644 --- a/src/stats.c +++ b/src/stats.c @@ -43,7 +43,7 @@ struct stat_s { unsigned long int num_denied; }; -static struct stat_s *stats; +static struct stat_s stats_buf, *stats; static pthread_mutex_t stats_update_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t stats_file_lock = PTHREAD_MUTEX_INITIALIZER; @@ -52,11 +52,7 @@ static pthread_mutex_t stats_file_lock = PTHREAD_MUTEX_INITIALIZER; */ void init_stats (void) { - stats = (struct stat_s *) safemalloc (sizeof (struct stat_s)); - if (!stats) - return; - - memset (stats, 0, sizeof (struct stat_s)); + stats = &stats_buf; } /* @@ -126,7 +122,7 @@ err_minus_one: add_error_variable (connptr, "deniedconns", denied); add_error_variable (connptr, "refusedconns", refused); add_standard_vars (connptr); - send_http_headers (connptr, 200, "Statistic requested"); + send_http_headers (connptr, 200, "Statistic requested", ""); send_html_file (statfile, connptr); fclose (statfile); pthread_mutex_unlock(&stats_file_lock); diff --git a/src/transparent-proxy.c b/src/transparent-proxy.c index 2c1e069..d090ae3 100644 --- a/src/transparent-proxy.c +++ b/src/transparent-proxy.c @@ -53,17 +53,17 @@ static int build_url (char **url, const char *host, int port, const char *path) } int -do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, +do_transparent_proxy (struct conn_s *connptr, orderedmap hashofheaders, struct request_s *request, struct config_s *conf, char **url) { socklen_t length; char *data; size_t ulen = strlen (*url); - ssize_t i; + size_t i; - length = hashmap_entry_by_key (hashofheaders, "host", (void **) &data); - if (length <= 0) { + data = orderedmap_find (hashofheaders, "host"); + if (!data) { union sockaddr_union dest_addr; const void *dest_inaddr; char namebuf[INET6_ADDRSTRLEN+1]; @@ -83,16 +83,14 @@ do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, return 0; } - af = length == sizeof(dest_addr.v4) ? AF_INET : AF_INET6; - if (af == AF_INET) dest_inaddr = &dest_addr.v4.sin_addr; - else dest_inaddr = &dest_addr.v6.sin6_addr; + af = SOCKADDR_UNION_AF(&dest_addr); + dest_inaddr = SOCKADDR_UNION_ADDRESS(&dest_addr); if (!inet_ntop(af, dest_inaddr, namebuf, sizeof namebuf)) goto addr_err; request->host = safestrdup (namebuf); - request->port = ntohs (af == AF_INET ? dest_addr.v4.sin_port - : dest_addr.v6.sin6_port); + request->port = ntohs (SOCKADDR_UNION_PORT(&dest_addr)); request->path = (char *) safemalloc (ulen + 1); strlcpy (request->path, *url, ulen + 1); @@ -102,6 +100,7 @@ do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, "process_request: trans IP %s %s for %d", request->method, *url, connptr->client_fd); } else { + length = strlen (data); request->host = (char *) safemalloc (length + 1); if (sscanf (data, "%[^:]:%hu", request->host, &request->port) != 2) { @@ -122,12 +121,12 @@ do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, return 1; } - for (i = 0; i < vector_length(conf->listen_addrs); i++) { - const char *addr; + for (i = 0; i < sblist_getsize(conf->listen_addrs); i++) { + char **addr; - addr = (char *)vector_getentry(conf->listen_addrs, i, NULL); + addr = sblist_get(conf->listen_addrs, i); - if (addr && strcmp(request->host, addr) == 0) { + if (addr && *addr && strcmp(request->host, *addr) == 0) { log_message(LOG_ERR, "transparent: destination IP %s is local " "on socket fd %d", diff --git a/src/transparent-proxy.h b/src/transparent-proxy.h index 4fc3a4d..b56c446 100644 --- a/src/transparent-proxy.h +++ b/src/transparent-proxy.h @@ -26,11 +26,11 @@ #ifdef TRANSPARENT_PROXY #include "conns.h" -#include "hashmap.h" +#include "orderedmap.h" #include "reqs.h" extern int do_transparent_proxy (struct conn_s *connptr, - hashmap_t hashofheaders, + orderedmap hashofheaders, struct request_s *request, struct config_s *config, char **url); diff --git a/src/upstream.c b/src/upstream.c index 0f7f24f..52dad80 100644 --- a/src/upstream.c +++ b/src/upstream.c @@ -43,34 +43,46 @@ proxy_type_name(proxy_type type) } } + +const char* upstream_build_error_string(enum upstream_build_error ube) { + static const char *emap[] = { + [UBE_SUCCESS] = "", + [UBE_OOM] = "Unable to allocate memory in upstream_build()", + [UBE_USERLEN] = "User / pass in upstream config too long", + [UBE_EDOMAIN] = "Nonsense upstream none rule: empty domain", + [UBE_INVHOST] = "Nonsense upstream rule: invalid host or port", + [UBE_INVPARAMS] = "Nonsense upstream rule: invalid parameters", + [UBE_NETMASK] = "Nonsense upstream rule: failed to parse netmask", + }; + return emap[ube]; +} + /** * Construct an upstream struct from input data. */ -static struct upstream *upstream_build (const char *host, int port, const char *domain, +static struct upstream *upstream_build (const char *host, int port, char *domain, const char *user, const char *pass, - proxy_type type) + proxy_type type, enum upstream_build_error *ube) { - char *ptr; struct upstream *up; + *ube = UBE_SUCCESS; up = (struct upstream *) safemalloc (sizeof (struct upstream)); if (!up) { - log_message (LOG_ERR, - "Unable to allocate memory in upstream_build()"); + *ube = UBE_OOM; return NULL; } up->type = type; - up->host = up->domain = up->ua.user = up->pass = NULL; - up->ip = up->mask = 0; + up->target.type = HST_NONE; + up->host = up->ua.user = up->pass = NULL; if (user) { if (type == PT_HTTP) { char b[BASE64ENC_BYTES((256+2)-1) + 1]; ssize_t ret; ret = basicauth_string(user, pass, b, sizeof b); if (ret == 0) { - log_message (LOG_ERR, - "User / pass in upstream config too long"); + *ube = UBE_USERLEN; return NULL; } up->ua.authstr = safestrdup (b); @@ -83,13 +95,11 @@ static struct upstream *upstream_build (const char *host, int port, const char * if (domain == NULL) { if (type == PT_NONE) { e_nonedomain:; - log_message (LOG_WARNING, - "Nonsense upstream none rule: empty domain"); + *ube = UBE_EDOMAIN; goto fail; } if (!host || !host[0] || port < 1) { - log_message (LOG_WARNING, - "Nonsense upstream rule: invalid host or port"); + *ube = UBE_INVHOST; goto fail; } @@ -103,39 +113,17 @@ static struct upstream *upstream_build (const char *host, int port, const char * if (!domain[0]) goto e_nonedomain; } else { if (!host || !host[0] || !domain[0]) { - log_message (LOG_WARNING, - "Nonsense upstream rule: invalid parameters"); + *ube = UBE_INVPARAMS; goto fail; } up->host = safestrdup (host); up->port = port; } - ptr = strchr (domain, '/'); - if (ptr) { - struct in_addr addrstruct; - - *ptr = '\0'; - if (inet_aton (domain, &addrstruct) != 0) { - up->ip = ntohl (addrstruct.s_addr); - *ptr++ = '/'; - - if (strchr (ptr, '.')) { - if (inet_aton (ptr, &addrstruct) != 0) - up->mask = - ntohl (addrstruct.s_addr); - } else { - up->mask = - ~((1 << (32 - atoi (ptr))) - 1); - } - up->ip = up->ip & up->mask; - } else { - log_message (LOG_WARNING, - "Nonsense upstream rule: failed to parse netmask"); - goto fail; - } - } else { - up->domain = safestrdup (domain); + if (hostspec_parse(domain, &up->target) + || up->target.type == HST_NONE) { + *ube = UBE_NETMASK; + goto fail; } if (type == PT_NONE) @@ -151,7 +139,8 @@ fail: safefree (up->ua.user); safefree (up->pass); safefree (up->host); - safefree (up->domain); + if(up->target.type == HST_STRING) + safefree (up->target.address.string); safefree (up); return NULL; @@ -160,22 +149,24 @@ fail: /* * Add an entry to the upstream list */ -void upstream_add (const char *host, int port, const char *domain, +enum upstream_build_error upstream_add ( + const char *host, int port, char *domain, const char *user, const char *pass, proxy_type type, struct upstream **upstream_list) { struct upstream *up; + enum upstream_build_error ube; - up = upstream_build (host, port, domain, user, pass, type); + up = upstream_build (host, port, domain, user, pass, type, &ube); if (up == NULL) { - return; + return ube; } - if (!up->domain && !up->ip) { /* always add default to end */ + if (up->target.type == HST_NONE) { /* always add default to end */ struct upstream *tmp = *upstream_list; while (tmp) { - if (!tmp->domain && !tmp->ip) { + if (tmp->target.type == HST_NONE) { log_message (LOG_WARNING, "Duplicate default upstream"); goto upstream_cleanup; @@ -184,7 +175,7 @@ void upstream_add (const char *host, int port, const char *domain, if (!tmp->next) { up->next = NULL; tmp->next = up; - return; + return ube; } tmp = tmp->next; @@ -194,14 +185,15 @@ void upstream_add (const char *host, int port, const char *domain, up->next = *upstream_list; *upstream_list = up; - return; + return ube; upstream_cleanup: safefree (up->host); - safefree (up->domain); + if(up->target.type == HST_STRING) + safefree (up->target.address.string); safefree (up); - return; + return ube; } /* @@ -209,34 +201,12 @@ upstream_cleanup: */ struct upstream *upstream_get (char *host, struct upstream *up) { - in_addr_t my_ip = INADDR_NONE; - while (up) { - if (up->domain) { - if (strcasecmp (host, up->domain) == 0) - break; /* exact match */ - - if (up->domain[0] == '.') { - char *dot = strchr (host, '.'); - - if (!dot && !up->domain[1]) - break; /* local host matches "." */ - - while (dot && strcasecmp (dot, up->domain)) - dot = strchr (dot + 1, '.'); + if (up->target.type == HST_NONE) + break; - if (dot) - break; /* subdomain match */ - } - } else if (up->ip) { - if (my_ip == INADDR_NONE) - my_ip = ntohl (inet_addr (host)); - - if ((my_ip & up->mask) == up->ip) - break; - } else { - break; /* No domain or IP, default upstream */ - } + if (hostspec_match(host, &up->target)) + break; up = up->next; } @@ -258,7 +228,8 @@ void free_upstream_list (struct upstream *up) while (up) { struct upstream *tmp = up; up = up->next; - safefree (tmp->domain); + if(tmp->target.type == HST_STRING) + safefree (tmp->target.address.string); safefree (tmp->host); safefree (tmp); } diff --git a/src/upstream.h b/src/upstream.h index c112784..9a2314d 100644 --- a/src/upstream.h +++ b/src/upstream.h @@ -26,6 +26,17 @@ #define _TINYPROXY_UPSTREAM_H_ #include "common.h" +#include "hostspec.h" + +enum upstream_build_error { + UBE_SUCCESS = 0, + UBE_OOM, + UBE_USERLEN, + UBE_EDOMAIN, + UBE_INVHOST, + UBE_INVPARAMS, + UBE_NETMASK, +}; /* * Even if upstream support is not compiled into tinyproxy, this @@ -40,7 +51,6 @@ typedef enum proxy_type { struct upstream { struct upstream *next; - char *domain; /* optional */ char *host; union { char *user; @@ -48,17 +58,19 @@ struct upstream { } ua; char *pass; int port; - in_addr_t ip, mask; + struct hostspec target; proxy_type type; }; #ifdef UPSTREAM_SUPPORT const char *proxy_type_name(proxy_type type); -extern void upstream_add (const char *host, int port, const char *domain, +extern enum upstream_build_error upstream_add ( + const char *host, int port, char *domain, const char *user, const char *pass, proxy_type type, struct upstream **upstream_list); extern struct upstream *upstream_get (char *host, struct upstream *up); extern void free_upstream_list (struct upstream *up); +extern const char* upstream_build_error_string(enum upstream_build_error); #endif /* UPSTREAM_SUPPORT */ #endif /* _TINYPROXY_UPSTREAM_H_ */ diff --git a/src/vector.c b/src/vector.c deleted file mode 100644 index cf9fc75..0000000 --- a/src/vector.c +++ /dev/null @@ -1,214 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes <rjkaes@users.sourceforge.net> - * - * 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 of the License, 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* A vector implementation. The vector can be of an arbitrary length, and - * the data for each entry is an lump of data (the size is stored in the - * vector.) - */ - -#include "main.h" - -#include "heap.h" -#include "vector.h" - -/* - * These structures are the storage for the "vector". Entries are - * stored in struct vectorentry_s (the data and the length), and the - * "vector" structure is implemented as a linked-list. The struct - * vector_s stores a pointer to the first vector (vector[0]) and a - * count of the number of entries (or how long the vector is.) - */ -struct vectorentry_s { - void *data; - size_t len; - - struct vectorentry_s *next; -}; - -struct vector_s { - size_t num_entries; - struct vectorentry_s *head; - struct vectorentry_s *tail; -}; - -/* - * Create an vector. The vector initially has no elements and no - * storage has been allocated for the entries. - * - * A NULL is returned if memory could not be allocated for the - * vector. - */ -vector_t vector_create (void) -{ - vector_t vector; - - vector = (vector_t) safemalloc (sizeof (struct vector_s)); - if (!vector) - return NULL; - - vector->num_entries = 0; - vector->head = vector->tail = NULL; - - return vector; -} - -/* - * Deletes an vector. All the entries when this function is run. - * - * Returns: 0 on success - * negative if a NULL vector is supplied - */ -int vector_delete (vector_t vector) -{ - struct vectorentry_s *ptr, *next; - - if (!vector) - return -EINVAL; - - ptr = vector->head; - while (ptr) { - next = ptr->next; - safefree (ptr->data); - safefree (ptr); - - ptr = next; - } - - safefree (vector); - - return 0; -} - -/* - * Appends an entry into the vector. The entry is an arbitrary - * collection of bytes of _len_ octets. The data is copied into the - * vector, so the original data must be freed to avoid a memory leak. - * The "data" must be non-NULL and the "len" must be greater than zero. - * "pos" is either 0 to prepend the data, or 1 to append the data. - * - * Returns: 0 on success - * negative number if there are errors - */ - -typedef enum { - INSERT_PREPEND, - INSERT_APPEND -} vector_pos_t; - -static int -vector_insert (vector_t vector, - void *data, - size_t len, - vector_pos_t pos) -{ - struct vectorentry_s *entry; - - if (!vector || !data || len <= 0 || - (pos != INSERT_PREPEND && pos != INSERT_APPEND)) - return -EINVAL; - - entry = - (struct vectorentry_s *) safemalloc (sizeof (struct vectorentry_s)); - if (!entry) - return -ENOMEM; - - entry->data = safemalloc (len); - if (!entry->data) { - safefree (entry); - return -ENOMEM; - } - - memcpy (entry->data, data, len); - entry->len = len; - entry->next = NULL; - - /* If there is no head or tail, create them */ - if (!vector->head && !vector->tail) - vector->head = vector->tail = entry; - else if (pos == INSERT_PREPEND) { - /* prepend the entry */ - entry->next = vector->head; - vector->head = entry; - } else { - /* append the entry */ - vector->tail->next = entry; - vector->tail = entry; - } - - vector->num_entries++; - - return 0; -} - -/* - * The following two function are used to make the API clearer. As you - * can see they simply call the vector_insert() function with appropriate - * arguments. - */ -int vector_append (vector_t vector, void *data, size_t len) -{ - return vector_insert (vector, data, len, INSERT_APPEND); -} - -int vector_prepend (vector_t vector, void *data, size_t len) -{ - return vector_insert (vector, data, len, INSERT_PREPEND); -} - -/* - * A pointer to the data at position "pos" (zero based) is returned. - * If the vector is out of bound, data is set to NULL. - * - * Returns: negative upon an error - * length of data if position is valid - */ -void *vector_getentry (vector_t vector, size_t pos, size_t * size) -{ - struct vectorentry_s *ptr; - size_t loc; - - if (!vector || pos >= vector->num_entries) - return NULL; - - loc = 0; - ptr = vector->head; - - while (loc != pos) { - ptr = ptr->next; - loc++; - } - - if (size) - *size = ptr->len; - - return ptr->data; -} - -/* - * Returns the number of entries (or the length) of the vector. - * - * Returns: negative if vector is not valid - * positive length of vector otherwise - */ -ssize_t vector_length (vector_t vector) -{ - if (!vector) - return -EINVAL; - - return vector->num_entries; -} diff --git a/src/vector.h b/src/vector.h deleted file mode 100644 index ef8f953..0000000 --- a/src/vector.h +++ /dev/null @@ -1,75 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes <rjkaes@users.sourceforge.net> - * - * 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 of the License, 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* See 'vector.c' for detailed information. */ - -#ifndef _VECTOR_H -#define _VECTOR_H - -/* - * We're using a typedef here to "hide" the implementation details of the - * vector. Sure, it's a pointer, but the struct is hidden in the C file. - * So, just use the vector_t like it's a cookie. :) - */ -typedef struct vector_s *vector_t; - -/* - * vector_create() takes no arguments. - * vector_delete() is self explanatory. - */ -extern vector_t vector_create (void); -extern int vector_delete (vector_t vector); - -/* - * When you insert a piece of data into the vector, the data will be - * duplicated, so you must free your copy if it was created on the heap. - * The data must be non-NULL and the length must be greater than zero. - * - * Returns: negative on error - * 0 upon successful insert. - */ -extern int vector_append (vector_t vector, void *data, size_t len); -extern int vector_prepend (vector_t vector, void *data, size_t len); - -/* - * A pointer to the data at position "pos" (zero based) is returned and the - * size pointer contains the length of the data stored. - * - * The pointer points to the actual data in the vector, so you have - * the power to modify the data, but do it responsibly since the - * library doesn't take any steps to prevent you from messing up the - * vector. (A better rule is, don't modify the data since you'll - * likely mess up the "length" parameter of the data.) However, DON'T - * try to realloc or free the data; doing so will break the vector. - * - * If "size" is NULL the size of the data is not returned. - * - * Returns: NULL on error - * valid pointer to data - */ -extern void *vector_getentry (vector_t vector, size_t pos, size_t * size); - -/* - * Returns the number of enteries (or the length) of the vector. - * - * Returns: negative if vector is not valid - * positive length of vector otherwise - */ -extern ssize_t vector_length (vector_t vector); - -#endif /* _VECTOR_H */ diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh index 799c9d6..eb1d3c4 100755 --- a/tests/scripts/run_tests.sh +++ b/tests/scripts/run_tests.sh @@ -80,6 +80,9 @@ Listen $TINYPROXY_IP Timeout 600 StatHost "$TINYPROXY_STATHOST_IP" DefaultErrorFile "$TINYPROXY_DATA_DIR/debug.html" +ErrorFile 400 "$TINYPROXY_DATA_DIR/debug.html" +ErrorFile 403 "$TINYPROXY_DATA_DIR/debug.html" +ErrorFile 501 "$TINYPROXY_DATA_DIR/debug.html" StatFile "$TINYPROXY_DATA_DIR/stats.html" Logfile "$TINYPROXY_LOG_FILE" PidFile "$TINYPROXY_PID_FILE" @@ -90,12 +93,17 @@ ViaProxyName "tinyproxy" #DisableViaHeader Yes ConnectPort 443 ConnectPort 563 -FilterURLs On +#FilterURLs On Filter "$TINYPROXY_FILTER_FILE" XTinyproxy Yes +AddHeader "X-My-Header1" "Powered by Tinyproxy" +AddHeader "X-My-Header2" "Powered by Tinyproxy" +AddHeader "X-My-Header3" "Powered by Tinyproxy" EOF - touch $TINYPROXY_FILTER_FILE +cat << 'EOF' > $TINYPROXY_FILTER_FILE +.*\.google-analytics\.com$ +EOF } start_tinyproxy() { @@ -104,6 +112,13 @@ start_tinyproxy() { echo " done (listening on $TINYPROXY_IP:$TINYPROXY_PORT)" } +reload_config() { + echo -n "signaling tinyproxy to reload config..." + pid=$(cat $TINYPROXY_PID_FILE) + #1: SIGHUP + kill -1 $pid && echo "ok" || echo "fail" +} + stop_tinyproxy() { echo -n "killing tinyproxy..." pid=$(cat $TINYPROXY_PID_FILE) @@ -157,7 +172,7 @@ wait_for_some_seconds() { } run_basic_webclient_request() { - $WEBCLIENT_BIN $1 $2 >> $WEBCLIENT_LOG 2>&1 + $WEBCLIENT_BIN $1 $2 > $WEBCLIENT_LOG 2>&1 WEBCLIENT_EXIT_CODE=$? if test "x$WEBCLIENT_EXIT_CODE" = "x0" ; then echo " ok" @@ -165,11 +180,31 @@ run_basic_webclient_request() { echo "ERROR ($WEBCLIENT_EXIT_CODE)" echo "webclient output:" cat $WEBCLIENT_LOG + echo "######################################" fi return $WEBCLIENT_EXIT_CODE } +run_failure_webclient_request() { + ec=$1 + expected_error=$(($1 - 399)) + shift + $WEBCLIENT_BIN "$1" "$2" "$3" "$4" > $WEBCLIENT_LOG 2>&1 + WEBCLIENT_EXIT_CODE=$? + if test "x$WEBCLIENT_EXIT_CODE" = "x$expected_error" ; then + echo " ok, got expected error code $ec" + return 0 + else + echo "ERROR ($WEBCLIENT_EXIT_CODE)" + echo "webclient output:" + cat $WEBCLIENT_LOG + echo "######################################" + fi + + return 1 +} + # "main" provision_initial @@ -179,10 +214,11 @@ provision_webserver start_webserver start_tinyproxy -wait_for_some_seconds 3 +wait_for_some_seconds 1 FAILED=0 +basic_test() { echo -n "checking direct connection to web server..." run_basic_webclient_request "$WEBSERVER_IP:$WEBSERVER_PORT" / test "x$?" = "x0" || FAILED=$((FAILED + 1)) @@ -194,6 +230,26 @@ test "x$?" = "x0" || FAILED=$((FAILED + 1)) echo -n "requesting statspage via stathost url..." run_basic_webclient_request "$TINYPROXY_IP:$TINYPROXY_PORT" "http://$TINYPROXY_STATHOST_IP" test "x$?" = "x0" || FAILED=$((FAILED + 1)) +} + +ext_test() { +echo -n "checking bogus request..." +run_failure_webclient_request 400 --method="BIG FART" "$TINYPROXY_IP:$TINYPROXY_PORT" "http://$WEBSERVER_IP:$WEBSERVER_PORT" +test "x$?" = "x0" || FAILED=$((FAILED + 1)) + +echo -n "testing connection to filtered domain..." +run_failure_webclient_request 403 "$TINYPROXY_IP:$TINYPROXY_PORT" "http://badgoy.google-analytics.com/" +test "x$?" = "x0" || FAILED=$((FAILED + 1)) + +echo -n "requesting connect method to denied port..." +run_failure_webclient_request 403 --method=CONNECT "$TINYPROXY_IP:$TINYPROXY_PORT" "localhost:12345" +test "x$?" = "x0" || FAILED=$((FAILED + 1)) +} + +basic_test +reload_config +basic_test +ext_test echo "$FAILED errors" diff --git a/tests/scripts/run_tests_valgrind.sh b/tests/scripts/run_tests_valgrind.sh index b93371f..72b2115 100755 --- a/tests/scripts/run_tests_valgrind.sh +++ b/tests/scripts/run_tests_valgrind.sh @@ -24,5 +24,5 @@ TESTS_DIR=$SCRIPTS_DIR/.. TESTENV_DIR=$TESTS_DIR/env LOG_DIR=$TESTENV_DIR/var/log -VALGRIND="valgrind -q --tool=memcheck --leak-check=full --log-file=$LOG_DIR/valgrind.log" $SCRIPTS_DIR/run_tests.sh +VALGRIND="valgrind --track-origins=yes --show-leak-kinds=all --tool=memcheck --leak-check=full --log-file=$LOG_DIR/valgrind.log" $SCRIPTS_DIR/run_tests.sh diff --git a/tests/scripts/webclient.pl b/tests/scripts/webclient.pl index 25c0fb7..4dddb69 100755 --- a/tests/scripts/webclient.pl +++ b/tests/scripts/webclient.pl @@ -122,11 +122,16 @@ foreach my $document (@ARGV) { print $remote $request; + $_ = <$remote>; + print; # /* HTTP/1.0 400 Bad Request */ + my($errn) = ($_ =~ /HTTP\/\d\.\d (\d{3})/); + while (<$remote>) { print; } close $remote; + exit($errn - 399) if($errn > 399); } exit(0); |