X-Git-Url: http://www.privoxy.org/gitweb/?a=blobdiff_plain;f=filters.c;h=15a9f31a5ecf4684c2b28153812652fe8b5e78a8;hb=e8c410da3b4ea069c0d3cb3692e049f51d957a3b;hp=1a1da409823f7a6ed0df34740e5f3da4222b71b4;hpb=656cce68348695162120584f5c12ceeed16b2792;p=privoxy.git diff --git a/filters.c b/filters.c index 1a1da409..15a9f31a 100644 --- a/filters.c +++ b/filters.c @@ -1,4 +1,4 @@ -const char filters_rcs[] = "$Id: filters.c,v 1.63 2006/08/31 10:11:28 fabiankeil Exp $"; +const char filters_rcs[] = "$Id: filters.c,v 1.88 2007/06/01 16:41:11 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/filters.c,v $ @@ -10,9 +10,10 @@ const char filters_rcs[] = "$Id: filters.c,v 1.63 2006/08/31 10:11:28 fabiankeil * `filter_popups', `forward_url', 'redirect_url', * `ij_untrusted_url', `intercept_url', `pcrs_filter_respose', * `ijb_send_banner', `trust_url', `gif_deanimate_response', - * `jpeg_inspect_response' + * `jpeg_inspect_response', `execute_single_pcrs_command', + * `rewrite_url', `get_last_url' * - * Copyright : Written by and Copyright (C) 2001, 2004 the SourceForge + * Copyright : Written by and Copyright (C) 2001, 2004-2007 the SourceForge * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written @@ -39,6 +40,112 @@ const char filters_rcs[] = "$Id: filters.c,v 1.63 2006/08/31 10:11:28 fabiankeil * * Revisions : * $Log: filters.c,v $ + * Revision 1.88 2007/06/01 16:41:11 fabiankeil + * Add forward-override{} to change the forwarding settings through + * action sections. This is mainly interesting to forward different + * clients differently (for example based on User-Agent or request + * origin). + * + * Revision 1.87 2007/04/30 15:53:10 fabiankeil + * Make sure filters with dynamic jobs actually use them. + * + * Revision 1.86 2007/04/30 15:03:28 fabiankeil + * - Introduce dynamic pcrs jobs that can resolve variables. + * - Don't run redirect functions more than once, + * unless they are activated more than once. + * + * Revision 1.85 2007/03/21 12:24:47 fabiankeil + * - Log the content size after decompression in decompress_iob() + * instead of pcrs_filter_response(). + * + * Revision 1.84 2007/03/20 15:16:34 fabiankeil + * Use dedicated header filter actions instead of abusing "filter". + * Replace "filter-client-headers" and "filter-client-headers" + * with "server-header-filter" and "client-header-filter". + * + * Revision 1.83 2007/03/17 15:20:05 fabiankeil + * New config option: enforce-blocks. + * + * Revision 1.82 2007/03/13 11:28:43 fabiankeil + * - Fix port handling in acl_addr() and use a temporary acl spec + * copy so error messages don't contain a truncated version. + * - Log size of iob before and after decompression. + * + * Revision 1.81 2007/03/05 14:40:53 fabiankeil + * - Cosmetical changes for LOG_LEVEL_RE_FILTER messages. + * - Hide the "Go there anyway" link for blocked CONNECT + * requests where going there anyway doesn't work anyway. + * + * Revision 1.80 2007/02/07 10:55:20 fabiankeil + * - Save the reason for generating http_responses. + * - Block (+block) with status code 403 instead of 404. + * - Use a different kludge to remember a failed decompression. + * + * Revision 1.79 2007/01/31 16:21:38 fabiankeil + * Search for Max-Forwards headers case-insensitive, + * don't generate the "501 unsupported" message for invalid + * Max-Forwards values and don't increase negative ones. + * + * Revision 1.78 2007/01/28 13:41:18 fabiankeil + * - Add HEAD support to finish_http_response. + * - Add error favicon to internal HTML error messages. + * + * Revision 1.77 2007/01/12 15:36:44 fabiankeil + * Mark *csp as immutable for is_untrusted_url() + * and is_imageurl(). Closes FR 1237736. + * + * Revision 1.76 2007/01/01 19:36:37 fabiankeil + * Integrate a modified version of Wil Mahan's + * zlib patch (PR #895531). + * + * Revision 1.75 2006/12/29 18:30:46 fabiankeil + * Fixed gcc43 conversion warnings, + * changed sprintf calls to snprintf. + * + * Revision 1.74 2006/12/24 17:37:38 fabiankeil + * Adjust comment in pcrs_filter_response() + * to recent pcrs changes. Hohoho. + * + * Revision 1.73 2006/12/23 16:01:02 fabiankeil + * Don't crash if pcre returns an error code + * that pcrs didn't expect. Fixes BR 1621173. + * + * Revision 1.72 2006/12/22 18:52:53 fabiankeil + * Modified is_untrusted_url to complain in case of + * write errors and to give a reason when adding new + * entries to the trustfile. Closes FR 1097611. + * + * Revision 1.71 2006/12/22 14:24:52 fabiankeil + * Skip empty filter files in pcrs_filter_response, + * but don't ignore the ones that come afterwards. + * Fixes parts of BR 1619208. + * + * Revision 1.70 2006/12/09 13:33:15 fabiankeil + * Added some sanity checks for get_last_url(). + * Fixed possible segfault caused by my last commit. + * + * Revision 1.69 2006/12/08 12:39:13 fabiankeil + * Let get_last_url() catch https URLs as well. + * + * Revision 1.68 2006/12/05 14:45:48 fabiankeil + * Make sure get_last_url() behaves like advertised + * and fast-redirects{} can be combined with redirect{}. + * + * Revision 1.67 2006/11/28 15:19:43 fabiankeil + * Implemented +redirect{s@foo@bar@} to generate + * a redirect based on a rewritten version of the + * original URL. + * + * Revision 1.66 2006/09/23 13:26:38 roro + * Replace TABs by spaces in source code. + * + * Revision 1.65 2006/09/21 12:54:43 fabiankeil + * Fix +redirect{}. Didn't work with -fast-redirects. + * + * Revision 1.64 2006/08/31 10:55:49 fabiankeil + * Block requests for untrusted URLs with status + * code 403 instead of 200. + * * Revision 1.63 2006/08/31 10:11:28 fabiankeil * Don't free p which is still in use and will be later * freed by free_map(). Don't claim the referrer is unknown @@ -488,6 +595,7 @@ const char filters_rcs[] = "$Id: filters.c,v 1.63 2006/08/31 10:11:28 fabiankeil #include "list.h" #include "deanimate.h" #include "urlmatch.h" +#include "loaders.h" #ifdef _WIN32 #include "win32.h" @@ -579,20 +687,33 @@ int block_acl(struct access_control_addr *dst, struct client_state *csp) * Returns : 0 => Ok, everything else is an error. * *********************************************************************/ -int acl_addr(char *aspec, struct access_control_addr *aca) +int acl_addr(const char *aspec, struct access_control_addr *aca) { - int i, masklength, port; + int i, masklength; + long port; char *p; + char *acl_spec = NULL; masklength = 32; port = 0; - if ((p = strchr(aspec, '/')) != NULL) + /* + * Use a temporary acl spec copy so we can log + * the unmodified original in case of parse errors. + */ + acl_spec = strdup(aspec); + if (acl_spec == NULL) { - *p++ = '\0'; + /* XXX: This will be logged as parse error. */ + return(-1); + } + if ((p = strchr(acl_spec, '/')) != NULL) + { + *p++ = '\0'; if (ijb_isdigit(*p) == 0) { + free(acl_spec); return(-1); } masklength = atoi(p); @@ -600,26 +721,32 @@ int acl_addr(char *aspec, struct access_control_addr *aca) if ((masklength < 0) || (masklength > 32)) { + free(acl_spec); return(-1); } - if ((p = strchr(aspec, ':')) != NULL) + if ((p = strchr(acl_spec, ':')) != NULL) { + char *endptr; + *p++ = '\0'; + port = strtol(p, &endptr, 10); - if (ijb_isdigit(*p) == 0) + if (port <= 0 || port > 65535 || *endptr != '\0') { + free(acl_spec); return(-1); } - port = atoi(p); } - aca->port = port; + aca->port = (unsigned long)port; - aca->addr = ntohl(resolve_hostname_to_ip(aspec)); + aca->addr = ntohl(resolve_hostname_to_ip(acl_spec)); + free(acl_spec); if (aca->addr == INADDR_NONE) { + /* XXX: This will be logged as parse error. */ return(-1); } @@ -765,7 +892,7 @@ struct http_response *block_url(struct client_state *csp) if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0) && is_imageurl(csp)) { - char *p; + char *p; /* determine HOW images should be blocked */ p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER]; @@ -889,7 +1016,6 @@ struct http_response *block_url(struct client_state *csp) return cgi_error_memory(); } } - } else #endif /* def FEATURE_IMAGE_BLOCKING */ @@ -918,7 +1044,7 @@ struct http_response *block_url(struct client_state *csp) } else { - rsp->status = strdup("404 Request for blocked URL"); + rsp->status = strdup("403 Request for blocked URL"); } if (rsp->status == NULL) @@ -936,7 +1062,15 @@ struct http_response *block_url(struct client_state *csp) #ifdef FEATURE_FORCE_LOAD err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1); - if (csp->http->ssl != 0) + /* + * Export the force conditional block killer if + * + * - Privoxy was compiled without FEATURE_FORCE_LOAD, or + * - Privoxy is configured to enforce blocks, or + * - it's a CONNECT request and enforcing wouldn't work anyway. + */ + if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS) + || (0 == strcmpic(csp->http->gpc, "connect"))) #endif /* ndef FEATURE_FORCE_LOAD */ { err = map_block_killer(exports, "force-support"); @@ -961,8 +1095,9 @@ struct http_response *block_url(struct client_state *csp) return cgi_error_memory(); } } + rsp->reason = RSP_REASON_BLOCKED; - return finish_http_response(rsp); + return finish_http_response(csp, rsp); } @@ -1044,7 +1179,7 @@ struct http_response *trust_url(struct client_state *csp) p = strdup(""); for (tl = csp->config->trust_list; (t = *tl) != NULL ; tl++) { - sprintf(buf, "
  • %s
  • \n", t->spec); + snprintf(buf, sizeof(buf), "
  • %s
  • \n", t->spec); string_append(&p, buf); } err = map(exports, "trusted-referrers", 1, p, 0); @@ -1066,7 +1201,7 @@ struct http_response *trust_url(struct client_state *csp) p = strdup(""); for (l = csp->config->trust_info->first; l ; l = l->next) { - sprintf(buf, "
  • %s
    \n",l->str, l->str); + snprintf(buf, sizeof(buf), "
  • %s
    \n", l->str, l->str); string_append(&p, buf); } err = map(exports, "trust-info", 1, p, 0); @@ -1084,10 +1219,22 @@ struct http_response *trust_url(struct client_state *csp) } /* - * Export the force prefix or the force conditional block killer + * Export the force conditional block killer if + * + * - Privoxy was compiled without FEATURE_FORCE_LOAD, or + * - Privoxy is configured to enforce blocks, or + * - it's a CONNECT request and enforcing wouldn't work anyway. */ #ifdef FEATURE_FORCE_LOAD - err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1); + if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS) + || (0 == strcmpic(csp->http->gpc, "connect"))) + { + err = map_block_killer(exports, "force-support"); + } + else + { + err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1); + } #else /* ifndef FEATURE_FORCE_LOAD */ err = map_block_killer(exports, "force-support"); #endif /* ndef FEATURE_FORCE_LOAD */ @@ -1108,107 +1255,357 @@ struct http_response *trust_url(struct client_state *csp) free_http_response(rsp); return cgi_error_memory(); } + rsp->reason = RSP_REASON_UNTRUSTED; - return finish_http_response(rsp); + return finish_http_response(csp, rsp); } #endif /* def FEATURE_TRUST */ -#ifdef FEATURE_FAST_REDIRECTS /********************************************************************* * - * Function : redirect_url + * Function : compile_dynamic_pcrs_job_list * - * Description : Checks for redirection URLs and returns a HTTP redirect - * to the destination URL, if necessary + * Description : Compiles a dynamic pcrs job list (one with variables + * resolved at request time) * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : b = The filter list to compile * - * Returns : NULL if URL was clean, HTTP redirect otherwise. + * Returns : NULL in case of errors, otherwise the + * pcrs job list. * *********************************************************************/ -struct http_response *redirect_url(struct client_state *csp) +pcrs_job *compile_dynamic_pcrs_job_list(const struct client_state *csp, const struct re_filterfile_spec *b) { - char *p, *q; - struct http_response *rsp; - char *redirect_mode = NULL; - int x, y; + struct list_entry *pattern; + pcrs_job *job_list = NULL; + pcrs_job *dummy = NULL; + pcrs_job *lastjob = NULL; + int error = 0; - if ((csp->action->flags & ACTION_REDIRECT)) + const struct pcrs_variable variables[] = { - q = csp->action->string[ACTION_STRING_REDIRECT]; - } - else + {"url", csp->http->url, 1}, + {"path", csp->http->path, 1}, + {"host", csp->http->host, 1}, + {"origin", csp->ip_addr_str, 1}, + {NULL, NULL, 1} + }; + + for (pattern = b->patterns->first; pattern != NULL; pattern = pattern->next) { - redirect_mode = csp->action->string[ACTION_STRING_FAST_REDIRECTS]; - if (0 == strcmpic(redirect_mode, "check-decoded-url")) - { - p = q = csp->http->path; - log_error(LOG_LEVEL_REDIRECTS, "Decoding path: %s if necessary.", p); - while (*p) + assert(pattern->str != NULL); + + dummy = pcrs_compile_dynamic_command(pattern->str, variables, &error); + if (NULL == dummy) + { + assert(error < 0); + log_error(LOG_LEVEL_ERROR, + "Adding filter job \'%s\' to dynamic filter %s failed: %s", + pattern->str, b->name, pcrs_strerror(error)); + continue; + } + else + { + if (error == PCRS_WARN_TRUNCATION) { - if (*p == '%') /* Escape sequence? */ - { - /* Yes, translate from hexadecimal to decimal */ - p++; - /* First byte */ - x=((int)*p++)-48; - if (x>9) x-=7; - x<<=4; - /* Second byte */ - y=((int)*p++)-48; - if (y>9)y-=7; - /* Merge */ - *q++=(char)(x|y); - } - else - { - /* No, forward character. */ - *q++=*p++; - } + log_error(LOG_LEVEL_ERROR, + "At least one of the variables in \'%s\' had to " + "be truncated before compilation", pattern->str); } - *q='\0'; + if (job_list == NULL) + { + job_list = dummy; + } + else + { + lastjob->next = dummy; + } + lastjob = dummy; } - p = q = csp->http->path; - log_error(LOG_LEVEL_REDIRECTS, "Checking path for redirects: %s", p); + } - /* - * find the last URL encoded in the request - */ - while ((p = strstr(p, "http://")) != NULL) + return job_list; +} + + +/********************************************************************* + * + * Function : rewrite_url + * + * Description : Rewrites a URL with a single pcrs command + * and returns the result if it differs from the + * original and isn't obviously invalid. + * + * Parameters : + * 1 : old_url = URL to rewrite. + * 2 : pcrs_command = pcrs command formatted as string (s@foo@bar@) + * + * + * Returns : NULL if the pcrs_command didn't change the url, or + * the result of the modification. + * + *********************************************************************/ +char *rewrite_url(char *old_url, const char *pcrs_command) +{ + char *new_url = NULL; + int hits; + + assert(old_url); + assert(pcrs_command); + + new_url = pcrs_execute_single_command(old_url, pcrs_command, &hits); + + if (hits == 0) + { + log_error(LOG_LEVEL_REDIRECTS, + "pcrs command \"%s\" didn't change \"%s\".", + pcrs_command, old_url); + freez(new_url); + } + else if (hits < 0) + { + log_error(LOG_LEVEL_REDIRECTS, + "executing pcrs command \"%s\" to rewrite %s failed: %s", + pcrs_command, old_url, pcrs_strerror(hits)); + freez(new_url); + } + else if (strncmpic(new_url, "http://", 7) && strncmpic(new_url, "https://", 8)) + { + log_error(LOG_LEVEL_ERROR, + "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s), " + "but the result doesn't look like a valid URL and will be ignored.", + pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts"); + freez(new_url); + } + else + { + log_error(LOG_LEVEL_REDIRECTS, + "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s).", + pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts"); + } + + return new_url; + +} + + +#ifdef FEATURE_FAST_REDIRECTS +/********************************************************************* + * + * Function : get_last_url + * + * Description : Search for the last URL inside a string. + * If the string already is a URL, it will + * be the first URL found. + * + * Parameters : + * 1 : subject = the string to check + * 2 : redirect_mode = +fast-redirect{} mode + * + * Returns : NULL if no URL was found, or + * the last URL found. + * + *********************************************************************/ +char *get_last_url(char *subject, const char *redirect_mode) +{ + char *new_url = NULL; + char *tmp; + + assert(subject); + assert(redirect_mode); + + subject = strdup(subject); + if (subject == NULL) + { + log_error(LOG_LEVEL_ERROR, "Out of memory while searching for redirects."); + return NULL; + } + + if (0 == strcmpic(redirect_mode, "check-decoded-url")) + { + log_error(LOG_LEVEL_REDIRECTS, "Decoding \"%s\" if necessary.", subject); + new_url = url_decode(subject); + if (new_url != NULL) + { + freez(subject); + subject = new_url; + } + else { - q = p++; + log_error(LOG_LEVEL_ERROR, "Unable to decode \"%s\".", subject); } } + + log_error(LOG_LEVEL_REDIRECTS, "Checking \"%s\" for redirects.", subject); + /* - * if there was any, generate and return a HTTP redirect + * Find the last URL encoded in the request */ - if (q != csp->http->path) + tmp = subject; + while ((tmp = strstr(tmp, "http://")) != NULL) + { + new_url = tmp++; + } + tmp = (new_url != NULL) ? new_url : subject; + while ((tmp = strstr(tmp, "https://")) != NULL) { - log_error(LOG_LEVEL_REDIRECTS, "redirecting to: %s", q); + new_url = tmp++; + } - if (NULL == (rsp = alloc_http_response())) + if ((new_url != NULL) + && ( (new_url != subject) + || (0 == strncmpic(subject, "http://", 7)) + || (0 == strncmpic(subject, "https://", 8)) + )) + { + /* + * Return new URL if we found a redirect + * or if the subject already was a URL. + * + * The second case makes sure that we can + * chain get_last_url after another redirection check + * (like rewrite_url) without losing earlier redirects. + */ + new_url = strdup(new_url); + freez(subject); + return new_url; + } + + freez(subject); + return NULL; + +} +#endif /* def FEATURE_FAST_REDIRECTS */ + + +/********************************************************************* + * + * Function : redirect_url + * + * Description : Checks if Privoxy should answer the request with + * a HTTP redirect and generates the redirect if + * necessary. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : NULL if the request can pass, HTTP redirect otherwise. + * + *********************************************************************/ +struct http_response *redirect_url(struct client_state *csp) +{ + struct http_response *rsp; +#ifdef FEATURE_FAST_REDIRECTS + /* + * XXX: Do we still need FEATURE_FAST_REDIRECTS + * as compile-time option? The user can easily disable + * it in his action file. + */ + char * redirect_mode; +#endif /* def FEATURE_FAST_REDIRECTS */ + char *old_url = NULL; + char *new_url = NULL; + char *redirection_string; + + if ((csp->action->flags & ACTION_REDIRECT)) + { + redirection_string = csp->action->string[ACTION_STRING_REDIRECT]; + + /* + * If the redirection string begins with 's', + * assume it's a pcrs command, otherwise treat it as + * properly formatted URL and use it for the redirection + * directly. + * + * According to RFC 2616 section 14.30 the URL + * has to be absolute and if the user tries: + * +redirect{shit/this/will/be/parsed/as/pcrs_command.html} + * she would get undefined results anyway. + * + */ + + if (*redirection_string == 's') { - return cgi_error_memory(); + old_url = csp->http->url; + new_url = rewrite_url(old_url, redirection_string); } - - if ( enlist_unique_header(rsp->headers, "Location", q) - || (NULL == (rsp->status = strdup("302 Local Redirect from Privoxy"))) ) + else { - free_http_response(rsp); - return cgi_error_memory(); + log_error(LOG_LEVEL_REDIRECTS, + "No pcrs command recognized, assuming that \"%s\" is already properly formatted.", + redirection_string); + new_url = strdup(redirection_string); } + } + +#ifdef FEATURE_FAST_REDIRECTS + if ((csp->action->flags & ACTION_FAST_REDIRECTS)) + { + redirect_mode = csp->action->string[ACTION_STRING_FAST_REDIRECTS]; - return finish_http_response(rsp); + /* + * If it exists, use the previously rewritten URL as input + * otherwise just use the old path. + */ + old_url = (new_url != NULL) ? new_url : strdup(csp->http->path); + new_url = get_last_url(old_url, redirect_mode); + freez(old_url); } - else + + /* + * Disable redirect checkers, so that they + * will be only run more than once if the user + * also enables them through tags. + * + * From a performance point of view + * it doesn't matter, but the duplicated + * log messages are annoying. + */ + csp->action->flags &= ~ACTION_FAST_REDIRECTS; +#endif /* def FEATURE_FAST_REDIRECTS */ + csp->action->flags &= ~ACTION_REDIRECT; + + /* Did any redirect action trigger? */ + if (new_url) { - return NULL; + if (0 == strcmpic(new_url, csp->http->url)) + { + log_error(LOG_LEVEL_ERROR, + "New URL \"%s\" and old URL \"%s\" are the same. Redirection loop prevented.", + csp->http->url, new_url); + freez(new_url); + } + else + { + log_error(LOG_LEVEL_REDIRECTS, "New URL is: %s", new_url); + + if (NULL == (rsp = alloc_http_response())) + { + freez(new_url); + return cgi_error_memory(); + } + + if ( enlist_unique_header(rsp->headers, "Location", new_url) + || (NULL == (rsp->status = strdup("302 Local Redirect from Privoxy"))) ) + { + freez(new_url); + free_http_response(rsp); + return cgi_error_memory(); + } + rsp->reason = RSP_REASON_REDIRECTED; + freez(new_url); + + return finish_http_response(csp, rsp); + } } + /* Only reached if no redirect is required */ + return NULL; + } -#endif /* def FEATURE_FAST_REDIRECTS */ #ifdef FEATURE_IMAGE_BLOCKING @@ -1229,7 +1626,7 @@ struct http_response *redirect_url(struct client_state *csp) * otherwise * *********************************************************************/ -int is_imageurl(struct client_state *csp) +int is_imageurl(const struct client_state *csp) { #ifdef FEATURE_IMAGE_DETECT_MSIE char *tmp; @@ -1276,7 +1673,7 @@ int is_imageurl(struct client_state *csp) * Returns : 0 => trusted, 1 => untrusted * *********************************************************************/ -int is_untrusted_url(struct client_state *csp) +int is_untrusted_url(const struct client_state *csp) { struct file_list *fl; struct block_spec *b; @@ -1328,7 +1725,7 @@ int is_untrusted_url(struct client_state *csp) { /* if the URL's referrer is from a trusted referrer, then * add the target spec to the trustfile as an unblocked - * domain and return NULL (which means it's OK). + * domain and return 0 (which means it's OK). */ FILE *fp; @@ -1359,9 +1756,19 @@ int is_untrusted_url(struct client_state *csp) string_join(&new_entry, path); } + /* + * Give a reason for generating this entry. + */ + string_append(&new_entry, " # Trusted referrer was: "); + string_append(&new_entry, referer); + if (new_entry != NULL) { - fprintf(fp, "%s\n", new_entry); + if (-1 == fprintf(fp, "%s\n", new_entry)) + { + log_error(LOG_LEVEL_ERROR, "Failed to append \'%s\' to trustfile \'%s\': %E", + new_entry, csp->config->trustfile); + } free(new_entry); } else @@ -1372,9 +1779,15 @@ int is_untrusted_url(struct client_state *csp) fclose(fp); } + else + { + log_error(LOG_LEVEL_ERROR, "Failed to append new entry for \'%s\' to trustfile \'%s\': %E", + csp->http->hostport, csp->config->trustfile); + } return 0; } } + return 1; } #endif /* def FEATURE_TRUST */ @@ -1390,6 +1803,11 @@ int is_untrusted_url(struct client_state *csp) * csp->content_length to the modified size and raise the * CSP_FLAG_MODIFIED flag. * + * XXX: Currently pcrs_filter_response is also responsible + * for dechunking and decompressing. Both should be + * done in separate functions so other content modifiers + * profit as well, even if pcrs filtering is disabled. + * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * @@ -1418,7 +1836,7 @@ char *pcrs_filter_response(struct client_state *csp) { return(NULL); } - size = csp->iob->eod - csp->iob->cur; + size = (size_t)(csp->iob->eod - csp->iob->cur); /* * Need to check the set of re_filterfiles... @@ -1439,7 +1857,7 @@ char *pcrs_filter_response(struct client_state *csp) if (0 == found_filters) { log_error(LOG_LEVEL_ERROR, "Unable to get current state of regexp filtering."); - return(NULL); + return(NULL); } /* @@ -1457,11 +1875,61 @@ char *pcrs_filter_response(struct client_state *csp) csp->flags |= CSP_FLAG_MODIFIED; } +#ifdef FEATURE_ZLIB + /* + * If the body has a compressed transfer-encoding, + * uncompress it first, adjusting size and iob->eod. + * Note that decompression occurs after de-chunking. + */ + if (csp->content_type & (CT_GZIP | CT_DEFLATE)) + { + /* Notice that we at least tried to decompress. */ + if (JB_ERR_OK != decompress_iob(csp)) + { + /* + * We failed to decompress the data; there's no point + * in continuing since we can't filter. + * + * XXX: Actually the Accept-Encoding header may + * just be incorrect in which case we could continue + * with filtering. + * + * Unset CT_GZIP and CT_DEFLATE to remember not + * to modify the Content-Encoding header later. + */ + csp->content_type &= ~CT_GZIP; + csp->content_type &= ~CT_DEFLATE; + return(NULL); + } + + /* + * Decompression gives us a completely new iob, + * so we need to update. + */ + size = (size_t)(csp->iob->eod - csp->iob->cur); + old = csp->iob->cur; + + csp->flags |= CSP_FLAG_MODIFIED; + csp->content_type &= ~CT_TABOO; + } +#endif + for (i = 0; i < MAX_AF_FILES; i++) { fl = csp->rlist[i]; if ((NULL == fl) || (NULL == fl->f)) - break; + { + /* + * Either there are no filter files + * left, or this filter file just + * contains no valid filters. + * + * Continue to be sure we don't miss + * valid filter files that are chained + * after empty or invalid ones. + */ + continue; + } /* * For all applying +filter actions, look if a filter by that * name exists and if yes, execute it's pcrs_joblist on the @@ -1469,14 +1937,25 @@ char *pcrs_filter_response(struct client_state *csp) */ for (b = fl->f; b; b = b->next) { + if (b->type != FT_CONTENT_FILTER) + { + /* Skip header filters */ + continue; + } + for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first; filtername ; filtername = filtername->next) { if (strcmp(b->name, filtername->str) == 0) { - int current_hits = 0; + int current_hits = 0; /* Number of hits caused by this filter */ + int job_number = 0; /* Which job we're currently executing */ + int job_hits = 0; /* How many hits the current job caused */ + pcrs_job *joblist = b->joblist; + + if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b); - if ( NULL == b->joblist ) + if (NULL == joblist) { log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name); continue; @@ -1484,15 +1963,54 @@ char *pcrs_filter_response(struct client_state *csp) prev_size = size; /* Apply all jobs from the joblist */ - for (job = b->joblist; NULL != job; job = job->next) + for (job = joblist; NULL != job; job = job->next) { - current_hits += pcrs_execute(job, old, size, &new, &size); - if (old != csp->iob->cur) free(old); - old=new; + job_number++; + job_hits = pcrs_execute(job, old, size, &new, &size); + + if (job_hits >= 0) + { + /* + * That went well. Continue filtering + * and use the result of this job as + * input for the next one. + */ + current_hits += job_hits; + if (old != csp->iob->cur) + { + free(old); + } + old = new; + } + else + { + /* + * This job caused an unexpected error. Inform the user + * and skip the rest of the jobs in this filter. We could + * continue with the next job, but usually the jobs + * depend on each other or are similar enough to + * fail for the same reason. + * + * At the moment our pcrs expects the error codes of pcre 3.4, + * but newer pcre versions can return additional error codes. + * As a result pcrs_strerror()'s error message might be + * "Unknown error ...", therefore we print the numerical value + * as well. + * + * XXX: Is this important enough for LOG_LEVEL_ERROR or + * should we use LOG_LEVEL_RE_FILTER instead? + */ + log_error(LOG_LEVEL_ERROR, "Skipped filter \'%s\' after job number %u: %s (%d)", + b->name, job_number, pcrs_strerror(job_hits), job_hits); + break; + } } - log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s%s (size %d) with filter %s produced %d hits (new size %d).", - csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size); + if (b->dynamic) pcrs_free_joblist(joblist); + + log_error(LOG_LEVEL_RE_FILTER, + "filtering %s%s (size %d) with \'%s\' produced %d hits (new size %d).", + csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size); hits += current_hits; } @@ -1538,7 +2056,9 @@ char *gif_deanimate_response(struct client_state *csp) { struct binbuffer *in, *out; char *p; - size_t size = csp->iob->eod - csp->iob->cur; + size_t size; + + size = (size_t)(csp->iob->eod - csp->iob->cur); /* * If the body has a "chunked" transfer-encoding, @@ -1608,9 +2128,12 @@ char *gif_deanimate_response(struct client_state *csp) *********************************************************************/ char *jpeg_inspect_response(struct client_state *csp) { - struct binbuffer *in = NULL, *out = NULL; + struct binbuffer *in = NULL; + struct binbuffer *out = NULL; char *p = NULL; - size_t size = csp->iob->eod - csp->iob->cur; + size_t size; + + size = (size_t)(csp->iob->eod - csp->iob->cur); /* * If the body has a "chunked" transfer-encoding, @@ -1683,7 +2206,7 @@ char *jpeg_inspect_response(struct client_state *csp) * went wrong * *********************************************************************/ -int remove_chunked_transfer_coding(char *buffer, const size_t size) +size_t remove_chunked_transfer_coding(char *buffer, const size_t size) { size_t newsize = 0; unsigned int chunksize = 0; @@ -1801,12 +2324,155 @@ void apply_url_actions(struct current_action_spec *action, } +/********************************************************************* + * + * Function : get_forward_override_settings + * + * Description : Returns forward settings as specified with the + * forward-override{} action. forward-override accepts + * forward lines similar to the one used in the + * configuration file, but without the URL pattern. + * + * For example: + * + * forward / . + * + * in the configuration file can be replaced with + * the action section: + * + * {+forward-override{forward .}} + * / + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : Pointer to forwarding structure in case of success. + * Invalid syntax is fatal. + * + *********************************************************************/ +static const struct forward_spec *get_forward_override_settings(struct client_state *csp) +{ + const char *forward_override_line = csp->action->string[ACTION_STRING_FORWARD_OVERRIDE]; + char forward_settings[BUFFER_SIZE]; + char *http_parent = NULL; + /* variable names were chosen for consistency reasons. */ + struct forward_spec *fwd = NULL; + int vec_count; + char *vec[3]; + + assert(csp->action->flags & ACTION_FORWARD_OVERRIDE); + /* Should be enforced by load_one_actions_file() */ + assert(strlen(forward_override_line) < sizeof(forward_settings) - 1); + + /* Create a copy ssplit can modify */ + strlcpy(forward_settings, forward_override_line, sizeof(forward_settings)); + + if (NULL != csp->fwd) + { + /* + * XXX: Currently necessary to prevent memory + * leaks when the show-url-info cgi page is visited. + */ + unload_forward_spec(csp->fwd); + } + + /* + * allocate a new forward node, valid only for + * the lifetime of this request. Save its location + * in csp as well, so sweep() can free it later on. + */ + fwd = csp->fwd = zalloc(sizeof(*fwd)); + if (NULL == fwd) + { + log_error(LOG_LEVEL_FATAL, + "can't allocate memory for forward-override{%s}", forward_override_line); + /* Never get here - LOG_LEVEL_FATAL causes program exit */ + } + + vec_count = ssplit(forward_settings, " \t", vec, SZ(vec), 1, 1); + if ((vec_count == 2) && !strcasecmp(vec[0], "forward")) + { + fwd->type = SOCKS_NONE; + + /* Parse the parent HTTP proxy host:port */ + http_parent = vec[1]; + + } + else if (vec_count == 3) + { + char *socks_proxy = NULL; + + if (!strcasecmp(vec[0], "forward-socks4")) + { + fwd->type = SOCKS_4; + socks_proxy = vec[1]; + } + else if (!strcasecmp(vec[0], "forward-socks4a")) + { + fwd->type = SOCKS_4A; + socks_proxy = vec[1]; + } + + if (NULL != socks_proxy) + { + /* Parse the SOCKS proxy host[:port] */ + fwd->gateway_host = strdup(socks_proxy); + + if (NULL != (socks_proxy = strchr(fwd->gateway_host, ':'))) + { + *socks_proxy++ = '\0'; + fwd->gateway_port = strtol(socks_proxy, NULL, 0); + } + + if (fwd->gateway_port <= 0) + { + fwd->gateway_port = 1080; + } + + http_parent = vec[2]; + } + } + + if (NULL == http_parent) + { + log_error(LOG_LEVEL_FATAL, + "Invalid forward-override syntax in: %s", forward_override_line); + /* Never get here - LOG_LEVEL_FATAL causes program exit */ + } + + /* Parse http forwarding settings */ + if (strcmp(http_parent, ".") != 0) + { + fwd->forward_host = strdup(http_parent); + + if (NULL != (http_parent = strchr(fwd->forward_host, ':'))) + { + *http_parent++ = '\0'; + fwd->forward_port = strtol(http_parent, NULL, 0); + } + + if (fwd->forward_port <= 0) + { + fwd->forward_port = 8000; + } + } + + assert (NULL != fwd); + + log_error(LOG_LEVEL_CONNECT, + "Overriding forwarding settings based on \'%s\'", forward_override_line); + + return fwd; +} + /********************************************************************* * * Function : forward_url * * Description : Should we forward this to another proxy? * + * XXX: Should be changed to make use of csp->fwd. + * * Parameters : * 1 : http = http_request request for current URL * 2 : csp = Current client state (buffers, headers, etc...) @@ -1820,6 +2486,11 @@ const struct forward_spec * forward_url(struct http_request *http, static const struct forward_spec fwd_default[1] = { FORWARD_SPEC_INITIALIZER }; struct forward_spec *fwd = csp->config->forward; + if (csp->action->flags & ACTION_FORWARD_OVERRIDE) + { + return get_forward_override_settings(csp); + } + if (fwd == NULL) { return fwd_default; @@ -1865,29 +2536,42 @@ struct http_response *direct_response(struct client_state *csp) { for (p = csp->headers->first; (p != NULL) ; p = p->next) { - if (!strncmp("Max-Forwards:", p->str, 13) - && (*(p->str+13) != '\0') && (atoi(p->str+13) == 0)) + if (!strncmpic("Max-Forwards:", p->str, 13)) { - /* FIXME: We could handle at least TRACE here, - but that would require a verbatim copy of - the request which we don't have anymore */ + unsigned int max_forwards; - log_error(LOG_LEVEL_HEADER, "Found Max-Forwards:0 in OPTIONS or TRACE request -- Returning 501"); - - /* Get mem for response or fail*/ - if (NULL == (rsp = alloc_http_response())) + /* + * If it's a Max-Forwards value of zero, + * we have to intercept the request. + */ + if (1 == sscanf(p->str+12, ": %u", &max_forwards) && max_forwards == 0) { - return cgi_error_memory(); - } + /* + * FIXME: We could handle at least TRACE here, + * but that would require a verbatim copy of + * the request which we don't have anymore + */ + log_error(LOG_LEVEL_HEADER, + "Detected header \'%s\' in OPTIONS or TRACE request. Returning 501.", + p->str); + + /* Get mem for response or fail*/ + if (NULL == (rsp = alloc_http_response())) + { + return cgi_error_memory(); + } - if (NULL == (rsp->status = strdup("501 Not Implemented"))) - { - free_http_response(rsp); - return cgi_error_memory(); - } + if (NULL == (rsp->status = strdup("501 Not Implemented"))) + { + free_http_response(rsp); + return cgi_error_memory(); + } - rsp->is_static = 1; - return(finish_http_response(rsp)); + rsp->is_static = 1; + rsp->reason = RSP_REASON_UNSUPPORTED; + + return(finish_http_response(csp, rsp)); + } } } }