/** * This function defines the request type with the hostname and port based on the URI gathered * from the buffer. * * This is for example used for * CONNECT IP:PORT HTTP/1.0 * types of requests. * * If :PORT is not found, then req.http_infos.port is left untouched (must be defined priorly). * * @return 0 on success, -1 if error */ static int get_hostname_from_uri(request_t* req, int offset) { char *ptr, *buf; buf = req->data + offset; /* isolate the whole block 'IP:PORT' */ ptr = strchr(buf, ' '); if (!ptr){ xlog(LOG_ERROR, "%s\n", "Invalid URI block"); return -1; } *ptr = '\0'; buf = proxenet_xstrdup2(buf); /* host and port */ ptr = strchr(buf, ':'); if (ptr){ /* explicit port */ req->http_infos.port = (unsigned short)atoi(ptr+1); *ptr = '\0'; } req->http_infos.hostname = proxenet_xstrdup2(buf); proxenet_xfree(buf); return 0; }
/** * Look up for a header in a request. If found, this function will allocate a buffer containing * the header value (after the ': ') until a line RET (CR/LF) is found. * If not found, it returns NULL. * * @note if found, the allocated buffer *MUST* be free-ed by caller. * @return a pointer to a malloc-ed buffer with the header value, or NULL if not found. */ static char* get_header_by_name(char* request, const char* header_name) { char *ptr, *ptr2, c, *header_value; /* get header */ ptr = strstr(request, header_name); if(!ptr){ return NULL; } /* move to start of hostname */ ptr += strlen(header_name); ptr2 = ptr; /* get the end of header line */ for(; *ptr2 && *ptr2!='\r' && *ptr2!='\n'; ptr2++); c = *ptr2; *ptr2 = '\0'; /* copy the value */ header_value = proxenet_xstrdup2(ptr); *ptr2 = c; proxenet_strip(header_value); return header_value; }
/** * Look up for a header in a request. If found, this function will allocate a buffer containing * the header value (after the ': ') until a line RET (CR/LF) is found. * If not found, it returns NULL. * * @note if found, the allocated buffer *MUST* be free-ed by caller. * @return a pointer to a malloc-ed buffer with the header value, or NULL if not found. */ static char* get_header_by_name(char* request, const char* header_name) { char *ptr, *ptr2, c, *header_value; /* get header */ ptr = strstr(request, header_name); if(!ptr){ xlog(LOG_ERROR, "Header '%s' not found\n", header_name); return NULL; } /* move to start of hostname */ ptr += strlen(header_name); ptr2 = ptr; /* get the end of header line */ for(; *ptr2 && *ptr2!='\r' && *ptr2!='\n'; ptr2++); c = *ptr2; *ptr2 = '\0'; /* copy the value */ header_value = proxenet_xstrdup2(ptr); if (!header_value){ xlog(LOG_ERROR, "strdup(header '%s') failed.\n", header_name); return NULL; } *ptr2 = c; return header_value; }
static int get_hostname_from_header(request_t *req) { char *ptr, *header; header = get_header_by_name(req->data, "Host:"); if (!header){ return -1; } /* if port number, copy it */ ptr = strchr(header, ':'); if (ptr){ *ptr = '\0'; req->http_infos.hostname = proxenet_xstrdup2(header); req->http_infos.port = (unsigned short)atoi(ptr+1); } else { req->http_infos.hostname = proxenet_xstrdup2(header); } proxenet_xfree(header); return 0; }
/** * Check that an argument is a valid file path by * - solving the path * - ensuring result is read accessible * * @param param is the parameter to validate * @return the real (no symlink) full path of the file it is valid * @return NULL in any other case */ static char* cfg_get_valid_file(char* param) { char buf[PATH_MAX+1]={0, }; if (!realpath(param, buf)){ xlog(LOG_CRITICAL, "realpath(%s) failed: %s\n", param, strerror(errno)); return NULL; } if ( !is_readable_file(buf) ){ xlog(LOG_CRITICAL, "Failed to read private key '%s'\n", param); return NULL; } return proxenet_xstrdup2(buf); }
/** * This function updates all the fields of the current request_t with the new values found in the * request. Since those values will be useful many times, they are strdup-ed in a specific structure * (http_infos_t). Those values *must* be free-ed later on. * * @return 0 if successful, -1 if any error occurs. */ int parse_http_request(request_t *req) { char *ptr, *buf, c; int offset; buf = req->data; /* method */ ptr = strchr(buf, ' '); if (!ptr){ xlog(LOG_ERROR, "%s\n", "Cannot find HTTP method in request"); if (cfg->verbose) xlog(LOG_ERROR, "Buffer sent:\n%s\n", buf); return -1; } c = *ptr; *ptr = '\0'; req->http_infos.method = proxenet_xstrdup2(buf); if (!req->http_infos.method){ xlog(LOG_ERROR, "%s\n", "strdup(method) failed, cannot pursue..."); return -1; } *ptr = c; req->http_infos.proto_type = HTTP; if (!strcmp(req->http_infos.method, "CONNECT")){ /* * We can receive a CONNECT if * - the connection uses HTTPS * - the client tries to upgrade to WebSocket/Secure WebSocket */ char *upgrade_header = get_header_by_name(buf, "Upgrade:"); if (upgrade_header){ if (strcmp(upgrade_header, "WebSocket")==0){ xlog(LOG_INFO, "%s\n", "Upgrading to WebSocket"); req->http_infos.proto_type = WS; req->is_ssl = false; req->http_infos.proto = WS_STRING; req->http_infos.port = HTTP_DEFAULT_PORT; req->http_infos.proto_type = WS; } proxenet_xfree(upgrade_header); } else { req->is_ssl = true; req->http_infos.proto = HTTPS_STRING; req->http_infos.port = HTTPS_DEFAULT_PORT; req->http_infos.proto_type = HTTPS; } offset = ptr - buf + 1; if( get_hostname_from_uri(req, offset) < 0 ){ xlog(LOG_ERROR, "%s\n", "Failed to get hostname (URI)"); goto failed_hostname; } req->http_infos.path = proxenet_xstrdup2("/"); req->http_infos.version = proxenet_xstrdup2("HTTP/1.0"); req->http_infos.uri = get_request_full_uri(req); if(!req->http_infos.uri){ xlog(LOG_ERROR, "%s\n", "get_request_full_uri() failed"); goto failed_uri; } return 0; } /* hostname and port */ if (req->is_ssl){ req->http_infos.port = HTTPS_DEFAULT_PORT; req->http_infos.proto = HTTPS_STRING; } else { req->http_infos.port = HTTP_DEFAULT_PORT; req->http_infos.proto = HTTP_STRING; } if( get_hostname_from_header(req) < 0 ){ xlog(LOG_ERROR, "%s\n", "Failed to get hostname (Host header)"); goto failed_hostname; } /* path */ buf = ptr+1; if (!strncmp(buf, HTTP_PROTO_STRING, strlen(HTTP_PROTO_STRING))){ buf = strchr(buf + 8, '/'); } ptr = strchr(buf, ' '); if (!ptr){ xlog(LOG_ERROR, "%s\n", "Cannot find HTTP path in request"); goto failed_path; } c = *ptr; *ptr = '\0'; req->http_infos.path = proxenet_xstrdup2(buf); if (!req->http_infos.path){ xlog(LOG_ERROR, "%s\n", "strdup(path) failed, cannot pursue..."); goto failed_path; } *ptr = c; buf = ptr+1; /* version */ ptr = strchr(req->data, '\r'); if (!ptr){ xlog(LOG_ERROR, "%s\n", "Cannot find HTTP version"); goto failed_version; } c = *ptr; *ptr = '\0'; req->http_infos.version = proxenet_xstrdup2(buf); if (!req->http_infos.version){ xlog(LOG_ERROR, "%s\n", "strdup(version) failed, cannot pursue..."); goto failed_version; } *ptr = c; /* refresh uri */ req->http_infos.uri = get_request_full_uri(req); if(!req->http_infos.uri){ xlog(LOG_ERROR, "%s\n", "get_request_full_uri() failed"); goto failed_uri; } if (cfg->verbose) { xlog(LOG_INFO, "New request %d to '%s'\n", req->id, req->http_infos.uri); if (cfg->verbose > 1) { xlog(LOG_INFO, "Request HTTP information:\n" "method=%s\n" "proto=%s\n" "hostname=%s\n" "port=%d\n" "path=%s\n" "version=%s\n" , req->http_infos.method, req->http_infos.proto, req->http_infos.hostname, req->http_infos.port, req->http_infos.path, req->http_infos.version); } } return 0; failed_uri: proxenet_xfree(req->http_infos.version); failed_version: proxenet_xfree(req->http_infos.path); failed_path: proxenet_xfree(req->http_infos.hostname); failed_hostname: proxenet_xfree(req->http_infos.method); return -1; }
/** * Checks command line arguments once and for all. Handles (de)allocation for config_t structure * * @return 0 if successfully parsed and allocated * @return -1 */ static int parse_options (int argc, char** argv) { int curopt, curopt_idx; char *logfile, *plugin_path; char *keyfile, *keyfile_pwd, *certfile; char *sslcli_keyfile, *sslcli_keyfile_pwd, *sslcli_certfile, *sslcli_domain; char *proxy_host, *proxy_port; char *intercept_pattern; bool use_socks_proxy; proxy_host = proxy_port = NULL; use_socks_proxy = false; const struct option long_opts[] = { { "version", 0, 0, 'V' }, { "help", 0, 0, 'h' }, { "verbose", 0, 0, 'v' }, { "daemon", 0, 0, 'd' }, { "no-color", 0, 0, 'n' }, { "plugins", 1, 0, 'x' }, { "logfile", 1, 0, 'l' }, { "intercept-only", 0, 0, 'E' }, { "intercept-except", 0, 0, 'I' }, { "match", 1, 0, 'm' }, { "nb-threads", 1, 0, 't' }, { "bind", 1, 0, 'b' }, { "port", 1, 0, 'p' }, { "proxy-host", 1, 0, 'X' }, { "proxy-port", 1, 0, 'P' }, { "use-socks", 0, 0, 'D' }, { "certfile", 1, 0, 'c' }, { "keyfile", 1, 0, 'k' }, { "keyfile-passphrase", 1, 0, 'K' }, { "sslcli-certfile", 1, 0, 'z' }, { "sslcli-domain", 1, 0, 'Z' }, { "sslcli-keyfile", 1, 0, 'y' }, { "sslcli-keyfile-passphrase", 1, 0, 'Y' }, { "ie-compat", 0, 0, 'i'}, { "no-ssl-intercept", 0, 0, 'N'}, { 0, 0, 0, 0 } }; cfg->iface = CFG_DEFAULT_BIND_ADDR; cfg->port = CFG_DEFAULT_BIND_PORT; cfg->logfile_fd = CFG_DEFAULT_OUTPUT; cfg->nb_threads = CFG_DEFAULT_NB_THREAD; cfg->use_color = true; cfg->ip_version = CFG_DEFAULT_IP_VERSION; cfg->try_exit = 0; cfg->try_exit_max = CFG_DEFAULT_TRY_EXIT_MAX; cfg->daemon = false; cfg->intercept_mode = INTERCEPT_ONLY; intercept_pattern = CFG_DEFAULT_INTERCEPT_PATTERN; plugin_path = CFG_DEFAULT_PLUGINS_PATH; logfile = NULL; certfile = CFG_DEFAULT_SSL_CERTFILE; keyfile = CFG_DEFAULT_SSL_KEYFILE; keyfile_pwd = CFG_DEFAULT_SSL_KEYFILE_PWD; cfg->certsdir = CFG_DEFAULT_SSL_CERTSDIR; cfg->certskey = CFG_DEFAULT_SSL_CERTSKEY; cfg->certskey_pwd = CFG_DEFAULT_SSL_CERTSPWD; sslcli_certfile = NULL; sslcli_domain = CFG_DEFAULT_SSL_CLIENT_DOMAIN; sslcli_keyfile = NULL; sslcli_keyfile_pwd = CFG_DEFAULT_SSL_KEYFILE_PWD; cfg->ie_compat = false; cfg->ssl_intercept = true; /* parse command line arguments */ while (1) { curopt_idx = 0; curopt = getopt_long (argc,argv, "dn46Vhvb:p:l:t:c:k:K:x:X:P:z:y:Y:IEm:NiD", long_opts, &curopt_idx); if (curopt == -1) break; switch (curopt) { case 'v': cfg->verbose++; break; case 'b': cfg->iface = optarg; break; case 'p': cfg->port = optarg; break; case 'l': logfile = optarg; break; case 't': cfg->nb_threads = (unsigned short)atoi(optarg); break; case 'X': proxy_host = optarg; proxy_port = CFG_DEFAULT_PROXY_PORT; break; case 'P': proxy_port = optarg; break; case 'D': use_socks_proxy = true; break; case 'c': certfile = optarg; break; case 'k': keyfile = optarg; break; case 'K': keyfile_pwd = optarg; break; case 'h': help(); break; case 'V': version(true); break; case 'n': cfg->use_color = false; break; case '4': cfg->ip_version = AF_INET; break; case '6': cfg->ip_version = AF_INET6; break; case 'x': plugin_path = optarg; break; case 'd': cfg->daemon = true; break; case 'I': cfg->intercept_mode = INTERCEPT_ONLY; break; case 'E': cfg->intercept_mode = INTERCEPT_EXCEPT; break; case 'm': intercept_pattern = optarg; break; case 'z': sslcli_certfile = optarg; break; case 'Z': sslcli_domain = optarg; break; case 'y': sslcli_keyfile = optarg; break; case 'Y': sslcli_keyfile_pwd = optarg; break; case 'N': cfg->ssl_intercept = false; break; case 'i': cfg->ie_compat = true; xlog(LOG_WARNING, "%s\n", "Enabling IE compatibility mode."); xlog(LOG_WARNING, "%s\n", "This mode should not be used with anything but IE <10."); break; case '?': default: usage (EXIT_FAILURE); } curopt_idx = 0; } /* validate command line arguments */ /* logfile validation : if a logfile is given, use its FILE* for output */ if (logfile) { cfg->logfile = realpath(logfile, NULL); if (cfg->logfile == NULL){ xlog(LOG_CRITICAL, "realpath(logfile) failed: %s\n", strerror(errno)); return -1; } if(is_readable_file(cfg->logfile)) { cfg->logfile_fd = fopen(cfg->logfile, "w"); if(!cfg->logfile_fd) { cfg->logfile_fd = stderr; xlog(LOG_CRITICAL, "[-] Failed to open '%s': %s\n", cfg->logfile, strerror(errno)); return -1; } } } /* check if nb of threads is in boundaries */ if (cfg->nb_threads > MAX_THREADS) { fprintf(stderr, "Thread number invalid. Setting to default.\n"); cfg->nb_threads = CFG_DEFAULT_NB_THREAD; } /* setting the interception mode */ cfg->intercept_pattern = proxenet_xstrdup2(intercept_pattern); if(!cfg->intercept_pattern) return -1; if(cfg->verbose) { switch(cfg->intercept_mode){ case INTERCEPT_ONLY: xlog(LOG_INFO, "Intercepting only matching '%s'\n", cfg->intercept_pattern); break; case INTERCEPT_EXCEPT: xlog(LOG_INFO, "Intercepting everything except matching '%s'\n", cfg->intercept_pattern); break; } } /* check plugins path */ if (!is_valid_plugin_path(plugin_path, &cfg->plugins_path, &cfg->autoload_path)) return -1; #ifdef DEBUG xlog(LOG_DEBUG, "Valid plugin tree for '%s' and '%s'\n", cfg->plugins_path, cfg->autoload_path); #endif /* validate proxenet SSL configuration for interception */ /* check ssl certificate */ cfg->cafile = cfg_get_valid_file(certfile); if (!cfg->cafile) return -1; /* check ssl key */ cfg->keyfile = cfg_get_valid_file(keyfile); if (!cfg->cafile) return -1; /* get the key passphrase */ cfg->keyfile_pwd = proxenet_xstrdup2(keyfile_pwd); if (!cfg->keyfile_pwd) return -1; /* validate proxenet client certificate paramaters */ /* check ssl client certificate if provided */ if (sslcli_certfile && sslcli_keyfile) { cfg->sslcli_certfile = cfg_get_valid_file(sslcli_certfile); if (!cfg->sslcli_certfile) return -1; /* check ssl private key associated with the client certificate */ cfg->sslcli_keyfile = cfg_get_valid_file(sslcli_keyfile); if (!cfg->sslcli_keyfile) return -1; /* get the key passphrase */ cfg->sslcli_keyfile_pwd = proxenet_xstrdup2(sslcli_keyfile_pwd); if (!cfg->sslcli_keyfile_pwd) return -1; /* get the domain */ cfg->sslcli_domain = proxenet_xstrdup2(sslcli_domain); if(!cfg->sslcli_domain) return -1; } /* check proxy values (if any) */ if ( proxy_port && !proxy_host) { xlog(LOG_CRITICAL, "%s\n", "Cannot use proxy-port without proxy-host"); return -1; } if (proxy_host) { cfg->proxy.host = proxenet_xstrdup2(proxy_host); if (!cfg->proxy.host) { xlog(LOG_CRITICAL, "proxy %s\n", strerror(errno)); return -1; } cfg->proxy.port = proxenet_xstrdup2(proxy_port); if (!cfg->proxy.port) { xlog(LOG_CRITICAL, "proxy_port %s\n", strerror(errno)); return -1; } if (use_socks_proxy) cfg->is_socks_proxy = true; } /* become a daemon */ if(cfg->daemon) { if (cfg->verbose){ xlog(LOG_WARNING, "%s will now run as daemon\n", PROGNAME); xlog(LOG_WARNING, "%s\n", "Use `control-client.py' to interact with the process."); } if (daemon(0, 0) < 0) { xlog(LOG_ERROR, "daemon failed: %s\n", strerror(errno)); return -1; } cfg->verbose = 0; } return 0; }
/** * This function is called by all threads to treat to process the request and response. * It will also apply the plugins. * * @param server_socket is the socket received by the main thread from the web browser (acting like server * to web browser) */ void proxenet_process_http_request(sock_t server_socket) { sock_t client_socket; request_t req; int retcode, n; fd_set rfds; struct timespec ts; ssl_context_t ssl_context; bool is_ssl; sigset_t emptyset; bool is_new_http_connection = false; client_socket = retcode = n = -1; proxenet_xzero(&req, sizeof(request_t)); proxenet_xzero(&ssl_context, sizeof(ssl_context_t)); /* wait for any event on sockets */ while(proxy_state == ACTIVE) { if (server_socket < 0) { xlog(LOG_ERROR, "sock browser->%s (#%d) died unexpectedly\n", PROGNAME, server_socket); break; } ts.tv_sec = HTTP_TIMEOUT_SOCK; ts.tv_nsec = 0; FD_ZERO(&rfds); FD_SET(server_socket, &rfds); if (client_socket > 0) FD_SET(client_socket, &rfds); sigemptyset(&emptyset); retcode = pselect(FD_SETSIZE, &rfds, NULL, NULL, &ts, &emptyset); if (retcode < 0) { if (errno == EINTR) { continue; } else { xlog(LOG_CRITICAL, "[thread] pselect returned %d: %s\n", retcode, strerror(errno)); break; } } if (retcode == 0) { continue; } is_ssl = ssl_context.use_ssl; /* is there data from web browser to proxy ? */ if( FD_ISSET(server_socket, &rfds ) ) { if(is_ssl && req.do_intercept) { n = proxenet_read_all(server_socket, &req.data, &(ssl_context.server.context)); } else { n = proxenet_read_all(server_socket, &req.data, NULL); } #ifdef DEBUG xlog(LOG_DEBUG, "Got %dB from client (%s srv_sock=#%d intercept_flag=%s)\n", n, is_ssl?"SSL":"PLAIN", server_socket, req.do_intercept?"true":"false"); #endif if (n < 0) { xlog(LOG_ERROR, "%s\n", "read() failed, end thread"); break; } if (n == 0){ #ifdef DEBUG xlog(LOG_DEBUG, "%s\n", "Socket EOF from client"); #endif break; } /* from here, n can only be positive */ req.size = (size_t) n; bytes_sent += n; if (req.id > 0 && !req.do_intercept){ #ifdef DEBUG xlog(LOG_DEBUG, "Intercept disabled for browser->'%s'\n", req.http_infos.hostname); #endif goto send_to_server; } /* proxy keep-alive */ if (req.id > 0){ request_t* old_req = (request_t*)proxenet_xmalloc(sizeof(request_t)); memcpy(old_req, &req, sizeof(request_t)); char* host = proxenet_xstrdup2( req.http_infos.hostname ); free_http_infos(&(req.http_infos)); if (update_http_infos(&req) < 0){ xlog(LOG_ERROR, "Failed to update HTTP information for request %d\n", req.id); proxenet_xfree( host ); proxenet_xfree( old_req ); req.id = 0; break; } if (strcmp( host, req.http_infos.hostname )){ /* reset the client connection parameters */ if (cfg->verbose) xlog(LOG_INFO, "Reusing sock=%d (old request=%d, old sock=%d) %s/%s\n", server_socket, req.id, client_socket, host, req.http_infos.hostname ); proxenet_close_socket(client_socket, &(ssl_context.client)); free_http_infos(&(req.http_infos)); client_socket = -1; } proxenet_xclean( old_req, sizeof(request_t) ); proxenet_xfree( host ); } req.type = REQUEST; req.id = get_new_request_id(); /* is connection to server not established ? -> new request */ if ( client_socket < 0) { retcode = create_http_socket(&req, &server_socket, &client_socket, &ssl_context); if (retcode < 0) { xlog(LOG_ERROR, "Failed to create %s->server socket\n", PROGNAME); proxenet_xfree(req.data); req.id = 0; break; } if (ssl_context.use_ssl) { req.is_ssl = true; if (req.do_intercept == false) { #ifdef DEBUG xlog(LOG_DEBUG, "SSL interception client <-> %s <-> server disabled\n", PROGNAME); #endif proxenet_xfree(req.data); req.type = REQUEST; req.id = get_new_request_id(); continue; } else if (ssl_context.server.is_valid && ssl_context.client.is_valid) { #ifdef DEBUG xlog(LOG_DEBUG, "SSL interception client <-> %s <-> server established\n", PROGNAME); #endif proxenet_xfree(req.data); is_new_http_connection = true; continue; } xlog(LOG_ERROR, "%s\n", "Failed to establish interception"); proxenet_xfree(req.data); break; } is_new_http_connection = true; } /* if proxenet does not relay to another proxy */ if (!cfg->proxy.host) { if (is_new_http_connection) { if (is_ssl) { /* * SSL request fields still have the values gathered in the CONNECT * Those values must be updated to reflect the real request */ free_http_infos(&(req.http_infos)); retcode = update_http_infos(&req); } else { /* * Format requests * GET http://foo/bar.blah HTTP/1.1 ... * into * GET /bar.blah HTTP/1.1 ... */ retcode = format_http_request(&req.data, &req.size); } } else { /* if here, at least 1 request has been to server */ /* so simply forward */ /* e.g. using HTTP/1.1 100 Continue */ #ifdef DEBUG xlog(LOG_DEBUG, "Resuming stream '%d'->'%d'\n", client_socket, server_socket); #endif free_http_infos(&(req.http_infos)); retcode = update_http_infos(&req); } if (retcode < 0){ xlog(LOG_ERROR, "Failed to update %s information in request %d\n", is_ssl?"HTTPS":"HTTP", req.id); proxenet_xfree(req.data); break; } } if(cfg->ie_compat){ if (is_ssl) retcode = ie_compat_read_post_body(server_socket, &req, &(ssl_context.server.context)); else retcode = ie_compat_read_post_body(server_socket, &req, NULL); if (retcode < 0){ xlog(LOG_ERROR, "%s\n", "Extending IE POST: failed"); proxenet_xfree(req.data); break; } } /* apply plugins for requests (from browser to server) */ if (cfg->verbose) { xlog(LOG_INFO, "%s request to '%s:%d'\n", is_ssl?"SSL":"plain", req.http_infos.hostname, req.http_infos.port); if (cfg->verbose > 1) xlog(LOG_INFO, "%s %s %s\n", req.http_infos.method, req.http_infos.uri, req.http_infos.version); } #ifdef DEBUG xlog(LOG_DEBUG, "Request %d pre-plugins: buflen:%lu\n", req.id, req.size); #endif /* hook request with all plugins in plugins_list */ if ( proxenet_apply_plugins(&req) < 0) { /* extremist action: any error on any plugin discard the whole request */ proxenet_xfree( req.data ); break; } #ifdef DEBUG xlog(LOG_DEBUG, "Request %d post-plugins: buflen:%lu\n", req.id, req.size); if(cfg->verbose > 2) proxenet_hexdump(req.data, req.size); #endif send_to_server: /* send modified data */ if (is_ssl && req.do_intercept) { retcode = proxenet_ssl_write(&(ssl_context.client.context), req.data, req.size); } else { retcode = proxenet_write(client_socket, req.data, req.size); } /* reset data */ proxenet_xfree(req.data); req.size = 0; if (retcode < 0) { xlog(LOG_ERROR, "[%d] %s\n", req.id, "Failed to write to server"); break; } #ifdef DEBUG xlog(LOG_DEBUG, "Written %d bytes to server (socket=%s socket #%d)\n", retcode, is_ssl?"SSL":"PLAIN", client_socket); #endif } /* end FD_ISSET(data_from_browser) */ /* is there data from remote server to proxy ? */ if( client_socket > 0 && FD_ISSET(client_socket, &rfds ) ) { if(req.is_ssl && req.do_intercept) { n = proxenet_read_all(client_socket, &req.data, &ssl_context.client.context); } else { n = proxenet_read_all(client_socket, &req.data, NULL); } if (n < 0){ xlog(LOG_ERROR, "read() %s on cli_sock=#%d failed: %d\n", is_ssl?"SSL":"PLAIN", client_socket, n); break; } if (n==0){ #ifdef DEBUG xlog(LOG_DEBUG, "Socket EOF from server (cli_sock=#%d)\n", client_socket); #endif break; } /* update request data structure */ req.type = RESPONSE; /* from here, n can only be positive */ req.size = (size_t) n; bytes_recv += n; if (req.do_intercept==false){ #ifdef DEBUG xlog(LOG_DEBUG, "Intercept disabled for '%s'->browser\n", req.http_infos.hostname); #endif goto send_to_client; } /* apply plugins for response (from server to browser) */ #ifdef DEBUG xlog(LOG_DEBUG, "Response %d pre-plugins: buflen:%lu\n", req.id, req.size); #endif /* execute response hooks */ if ( proxenet_apply_plugins(&req) < 0) { /* extremist action: any error on any plugin discard the whole request */ proxenet_xfree(req.data); break; } #ifdef DEBUG xlog(LOG_DEBUG, "Response %d post-plugins: buflen:%lu\n", req.id, req.size); if(cfg->verbose > 2) proxenet_hexdump(req.data, req.size); #endif send_to_client: /* send modified data to client */ if (req.is_ssl && req.do_intercept) retcode = proxenet_ssl_write(&(ssl_context.server.context), req.data, req.size); else retcode = proxenet_write(server_socket, req.data, req.size); if (retcode < 0) { xlog(LOG_ERROR, "[%d] %s\n", req.id, "proxy->client: write failed"); proxenet_xfree(req.data); break; } #ifdef DEBUG xlog(LOG_DEBUG, "Written %d bytes to browser (socket=%s socket #%d)\n", retcode, is_ssl?"SSL":"PLAIN", client_socket); #endif proxenet_xfree(req.data); } /* end FD_ISSET(data_from_server) */ } /* end for(;;) { select() } */ if (req.id) { if (cfg->verbose) xlog(LOG_INFO, "End of request %d, cleaning context\n", req.id); free_http_infos(&(req.http_infos)); } /* close client socket */ if (client_socket > 0) { if (cfg->verbose >= 2) xlog(LOG_INFO, "Closing %s->server (fd=#%d)\n", PROGNAME, client_socket); proxenet_close_socket(client_socket, &(ssl_context.client)); } /* close local socket */ if (server_socket > 0) { if (cfg->verbose >= 2) xlog(LOG_INFO, "Closing browser->%s (fd=#%d)\n", PROGNAME, server_socket); proxenet_close_socket(server_socket, &(ssl_context.server)); } #ifdef DEBUG xlog(LOG_DEBUG, "Request %d: Structures closed, leaving\n", req.id); #endif /* and that's all folks */ return; }
/** * This function updates all the fields of the current request_t with the new values found in the * request. Since those values will be useful many times, they are strdup-ed in a specific structure * (http_request_t). Those values *must* be free-ed later on. * * @return 0 if successful, -1 if any error occurs. */ int update_http_infos(request_t *req) { char *ptr, *buf, c; buf = req->data; /* method */ ptr = strchr(buf, ' '); if (!ptr){ xlog(LOG_ERROR, "%s\n", "Cannot find HTTP method in request"); if (cfg->verbose) xlog(LOG_ERROR, "Buffer sent:\n%s\n", buf); return -1; } c = *ptr; *ptr = '\0'; req->http_infos.method = proxenet_xstrdup2(buf); if (!req->http_infos.method){ xlog(LOG_ERROR, "%s\n", "strdup(method) failed, cannot pursue..."); return -1; } *ptr = c; if (!strcmp(req->http_infos.method, "CONNECT")){ int offset; req->is_ssl = true; req->http_infos.proto = HTTPS_STRING; req->http_infos.port = HTTPS_DEFAULT_PORT; offset = ptr - buf + 1; if( get_hostname_from_uri(req, offset) < 0 ){ xlog(LOG_ERROR, "%s\n", "Failed to get hostname (URI)"); goto failed_hostname; } req->http_infos.path = proxenet_xstrdup2("/"); req->http_infos.version = proxenet_xstrdup2("HTTP/1.0"); req->http_infos.uri = get_request_full_uri(req); if(!req->http_infos.uri){ xlog(LOG_ERROR, "%s\n", "get_request_full_uri() failed"); goto failed_uri; } return 0; } /* hostname and port */ if (req->is_ssl){ req->http_infos.port = HTTPS_DEFAULT_PORT; req->http_infos.proto = HTTPS_STRING; } else { req->http_infos.port = HTTP_DEFAULT_PORT; req->http_infos.proto = HTTP_STRING; } if( get_hostname_from_header(req) < 0 ){ xlog(LOG_ERROR, "%s\n", "Failed to get hostname (Host header)"); goto failed_hostname; } /* path */ buf = ptr+1; if (!strncmp(buf, HTTP_PROTO_STRING, strlen(HTTP_PROTO_STRING))){ buf = strchr(buf + 8, '/'); } ptr = strchr(buf, ' '); if (!ptr){ xlog(LOG_ERROR, "%s\n", "Cannot find HTTP path in request"); goto failed_path; } c = *ptr; *ptr = '\0'; req->http_infos.path = proxenet_xstrdup2(buf); if (!req->http_infos.path){ xlog(LOG_ERROR, "%s\n", "strdup(path) failed, cannot pursue..."); goto failed_path; } *ptr = c; buf = ptr+1; /* version */ ptr = strchr(req->data, '\r'); if (!ptr){ xlog(LOG_ERROR, "%s\n", "Cannot find HTTP version"); goto failed_version; } c = *ptr; *ptr = '\0'; req->http_infos.version = proxenet_xstrdup2(buf); if (!req->http_infos.version){ xlog(LOG_ERROR, "%s\n", "strdup(version) failed, cannot pursue..."); goto failed_version; } *ptr = c; /* refresh uri */ req->http_infos.uri = get_request_full_uri(req); if(!req->http_infos.uri){ xlog(LOG_ERROR, "%s\n", "get_request_full_uri() failed"); goto failed_uri; } if (cfg->verbose) { xlog(LOG_INFO, "New request %d to '%s'\n", req->id, req->http_infos.uri); if (cfg->verbose >= 2) { xlog(LOG_INFO, "Request HTTP information:\n" "method=%s\n" "proto=%s\n" "hostname=%s\n" "port=%d\n" "path=%s\n" "version=%s\n" , req->http_infos.method, req->http_infos.proto, req->http_infos.hostname, req->http_infos.port, req->http_infos.path, req->http_infos.version); } } return 0; failed_uri: proxenet_xfree(req->http_infos.version); failed_version: proxenet_xfree(req->http_infos.path); failed_path: proxenet_xfree(req->http_infos.hostname); failed_hostname: proxenet_xfree(req->http_infos.method); return -1; }