/* MiddleMan filtering proxy server Copyright (C) 2002 Jason McLaughlin 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "proto.h" time_t last_dns_expire = 0; extern HASH_TABLE *dns_cache; extern char username[]; SOCKLIST socklist[MAXLISTENPORTS]; pthread_mutex_t dns_cache_lock = PTHREAD_MUTEX_INITIALIZER; /* initialize the socket list */ void net_init() { int i; for (i = 0; i < MAXLISTENPORTS; i++) { socklist[i].status = NET_UNUSED; socklist[i].ip = NULL; socklist[i].port = 0; } } /* open a listening socket on a specific port and ip */ int net_listen(int port, char *ip) { int i, x = -1, on = 1; struct sockaddr_in saddr; for (i = 0; i < MAXLISTENPORTS; i++) { if (socklist[i].status == NET_UNUSED) { x = i; break; } } if (x == -1) return -1; socklist[x].fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (socklist[x].fd == -1) return -1; /* this allows the proxy to successfully bind to the socket again when the server is restarted, otherwise it would have to wait for all connections is TIME_WAIT to be cleaned up */ setsockopt(socklist[x].fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); /* TCP_NODELAY prevents the kernel from buffering the socket, which is only useful for interactive connections such as telnet or ssh */ setsockopt(socklist[x].fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); fcntl(socklist[x].fd, F_SETFL, FD_CLOEXEC); memset(&saddr, 0, sizeof(struct sockaddr_in)); saddr.sin_family = AF_INET; saddr.sin_port = htons(port); if (ip == NULL) saddr.sin_addr.s_addr = INADDR_ANY; else saddr.sin_addr.s_addr = inet_addr(ip); i = bind(socklist[x].fd, (struct sockaddr *) &saddr, sizeof(saddr)); if (i == -1) { close(socklist[x].fd); return -1; } i = listen(socklist[x].fd, 25); if (i == -1) { close(socklist[x].fd); return -1; } if (ip != NULL) socklist[x].ip = xstrdup(ip); else socklist[x].ip = NULL; socklist[x].port = port; socklist[x].status = NET_LISTEN; return x; } /* poll on every open listening socket in socklist until timeout is reached (or forever if timeout is -1), accept then return a pointer to a CONNECTION struct */ CONNECTION *net_accept(int timeout) { int i, x = 0, j, newfd; struct sockaddr_in saddr; unsigned int sl = sizeof(struct sockaddr); CONNECTION *connection; struct pollfd pfd[MAXLISTENPORTS]; for (i = 0; i < MAXLISTENPORTS; i++) { if (socklist[i].status == NET_LISTEN) { pfd[x].fd = socklist[i].fd; pfd[x].events = POLLIN; x++; } else break; } j = p_poll(pfd, x, (timeout != -1) ? timeout * 1000 : -1); if (j > 0) { for (i = 0; i < MAXLISTENPORTS; i++) { if (pfd[i].revents & POLLIN) { newfd = accept(socklist[i].fd, (struct sockaddr *) &saddr, &sl); if (newfd == -1) return NULL; connection = xmalloc(sizeof(CONNECTION)); connection->client = sock_new(newfd); connection->server = NULL; connection->port = socklist[i].port; connection->ip = xstrdup(inet_ntoa(saddr.sin_addr)); connection->buffer = FALSE; connection->site_host = NULL; connection->site_port = -1; connection->bypass = connection->obypass = 0; connection->header = NULL; connection->rheader = NULL; connection->keepalive_client = FALSE; connection->keepalive_server = FALSE; connection->request = FALSE; connection->proxy_host = NULL; connection->proxy_type = PROXY_DIRECT; connection->proxy_username = NULL; connection->proxy_password = NULL; connection->proxy_domain = NULL; connection->proxy_auth = NULL; connection->authenticate = FALSE; return connection; } } } return NULL; } /* open a connection to somewhere and return file descriptor */ int net_connect(char *host, int port) { int fd, ret, on = TRUE; HOSTENT *hostent; struct sockaddr_in saddr; if (!host || port <= 0) return ERROR_UNKNOWN; memset(&saddr, 0, sizeof(saddr)); hostent = net_dns(host); if (hostent == NULL) return ERROR_DNS; saddr.sin_addr = *(struct in_addr *)hostent->addr; hostent_free(hostent); saddr.sin_family = AF_INET; saddr.sin_port = htons(port); fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (fd == -1) return ERROR_CONNECT; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); fcntl(fd, F_SETFL, FD_CLOEXEC); ret = connect(fd, (struct sockaddr *) &saddr, sizeof(saddr)); if (ret == -1) { close(fd); return ERROR_CONNECT; } return fd; } /* close a connection and free used memory from structure */ void net_close(CONNECTION * connection) { if (!connection) return; sock_close(connection->client); FREE_AND_NULL(connection->ip); FREE_AND_NULL(connection->proxy_host); FREE_AND_NULL(connection->proxy_username); FREE_AND_NULL(connection->proxy_password); FREE_AND_NULL(connection->proxy_domain); FREE_AND_NULL(connection->proxy_auth); FREE_AND_NULL(connection->site_host); xfree(connection); } /* close all open listening sockets */ void net_listen_closeall() { int i; for (i = 0; i < MAXLISTENPORTS; i++) { if (socklist[i].status == NET_LISTEN) { close(socklist[i].fd); if (socklist[i].ip != NULL) xfree(socklist[i].ip); socklist[i].status = NET_UNUSED; } } } /* load section from XML_LIST */ NETWORK *network_load(NETWORK * network, XML_LIST * xml_list) { NETWORK *network_tmp = network; struct LISTEN_LIST *listen_list = NULL; if (network_tmp == NULL) { network_tmp = xmalloc(sizeof(NETWORK)); network_tmp->listen_list = NULL; network_tmp->id = 0; network = network_tmp; pthread_rwlock_init(&network_tmp->lock, NULL); } else listen_list = network->listen_list; while ((xml_list = xml_section(xml_list, ""))) { XML_LIST_LOOP(xml_list, "") { XML_LIST_CMP(xml_list, "") { listen_list = listen_list_new(listen_list); listen_list->id = network->id++; if (network_tmp->listen_list == NULL) network_tmp->listen_list = listen_list; XML_LIST_LOOP(xml_list, "") { XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) listen_list_insert(listen_list, xml_list->item, NULL); } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) listen_list_insert(listen_list, NULL, xml_list->item); } } } } } return network; } XML_LIST *network_xml(NETWORK * network, XML_LIST * xml_list) { char buf[16], *ptr; struct LISTEN_LIST *ll; if (network == NULL) return xml_list; pthread_rwlock_rdlock(&network->lock); xml_list = xml_list_add(xml_list, "", XML_TAG); ll = network->listen_list; for (ll = network->listen_list; ll; ll = ll->next) { xml_list = xml_list_add(xml_list, "", XML_TAG); xml_list = xml_list_add(xml_list, "", XML_TAG); snprintf(buf, sizeof(buf), "%d", ll->port); xml_list = xml_list_add(xml_list, buf, XML_VALUE); xml_list = xml_list_add(xml_list, "", XML_TAG); if (ll->ip != NULL) { xml_list = xml_list_add(xml_list, "", XML_TAG); ptr = string_to_xml(ll->ip); xml_list = xml_list_add(xml_list, ptr, XML_VALUE); xfree(ptr); xml_list = xml_list_add(xml_list, "", XML_TAG); } xml_list = xml_list_add(xml_list, "", XML_TAG); } xml_list = xml_list_add(xml_list, "", XML_TAG); pthread_rwlock_unlock(&network->lock); return xml_list; } void listen_list_insert(struct LISTEN_LIST *x, char *a, char *b) { if (a != NULL) { FREE_AND_NULL(x->ip); if (strcmp(a, "")) x->ip = xstrdup(a); } if (b != NULL) x->port = atoi(b); } struct LISTEN_LIST *listen_list_delete(struct LISTEN_LIST *x) { struct LISTEN_LIST *start = x; while (start->prev != NULL) start = start->prev; if (x->next != NULL) x->next->prev = x->prev; if (x->prev != NULL) x->prev->next = x->next; else start = start->next; FREE_AND_NULL(x->ip); FREE_AND_NULL(x->proxy); xfree(x); return start; } struct LISTEN_LIST *listen_list_new(struct LISTEN_LIST *x) { if (x == NULL) { x = xmalloc(sizeof(struct LISTEN_LIST)); x->prev = NULL; } else { while (x->next != NULL) x = x->next; x->next = xmalloc(sizeof(struct LISTEN_LIST)); x->next->prev = x; x = x->next; } x->port = -1; x->ip = NULL; x->next = NULL; return x; } /* free memory allocated by network_load */ void network_free(NETWORK * network) { struct LISTEN_LIST *listen_list, *tmp_list; if (!network) return; listen_list = network->listen_list; while (listen_list != NULL) { tmp_list = listen_list->next; if (listen_list->ip != NULL) xfree(listen_list->ip); if (listen_list->proxy != NULL) xfree(listen_list->proxy); xfree(listen_list); listen_list = tmp_list; } pthread_rwlock_destroy(&network->lock); xfree(network); } /* look through network->listen_list linked list and bind to listening ports */ void network_check(NETWORK * network) { int ret; struct LISTEN_LIST *listen_list; if (!network) return; listen_list = network->listen_list; pthread_rwlock_rdlock(&network->lock); while (listen_list != NULL) { if (listen_list->port != -1) { ret = net_listen(listen_list->port, listen_list->ip); if (ret != -1) putlog(MMLOG_NETWORK, "listening on %s:%d", (listen_list->ip != NULL) ? listen_list->ip : "0.0.0.0", listen_list->port); else putlog(MMLOG_NETWORK, "failed to bind to %s:%d", (listen_list->ip != NULL) ? listen_list->ip : "0.0.0.0", listen_list->port); } listen_list = listen_list->next; } pthread_rwlock_unlock(&network->lock); } /* relay anything from one socket to another, and vice versa */ int net_proxy(CONNECTION * connection, int len) { int x, bytes = 0; char buf[BLOCKSIZE]; struct pollfd pfd[2]; SOCKET *sock1, *sock2; sock1 = connection->client; sock2 = connection->server; pfd[0].fd = sock1->fd; pfd[0].events = POLLIN; pfd[1].fd = sock2->fd; pfd[1].events = POLLIN; while (1) { x = p_poll(pfd, 2, (sock1->inbuf_len != 0 || sock2->inbuf_len != 0) ? 0 : -1); if (sock1->inbuf_len != 0 || pfd[0].revents & POLLIN) { x = sock_read(sock1, buf, (len != -1) ? (len - bytes < sizeof(buf)) ? len - bytes : sizeof(buf) : sizeof(buf)); if (x > 0) sock_write(sock2, buf, x); else break; bytes += x; } else if (pfd[0].revents & (POLLHUP | POLLERR)) break; if (sock1->inbuf_len != 0 || pfd[1].revents & POLLIN) { x = sock_read(sock2, buf, (len != -1) ? (len - bytes < sizeof(buf)) ? len - bytes : sizeof(buf) : sizeof(buf)); if (x > 0) sock_write(sock1, buf, x); else break; bytes += x; } else if (pfd[1].revents & (POLLHUP | POLLERR)) break; if (len != -1 && bytes == len) break; } return bytes; } /* tranfer data one way between two sockets */ int net_transfer(CONNECTION * connection, int flags, int bytes) { int x; char buf[BLOCKSIZE]; struct pollfd pfd[2]; SOCKET *sock1, *sock2; sock1 = (flags == SERVER) ? connection->server : connection->client; sock2 = (flags == SERVER) ? connection->client : connection->server; pfd[0].fd = sock1->fd; pfd[0].events = POLLIN; pfd[1].fd = sock2->fd; pfd[1].events = POLLIN; while (bytes == -1 || bytes > 0) { x = p_poll(pfd, 2, (sock1->inbuf_len != 0) ? 0 : -1); if (sock1->inbuf_len != 0 || pfd[0].revents & POLLIN) { x = sock_read(sock1, buf, (bytes != -1) ? (bytes < sizeof(buf)) ? bytes : sizeof(buf) : sizeof(buf)); if (bytes != -1) bytes -= x; if (x > 0) { x = sock_write(sock2, buf, x); if (x == -1) break; } else break; } else if (pfd[0].revents & (POLLHUP | POLLERR)) break; if (pfd[1].revents & POLLIN) { x = sock_read(sock2, NULL, -1); if (x <= 0) break; } else if (pfd[1].revents & (POLLHUP | POLLERR)) break; } return bytes; } int putsock(SOCKET * sock, char *format, ...) { char buf[8096]; va_list va; va_start(va, format); vsnprintf(buf, sizeof(buf), format, va); va_end(va); return sock_write(sock, buf, strlen(buf)); } void net_filebuf_read(FILEBUF * filebuf, CONNECTION * connection, int flags, int bytes) { int x; char buf[BLOCKSIZE]; struct pollfd pfd[2]; SOCKET *sock1, *sock2; sock1 = (flags == SERVER) ? connection->server : connection->client; sock2 = (flags == SERVER) ? connection->client : connection->server; pfd[0].fd = sock1->fd; pfd[0].events = POLLIN; if (sock2 != NULL) { pfd[1].fd = sock2->fd; pfd[1].events = POLLIN; } else pfd[1].revents = 0; while (bytes == -1 || bytes > 0) { x = p_poll(pfd, (sock2 != NULL) ? 2 : 1, (sock1->inbuf_len != 0) ? 0 : -1); if (sock1->inbuf_len != 0 || pfd[0].revents & POLLIN) { x = sock_read(sock1, buf, (bytes != -1) ? (bytes < sizeof(buf)) ? bytes : sizeof(buf) : sizeof(buf)); if (x > 0) { if (filebuf != NULL) filebuf_add(filebuf, buf, x); if (bytes != -1) bytes -= x; } else break; } else if (pfd[0].revents & (POLLHUP | POLLERR)) break; if (pfd[1].revents & POLLIN) { x = sock_read(sock2, NULL, -1); if (x <= 0) break; } else if (pfd[1].revents & (POLLHUP | POLLERR)) break; } } int net_filebuf_send(FILEBUF * filebuf, CONNECTION * connection, int flags) { SOCKET *sock; sock = (flags == SERVER) ? connection->server : connection->client; return sock_write(sock, filebuf->data, filebuf->size); } /* perform a dns lookup, using cached response from a previous lookup if possible */ HOSTENT *net_dns(char *host) { time_t t; char *string, hst[128], buf[24]; HOSTENT *hostent; pthread_mutex_lock(&dns_cache_lock); t = time(NULL); s_strncpy(hst, host, 128); string_tolower(hst); if (last_dns_expire == 0) last_dns_expire = t; if (t - last_dns_expire >= DNS_EXPIRE) { putlog(MMLOG_DEBUG, "dns cache expire: entry > %d seconds", DNS_EXPIRE); hash_expire(dns_cache, DNS_EXPIRE); last_dns_expire = t; } if (isip(host)) { hostent = xmalloc(sizeof(HOSTENT)); hostent->addr = xmalloc(sizeof(struct in_addr)); hostent->len = sizeof(struct in_addr); inet_aton(host, hostent->addr); } else { string = hash_search(dns_cache, hst); if (string == NULL) { pthread_mutex_unlock(&dns_cache_lock); hostent = p_gethostbyname(host); if (hostent == NULL) return NULL; pthread_mutex_lock(&dns_cache_lock); s_strncpy(buf, inet_ntoa(*hostent->addr), sizeof(buf)); hash_insert(dns_cache, hst, xstrdup(buf)); putlog(MMLOG_DEBUG, "dns cache insert: %s -> %s", hst, buf); } else { putlog(MMLOG_DEBUG, "dns cache hit: %s -> %s", hst, string); hostent = xmalloc(sizeof(HOSTENT)); hostent->addr = xmalloc(sizeof(struct in_addr)); hostent->len = sizeof(struct in_addr); inet_aton(string, hostent->addr); } } pthread_mutex_unlock(&dns_cache_lock); return hostent; } /* connect through socks4 server */ int net_socks4(CONNECTION * connection, char *host, int port) { int ret; char buf[64]; HOSTENT *hostent; hostent = net_dns(host); if (hostent == NULL) return ERROR_DNS; ret = snprintf(buf, sizeof(buf), "\004\001%c%c%c%c%c%c%s", (port >> 8) % 256, port % 256, '0', '0', '0', '0', username); memcpy(&buf[4], hostent->addr, hostent->len); hostent_free(hostent); sock_write(connection->server, buf, ret + 1); /* come on.. what are the changes we won't get the packet in one read() ? :) */ sock_read(connection->server, buf, 8); switch (buf[1]) { case SOCKS_GRANTED: ret = TRUE; break; case SOCKS_FAILED: ret = ERROR_CONNECT; break; case SOCKS_BADIDENT: case SOCKS_NOIDENT: ret = ERROR_AUTH; break; default: ret = ERROR_UNKNOWN; } return ret; }