/* * active port forwarder - software for secure forwarding * Copyright (C) 2003-2007 jeremian * * 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 "make_ssl_handshake.h" #include "http_proxy_server.h" #include "thread_management.h" #include "stats.h" #include "logging.h" #include "network.h" #ifdef HAVE_LIBPTHREAD typedef struct { int sockfd; char *host; char *serv; socklen_t *addrlenp; char type; int limit; char https; SSL_CTX* ctx; } sproxy_argT; /* * Function name: afserver_connect * Description: Connects new http proxy connection to the afserver. * Arguments: sockfd - the file descriptor which will be used for communication with afserver * afserverfd - the afserver's file descriptor * cliaddr - pointer to sockaddr structure * addrlenp - pointer to the length of the sockaddr structure * type - the type of the connection */ int afserver_connect(int* sockfd, int afserverfd, struct sockaddr* cliaddr, socklen_t* addrlenp, char type) { int sockets[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)) { return 1; } if (write(afserverfd, &sockets[0], 4) != 4) { return 2; } if (write(afserverfd, &type, 1) != 1) { return 3; } if (write(afserverfd, addrlenp, 4) != 4) { return 4; } if (write(afserverfd, cliaddr, *addrlenp) != *addrlenp) { return 5; } (*sockfd) = sockets[1]; return 0; } /* * Function name: http_proxy_server * Description: Function responsible for the server part of the http proxy connection. * Arguments: vptr - the structure with all the information needed for http proxy tunnel. */ void* http_proxy_server(void *vptr) { int listenfd, afserverfd; SslFd* connFd; struct sockaddr* cliaddr; char tab[9000]; connection* table; header hdr; int i, n, maxfdp1; fd_set rset, allset; struct timeval tv; int maxclients, tmp; int timeout = 5; socklen_t *addrlenp; socklen_t addrlen; char type, nothttp, https; char *host, *serv, *name = ""; SSL_CTX* ctx; sproxy_argT *proxy_argptr; start_critical_section(); proxy_argptr = (sproxy_argT *) vptr; afserverfd = proxy_argptr->sockfd; host = proxy_argptr->host; serv = proxy_argptr->serv; addrlenp = proxy_argptr->addrlenp; type = proxy_argptr->type; maxclients = proxy_argptr->limit+1; https = proxy_argptr->https; ctx = proxy_argptr->ctx; if (https) { name = "s"; } table = calloc(maxclients, sizeof(connection)); if (table == NULL) { aflog(LOG_T_INIT, LOG_I_CRIT, "http%s proxy: Can't allocate memory... exiting.", name); exit(1); } for (i = 0; i < maxclients; ++i) { table[i].postFd = SslFd_new(); table[i].getFd = SslFd_new(); table[i].tmpFd = SslFd_new(); if ((table[i].postFd == NULL) || (table[i].getFd == NULL) || (table[i].tmpFd == NULL)) { aflog(LOG_T_INIT, LOG_I_CRIT, "http%s proxy: Can't allocate memory... exiting.", name); exit(1); } if (https) { SslFd_set_ssl(table[i].postFd, SSL_new(ctx)); SslFd_set_ssl(table[i].getFd, SSL_new(ctx)); SslFd_set_ssl(table[i].tmpFd, SSL_new(ctx)); if ((SslFd_get_ssl(table[i].postFd) == NULL) || (SslFd_get_ssl(table[i].getFd) == NULL) || (SslFd_get_ssl(table[i].tmpFd) == NULL)) { aflog(LOG_T_INIT, LOG_I_CRIT, "http%s proxy: Can't allocate memory... exiting.", name); exit(1); } } } connFd = SslFd_new(); if (connFd == NULL) { aflog(LOG_T_INIT, LOG_I_CRIT, "http%s proxy: Can't allocate memory... exiting.", name); exit(1); } if (https) { SslFd_set_ssl(connFd, SSL_new(ctx)); if (SslFd_get_ssl(connFd) == NULL) { aflog(LOG_T_INIT, LOG_I_CRIT, "http%s proxy: Can't allocate memory... exiting.", name); exit(1); } } if (ip_listen(&listenfd, host, serv, addrlenp, type)) { aflog(LOG_T_INIT, LOG_I_CRIT, "http%s proxy: Can't listen on %s:%s", name, host, serv); exit(1); } cliaddr = malloc(*addrlenp); addrlen = (*addrlenp); addrlenp = &addrlen; broadcast_condition(); end_critical_section(); FD_ZERO(&allset); FD_SET(listenfd, &allset); maxfdp1 = listenfd + 1; tv.tv_usec = 0; tv.tv_sec = timeout; while (1) { rset = allset; if (select(maxfdp1, &rset, NULL, NULL, &tv) == 0) { aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: timeout", name); tv.tv_sec = timeout; for (i = 0; i < maxclients; ++i) { if ((table[i].state == C_CLOSED) || (table[i].state & C_GET_WAIT) || (table[i].type == 1)) { continue; } aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: send T to table[%d].getfd", name, i); if (table[i].sent_ptr+1 >= 90000) { http_write(https, table[i].getFd, (unsigned char*) "T", 1); table[i].sent_ptr = 0; clear_sslFd(table[i].getFd, &allset); FD_CLR(table[i].sockfd, &allset); table[i].state |= C_GET_WAIT; } else { http_write(https, table[i].getFd, (unsigned char*) "T", 1); table[i].sent_ptr += 1; } } continue; } /* http proxy tunnels */ for (i = 0; i < maxclients; ++i) { if ((table[i].state == C_CLOSED) || (table[i].type == 1)) { continue; } /* sockfd */ if ((!(table[i].state & C_GET_WAIT)) && (FD_ISSET(table[i].sockfd, &rset))) { aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: FD_ISSET(table[%d].sockfd)", name, i); n = read(table[i].sockfd, table[i].buf+5, 8995); if (n <= 0) { http_write(https, table[i].getFd, (unsigned char*) "Q", 1); delete_user(table, i, &allset); continue; } table[i].buf[0] = 'M'; tmp = htonl(n); memcpy(&table[i].buf[1], &tmp, 4); if (table[i].sent_ptr+5 + n >= 90000) { http_write(https, table[i].getFd, (unsigned char*) table[i].buf, 90000 - table[i].sent_ptr); table[i].ptr = 90000 - table[i].sent_ptr; table[i].length = 5+n - table[i].ptr; table[i].sent_ptr = 0; clear_sslFd(table[i].getFd, &allset); FD_CLR(table[i].sockfd, &allset); table[i].state |= C_GET_WAIT; continue; } else { http_write(https, table[i].getFd, (unsigned char*) table[i].buf, n+5); table[i].sent_ptr += n+5; } } /* getfd */ if (FD_ISSET(SslFd_get_fd(table[i].getFd), &rset)) { aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: FD_ISSET(table[%d].getfd)", name, i); delete_user(table, i, &allset); continue; } /* postfd */ if (FD_ISSET(SslFd_get_fd(table[i].postFd), &rset)) { aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: FD_ISSET(table[%d].postfd)", name, i); n = http_read(https, table[i].postFd, (unsigned char*) tab, 9000); if (n != 0) { table[i].received += n; if (read_message(table[i].sockfd, n, &table[i], tab, 0)) { delete_user(table, i, &allset); } } if ((n == 0) || (table[i].received == 90000)) { table[i].received = 0; clear_sslFd(table[i].postFd, &allset); table[i].state |= C_POST_WAIT; if (!(table[i].state & C_GET_WAIT)) { aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: send A to table[%d].getfd", name, i); if (table[i].sent_ptr+1 >= 90000) { http_write(https, table[i].getFd, (unsigned char*) "A", 1); table[i].sent_ptr = 0; clear_sslFd(table[i].getFd, &allset); FD_CLR(table[i].sockfd, &allset); table[i].state |= C_GET_WAIT; } else { http_write(https, table[i].getFd, (unsigned char*) "A", 1); table[i].sent_ptr += 1; } } else { table[i].state |= C_DELAYED_A; } if (table[i].tmpstate == 1) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: get old POST request...", name); table[i].state &= ~C_POST_WAIT; SslFd_swap_content(table[i].postFd, table[i].tmpFd); set_fd(SslFd_get_fd(table[i].postFd), &maxfdp1, &allset); table[i].tmpstate = 0; if (table[i].tmpheader.length) { table[i].received += table[i].tmpheader.length; if (read_message(table[i].sockfd, table[i].tmpheader.length, &table[i], table[i].tmpbuf, table[i].tmpheader.ptr)) { delete_user(table, i, &allset); } } } continue; } } } /* direct tunnels */ for (i = 0; i < maxclients; ++i) { if ((table[i].state == C_OPEN) && (table[i].type == 1)) { if (FD_ISSET(table[i].sockfd, &rset)) { n = read(table[i].sockfd, table[i].buf, 9000); if (n > 0) { writen(SslFd_get_fd(table[i].postFd), (unsigned char*) table[i].buf, n); } else { delete_user(table, i, &allset); continue; } } if (FD_ISSET(SslFd_get_fd(table[i].postFd), &rset)) { n = read(SslFd_get_fd(table[i].postFd), tab, 9000); if (n > 0) { writen(table[i].sockfd, (unsigned char*) tab, n); } else { delete_user(table, i, &allset); continue; } } } } /* listen */ if (FD_ISSET(listenfd, &rset)) { aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: FD_ISSET(listenfd)", name); tmp = accept(listenfd, cliaddr, addrlenp); if (tmp != -1) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: New connection...", name); SslFd_set_fd(connFd, tmp); if (https) { make_ssl_initialize(connFd); if (make_ssl_accept(connFd)) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "https proxy: DENIED by SSL_accept"); close(SslFd_get_fd(connFd)); SSL_clear(SslFd_get_ssl(connFd)); continue; } } } else { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: New connection --> EAGAIN", name); continue; } memset(tab, 0, 9000); nothttp = 0; if (parse_header(connFd, tab, &hdr, https)) { nothttp = 1; aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: no http header...", name); } n = -1; for (i = 0; i < maxclients; ++i) { if (table[i].state == C_CLOSED) { if (n == -1) n = i; } else { if ((!nothttp) && (strcmp(table[i].id, hdr.id) == 0)) { break; } } } if (i < maxclients) { /* the client exists */ aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: the client exist...", name); if (hdr.type == H_TYPE_GET) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: type GET...", name); if (!(table[i].state & C_GET_WAIT)) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: not waiting for GET...", name); table[i].sent_ptr = 0; FD_CLR(table[i].sockfd, &allset); clear_sslFd(table[i].getFd, &allset); table[i].state |= C_GET_WAIT; } if (!(table[i].state & C_OPEN)) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: not opened...", name); if (afserver_connect(&table[i].sockfd, afserverfd, cliaddr, addrlenp, 1)) { memset(tab, 0, 9000); sprintf(tab,"HTTP/1.1 400 Bad Request\r\n\r\n"); n = strlen (tab); http_write(https, connFd, (unsigned char*) tab, n); close_fd((&(connFd->fd))); SSL_clear(SslFd_get_ssl(connFd)); clear_sslFd(table[i].postFd, &allset); table[i].state = C_CLOSED; continue; } table[i].state |= C_OPEN; aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: OPEN...", name); } table[i].state &= ~C_GET_WAIT; table[i].sent_ptr = 0; SslFd_swap_content(table[i].getFd, connFd); set_fd(table[i].sockfd, &maxfdp1, &allset); set_fd(SslFd_get_fd(table[i].getFd), &maxfdp1, &allset); memset(tab, 0, 9000); sprintf(tab, "HTTP/1.1 200 OK\r\n" "Content-Length: 90000\r\n" "Connection: close\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache, no-store, must-revalidate\r\n" "Expires: 0\r\n" "Content-Type: text/html\r\n\r\n"); n = strlen(tab); if (writen(SslFd_get_fd(table[i].getFd), (unsigned char*) tab, n) <= 0) { delete_user(table, i, &allset); continue; } if (table[i].length) { if (writen(SslFd_get_fd(table[i].getFd), (unsigned char*) (table[i].buf+table[i].ptr), table[i].length) <= 0) { delete_user(table, i, &allset); continue; } } table[i].sent_ptr = table[i].length; table[i].ptr = 0; table[i].length = 0; if (table[i].state & C_DELAYED_A) { aflog(LOG_T_MAIN, LOG_I_DDEBUG, "http%s proxy: send A to table[%d].getfd", name, i); http_write(https, table[i].getFd, (unsigned char*) "A", 1); table[i].sent_ptr += 1; table[i].state &= ~C_DELAYED_A; } } else if (hdr.type == H_TYPE_POST) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: type POST...", name); if (!(table[i].state & C_POST_WAIT)) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: unexpected POST request...", name); if (table[i].tmpstate == 0) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: buffering POST request...", name); table[i].tmpstate = 1; SslFd_swap_content(table[i].tmpFd, connFd); memcpy(table[i].tmpbuf, tab, 9000); table[i].tmpheader = hdr; } else { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: no space to buffer POST request (received from first postfd: %d)", name, table[i].received); delete_user(table, i, &allset); } } else { if (hdr.length) { table[i].received += hdr.length; if (read_message(table[i].sockfd, hdr.length, &table[i], tab, hdr.ptr)) { delete_user(table, i, &allset); } } table[i].state &= ~C_POST_WAIT; SslFd_swap_content(table[i].postFd, connFd); set_fd(SslFd_get_fd(table[i].postFd), &maxfdp1, &allset); } } else { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: unrecognized type...", name); delete_user(table, i, &allset); } } else if (n != -1) { /* there are free slots */ aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: there are free slots...", name); if (!nothttp) { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: http header...", name); if (hdr.type == H_TYPE_POST) { /* POST request must be first */ aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: type POST...", name); table[n].state = C_GET_WAIT; memcpy(table[n].id,hdr.id, 9); SslFd_swap_content(table[n].postFd, connFd); set_fd(SslFd_get_fd(table[n].postFd), &maxfdp1, &allset); } else { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: closing this connection... (not POST type)", name); close_fd((&(connFd->fd))); if (https) { SSL_clear(SslFd_get_ssl(connFd)); } continue; } } else { table[n].state = C_OPEN; SslFd_set_fd(table[n].postFd, SslFd_get_fd(connFd)); table[n].type = 1; set_fd(SslFd_get_fd(table[n].postFd), &maxfdp1, &allset); if (afserver_connect(&table[n].sockfd, afserverfd, cliaddr, addrlenp, 0)) { clear_sslFd(table[n].postFd, &allset); table[n].state = C_CLOSED; continue; } set_fd(table[n].sockfd, &maxfdp1, &allset); write(table[n].sockfd, tab, hdr.allreaded); } } else { aflog(LOG_T_MAIN, LOG_I_DEBUG, "http%s proxy: closing this connection... (no free slots)", name); close_fd((&(connFd->fd))); continue; } } } return 0; } /* * Function name: initialize_http_proxy_server * Description: Initializes the thread responsible for http proxy connection. * Arguments: sockfd - the new connection descriptor will be stored here * host - the name of the host on which we will be listening on * serv - the port on which we will be listening on * addrlenp - pointer to the length of the sockaddr structure * type - the type of the connection * limit - the limit for user's connections * https - if the connection should be https instead of http * ctx - the pointer to SSL_CTX structure * Returns: 0 - success, * !0 - failure. */ int initialize_http_proxy_server(int* sockfd, const char *host, const char *serv, socklen_t *addrlenp, const char type, int limit, char https, SSL_CTX* ctx) { int retval; int sockets[2]; pthread_t proxy_thread; static sproxy_argT arg; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)) { return 1; } (*sockfd) = sockets[0]; start_critical_section(); arg.host = (char*) host; arg.serv = (char*) serv; arg.addrlenp = addrlenp; arg.limit = limit; arg.type = (char) type; arg.sockfd = sockets[1]; arg.https = https; arg.ctx = ctx; retval = pthread_create(&proxy_thread, NULL, &http_proxy_server, &arg); wait_for_condition(); end_critical_section(); return retval; } #endif