privoxy-3.0.28-stable/./acconfig.h000640 001751 000000 00000016200 13412210415 016371 0ustar00fkwheel000000 000000 #ifndef CONFIG_H_INCLUDED #define CONFIG_H_INCLUDED /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/acconfig.h,v $ * * Purpose : This file should be the first thing included in every * .c file. (Before even system headers). It contains * #define statements for various features. It was * introduced because the compile command line started * getting ludicrously long with feature defines. * * Copyright : Written by and Copyright (C) 2001-2014 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and * Junkbusters Corporation. http://www.junkbusters.com * * 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. * * The GNU General Public License should be included with * this file. If not, you can view it at * http://www.gnu.org/copyleft/gpl.html * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *********************************************************************/ @TOP@ /* * Version number - Major (X._._) */ #undef VERSION_MAJOR /* * Version number - Minor (_.X._) */ #undef VERSION_MINOR /* * Version number - Point (_._.X) */ #undef VERSION_POINT /* * Version number, as a string */ #undef VERSION /* * Status of the code: "alpha", "beta" or "stable". */ #undef CODE_STATUS /* * Should pcre be statically built in instead of linkling with libpcre? * (This is determined by configure depending on the availiability of * libpcre and user preferences). * Don't bother to change this here! Use configure instead. */ #undef FEATURE_DYNAMIC_PCRE /* * Should pcrs be statically built in instead of linkling with libpcrs? * (This is determined by configure depending on the availiability of * libpcrs and user preferences). * Don't bother to change this here! Use configure instead. */ #undef STATIC_PCRS /* * Allows the use of an ACL to control access to the proxy by IP address. */ #undef FEATURE_ACL /* * Allow Privoxy to use accf_http(9) if supported. */ #undef FEATURE_ACCEPT_FILTER /* * Enables the web-based configuration (actionsfile) editor. If you * have a shared proxy, you might want to turn this off. */ #undef FEATURE_CGI_EDIT_ACTIONS /* * Locally redirect remote script-redirect URLs */ #undef FEATURE_FAST_REDIRECTS /* * Bypass filtering for 1 page only */ #undef FEATURE_FORCE_LOAD /* * Allow blocking using images as well as HTML. * If you do not define this then everything is blocked as HTML. */ #undef FEATURE_IMAGE_BLOCKING /* * Use PNG instead of GIF for built-in images */ #undef FEATURE_NO_GIFS /* * Allow to shutdown Privoxy through the webinterface. */ #undef FEATURE_GRACEFUL_TERMINATION /* * Allow PCRE syntax in host patterns. */ #undef FEATURE_EXTENDED_HOST_PATTERNS /* * Allow filtering with scripts and programs. */ #undef FEATURE_EXTERNAL_FILTERS /* * Keep connections alive if possible. */ #undef FEATURE_CONNECTION_KEEP_ALIVE /* * Allow to share outgoing connections between incoming connections. */ #undef FEATURE_CONNECTION_SHARING /* * Use POSIX threads instead of native threads. */ #undef FEATURE_PTHREAD /* * Enables statistics function. */ #undef FEATURE_STATISTICS /* * Enable strptime() sanity checks. */ #undef FEATURE_STRPTIME_SANITY_CHECKS /* * Allow Privoxy to be "disabled" so it is just a normal non-blocking * non-anonymizing proxy. This is useful if you're trying to access a * blocked or broken site - just change the setting in the config file, * or use the handy "Disable" menu option in the Windows GUI. */ #undef FEATURE_TOGGLE /* * Allows the use of trust files. */ #undef FEATURE_TRUST /* * Defined on Solaris only. Makes the system libraries thread safe. */ #undef _REENTRANT /* * Defined on Solaris only. Without this, many important functions are not * defined in the system headers. */ #undef __EXTENSIONS__ /* * Defined always. * FIXME: Don't know what it does or why we need it. * (presumably something to do with MultiThreading?) */ #undef __MT__ /* If the (nonstandard and thread-safe) function gethostbyname_r * is available, select which signature to use */ #undef HAVE_GETHOSTBYNAME_R_6_ARGS #undef HAVE_GETHOSTBYNAME_R_5_ARGS #undef HAVE_GETHOSTBYNAME_R_3_ARGS /* If the (nonstandard and thread-safe) function gethostbyaddr_r * is available, select which signature to use */ #undef HAVE_GETHOSTBYADDR_R_8_ARGS #undef HAVE_GETHOSTBYADDR_R_7_ARGS #undef HAVE_GETHOSTBYADDR_R_5_ARGS /* Defined if you have gmtime_r and localtime_r with a signature * of (struct time *, struct tm *) */ #undef HAVE_GMTIME_R #undef HAVE_LOCALTIME_R /* Define to 'int' if doesn't have it. */ #undef socklen_t /* Define if pcre.h must be included as */ #undef PCRE_H_IN_SUBDIR /* Define if pcreposix.h must be included as */ #undef PCREPOSIX_H_IN_SUBDIR @BOTTOM@ /* * Defined always. * FIXME: Don't know what it does or why we need it. * (presumably something to do with ANSI Standard C?) */ #ifndef __STDC__ #define __STDC__ 1 #endif /* ndef __STDC__ */ /* * Need to set up this define only for the Pthreads library for * Win32, available from http://sources.redhat.com/pthreads-win32/ */ #if defined(FEATURE_PTHREAD) && defined(_WIN32) #define __CLEANUP_C #endif /* defined(FEATURE_PTHREAD) && defined(_WIN32) */ /* * BEOS does not currently support POSIX threads. * This *should* be detected by ./configure, but let's be sure. */ #if defined(FEATURE_PTHREAD) && defined(__BEOS__) #error BEOS does not support pthread - please run ./configure again with "--disable-pthread" #endif /* defined(FEATURE_PTHREAD) && defined(__BEOS__) */ /* * On OpenBSD and maybe also FreeBSD, gcc doesn't define the cpp * symbol unix; it defines __unix__ and sometimes not even that: */ #if ( defined(__unix__) || defined(__NetBSD__) ) && !defined(unix) #define unix 1 #endif /* * It's too easy to accidentally use a Cygwin or MinGW32 version of config.h * under VC++, and it usually gives many weird error messages. Let's make * the error messages understandable, by bailing out now. */ #ifdef _MSC_VER #error For MS VC++, please use vc_config_winthreads.h or vc_config_pthreads.h. You can usually do this by selecting the "Build", "Clean" menu option. #endif /* def _MSC_VER */ #endif /* CONFIG_H_INCLUDED */ privoxy-3.0.28-stable/./actionlist.h000640 001751 000000 00000026251 13412210415 017000 0ustar00fkwheel000000 000000 /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/actionlist.h,v $ * * Purpose : Master list of supported actions. * Not really a header, since it generates code. * This is included (3 times!) from actions.c * Each time, the following macros are defined to * suitable values beforehand: * DEFINE_ACTION_MULTI() * DEFINE_ACTION_STRING() * DEFINE_ACTION_BOOL() * DEFINE_ACTION_ALIAS * * Copyright : Written by and Copyright (C) 2001-2014 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and * Junkbusters Corporation. http://www.junkbusters.com * * 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. * * The GNU General Public License should be included with * this file. If not, you can view it at * http://www.gnu.org/copyleft/gpl.html * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *********************************************************************/ #if !(defined(DEFINE_ACTION_BOOL) && defined(DEFINE_ACTION_MULTI) && defined(DEFINE_ACTION_STRING)) #error Please define lots of macros before including "actionlist.h". #endif /* !defined(all the DEFINE_ACTION_xxx macros) */ #ifndef DEFINE_CGI_PARAM_RADIO #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) #endif /* ndef DEFINE_CGI_PARAM_RADIO */ DEFINE_ACTION_MULTI ("add-header", ACTION_MULTI_ADD_HEADER) DEFINE_ACTION_STRING ("block", ACTION_BLOCK, ACTION_STRING_BLOCK) DEFINE_CGI_PARAM_NO_RADIO("block", ACTION_BLOCK, ACTION_STRING_BLOCK, "No reason specified.") DEFINE_ACTION_STRING ("change-x-forwarded-for", ACTION_CHANGE_X_FORWARDED_FOR, ACTION_STRING_CHANGE_X_FORWARDED_FOR) DEFINE_CGI_PARAM_RADIO ("change-x-forwarded-for", ACTION_CHANGE_X_FORWARDED_FOR, ACTION_STRING_CHANGE_X_FORWARDED_FOR, "block", 0) DEFINE_CGI_PARAM_RADIO ("change-x-forwarded-for", ACTION_CHANGE_X_FORWARDED_FOR, ACTION_STRING_CHANGE_X_FORWARDED_FOR, "add", 1) DEFINE_ACTION_MULTI ("client-header-filter", ACTION_MULTI_CLIENT_HEADER_FILTER) DEFINE_ACTION_MULTI ("client-header-tagger", ACTION_MULTI_CLIENT_HEADER_TAGGER) DEFINE_ACTION_STRING ("content-type-overwrite", ACTION_CONTENT_TYPE_OVERWRITE, ACTION_STRING_CONTENT_TYPE) DEFINE_CGI_PARAM_NO_RADIO("content-type-overwrite", ACTION_CONTENT_TYPE_OVERWRITE, ACTION_STRING_CONTENT_TYPE, "text/html") DEFINE_ACTION_STRING ("crunch-client-header", ACTION_CRUNCH_CLIENT_HEADER, ACTION_STRING_CLIENT_HEADER) DEFINE_CGI_PARAM_NO_RADIO("crunch-client-header", ACTION_CRUNCH_CLIENT_HEADER, ACTION_STRING_CLIENT_HEADER, "X-Whatever:") DEFINE_ACTION_BOOL ("crunch-if-none-match", ACTION_CRUNCH_IF_NONE_MATCH) DEFINE_ACTION_BOOL ("crunch-incoming-cookies", ACTION_CRUNCH_INCOMING_COOKIES) DEFINE_ACTION_BOOL ("crunch-outgoing-cookies", ACTION_CRUNCH_OUTGOING_COOKIES) DEFINE_ACTION_STRING ("crunch-server-header", ACTION_CRUNCH_SERVER_HEADER, ACTION_STRING_SERVER_HEADER) DEFINE_CGI_PARAM_NO_RADIO("crunch-server-header", ACTION_CRUNCH_SERVER_HEADER, ACTION_STRING_SERVER_HEADER, "X-Whatever:") DEFINE_ACTION_STRING ("deanimate-gifs", ACTION_DEANIMATE, ACTION_STRING_DEANIMATE) DEFINE_CGI_PARAM_RADIO ("deanimate-gifs", ACTION_DEANIMATE, ACTION_STRING_DEANIMATE, "first", 0) DEFINE_ACTION_STRING ("delay-response", ACTION_DELAY_RESPONSE, ACTION_STRING_DELAY_RESPONSE) DEFINE_CGI_PARAM_NO_RADIO("delay-response", ACTION_DELAY_RESPONSE, ACTION_STRING_DELAY_RESPONSE, "100") DEFINE_CGI_PARAM_RADIO ("deanimate-gifs", ACTION_DEANIMATE, ACTION_STRING_DEANIMATE, "last", 1) DEFINE_ACTION_BOOL ("downgrade-http-version", ACTION_DOWNGRADE) #ifdef FEATURE_EXTERNAL_FILTERS DEFINE_ACTION_MULTI ("external-filter", ACTION_MULTI_EXTERNAL_FILTER) #endif #ifdef FEATURE_FAST_REDIRECTS DEFINE_ACTION_STRING ("fast-redirects", ACTION_FAST_REDIRECTS, ACTION_STRING_FAST_REDIRECTS) DEFINE_CGI_PARAM_RADIO ("fast-redirects", ACTION_FAST_REDIRECTS, ACTION_STRING_FAST_REDIRECTS, "simple-check", 0) DEFINE_CGI_PARAM_RADIO ("fast-redirects", ACTION_FAST_REDIRECTS, ACTION_STRING_FAST_REDIRECTS, "check-decoded-url", 1) #endif /* def FEATURE_FAST_REDIRECTS */ DEFINE_ACTION_MULTI ("filter", ACTION_MULTI_FILTER) DEFINE_ACTION_BOOL ("force-text-mode", ACTION_FORCE_TEXT_MODE) DEFINE_ACTION_STRING ("forward-override", ACTION_FORWARD_OVERRIDE, ACTION_STRING_FORWARD_OVERRIDE) DEFINE_CGI_PARAM_CUSTOM ("forward-override", ACTION_FORWARD_OVERRIDE, ACTION_STRING_FORWARD_OVERRIDE, "forward .") DEFINE_ACTION_BOOL ("handle-as-empty-document", ACTION_HANDLE_AS_EMPTY_DOCUMENT) DEFINE_ACTION_BOOL ("handle-as-image", ACTION_IMAGE) DEFINE_ACTION_STRING ("hide-accept-language", ACTION_HIDE_ACCEPT_LANGUAGE, ACTION_STRING_LANGUAGE) DEFINE_CGI_PARAM_RADIO ("hide-accept-language", ACTION_HIDE_ACCEPT_LANGUAGE, ACTION_STRING_LANGUAGE, "block", 0) DEFINE_CGI_PARAM_CUSTOM ("hide-accept-language", ACTION_HIDE_ACCEPT_LANGUAGE, ACTION_STRING_LANGUAGE, "de-de") DEFINE_ACTION_STRING ("hide-content-disposition", ACTION_HIDE_CONTENT_DISPOSITION, ACTION_STRING_CONTENT_DISPOSITION) DEFINE_CGI_PARAM_RADIO ("hide-content-disposition", ACTION_HIDE_CONTENT_DISPOSITION, ACTION_STRING_CONTENT_DISPOSITION, "block", 0) DEFINE_CGI_PARAM_CUSTOM ("hide-content-disposition", ACTION_HIDE_CONTENT_DISPOSITION, ACTION_STRING_CONTENT_DISPOSITION, "attachment; filename=WHATEVER.txt") DEFINE_ACTION_STRING ("hide-from-header", ACTION_HIDE_FROM, ACTION_STRING_FROM) DEFINE_CGI_PARAM_RADIO ("hide-from-header", ACTION_HIDE_FROM, ACTION_STRING_FROM, "block", 1) DEFINE_CGI_PARAM_CUSTOM ("hide-from-header", ACTION_HIDE_FROM, ACTION_STRING_FROM, "spam_me_senseless@sittingduck.xyz") DEFINE_ACTION_STRING ("hide-if-modified-since", ACTION_HIDE_IF_MODIFIED_SINCE, ACTION_STRING_IF_MODIFIED_SINCE) DEFINE_CGI_PARAM_RADIO ("hide-if-modified-since", ACTION_HIDE_IF_MODIFIED_SINCE, ACTION_STRING_IF_MODIFIED_SINCE, "block", 0) DEFINE_CGI_PARAM_CUSTOM ("hide-if-modified-since", ACTION_HIDE_IF_MODIFIED_SINCE, ACTION_STRING_IF_MODIFIED_SINCE, "-1") DEFINE_ACTION_STRING ("hide-referrer", ACTION_HIDE_REFERER, ACTION_STRING_REFERER) DEFINE_CGI_PARAM_RADIO ("hide-referrer", ACTION_HIDE_REFERER, ACTION_STRING_REFERER, "conditional-forge", 3) DEFINE_CGI_PARAM_RADIO ("hide-referrer", ACTION_HIDE_REFERER, ACTION_STRING_REFERER, "conditional-block", 2) DEFINE_CGI_PARAM_RADIO ("hide-referrer", ACTION_HIDE_REFERER, ACTION_STRING_REFERER, "forge", 1) DEFINE_CGI_PARAM_RADIO ("hide-referrer", ACTION_HIDE_REFERER, ACTION_STRING_REFERER, "block", 0) DEFINE_CGI_PARAM_CUSTOM ("hide-referrer", ACTION_HIDE_REFERER, ACTION_STRING_REFERER, "http://www.privoxy.org/") DEFINE_ACTION_STRING ("hide-user-agent", ACTION_HIDE_USER_AGENT, ACTION_STRING_USER_AGENT) DEFINE_CGI_PARAM_NO_RADIO("hide-user-agent", ACTION_HIDE_USER_AGENT, ACTION_STRING_USER_AGENT, "Privoxy " VERSION) DEFINE_ACTION_STRING ("limit-connect", ACTION_LIMIT_CONNECT, ACTION_STRING_LIMIT_CONNECT) DEFINE_CGI_PARAM_NO_RADIO("limit-connect", ACTION_LIMIT_CONNECT, ACTION_STRING_LIMIT_CONNECT, "443") DEFINE_ACTION_STRING ("limit-cookie-lifetime", ACTION_LIMIT_COOKIE_LIFETIME, ACTION_STRING_LIMIT_COOKIE_LIFETIME) DEFINE_CGI_PARAM_CUSTOM ("limit-cookie-lifetime", ACTION_LIMIT_COOKIE_LIFETIME, ACTION_STRING_LIMIT_COOKIE_LIFETIME, "60") DEFINE_ACTION_STRING ("overwrite-last-modified", ACTION_OVERWRITE_LAST_MODIFIED, ACTION_STRING_LAST_MODIFIED) DEFINE_CGI_PARAM_RADIO ("overwrite-last-modified", ACTION_OVERWRITE_LAST_MODIFIED, ACTION_STRING_LAST_MODIFIED, "block", 0) DEFINE_CGI_PARAM_RADIO ("overwrite-last-modified", ACTION_OVERWRITE_LAST_MODIFIED, ACTION_STRING_LAST_MODIFIED, "reset-to-request-time", 1) DEFINE_CGI_PARAM_RADIO ("overwrite-last-modified", ACTION_OVERWRITE_LAST_MODIFIED, ACTION_STRING_LAST_MODIFIED, "randomize", 2) DEFINE_ACTION_BOOL ("prevent-compression", ACTION_NO_COMPRESSION) DEFINE_ACTION_STRING ("redirect", ACTION_REDIRECT, ACTION_STRING_REDIRECT) DEFINE_CGI_PARAM_NO_RADIO("redirect", ACTION_REDIRECT, ACTION_STRING_REDIRECT, "http://localhost/") DEFINE_ACTION_MULTI ("server-header-filter", ACTION_MULTI_SERVER_HEADER_FILTER) DEFINE_ACTION_MULTI ("server-header-tagger", ACTION_MULTI_SERVER_HEADER_TAGGER) DEFINE_ACTION_BOOL ("session-cookies-only", ACTION_SESSION_COOKIES_ONLY) DEFINE_ACTION_STRING ("set-image-blocker", ACTION_IMAGE_BLOCKER, ACTION_STRING_IMAGE_BLOCKER) DEFINE_CGI_PARAM_RADIO ("set-image-blocker", ACTION_IMAGE_BLOCKER, ACTION_STRING_IMAGE_BLOCKER, "pattern", 1) DEFINE_CGI_PARAM_RADIO ("set-image-blocker", ACTION_IMAGE_BLOCKER, ACTION_STRING_IMAGE_BLOCKER, "blank", 0) DEFINE_CGI_PARAM_CUSTOM ("set-image-blocker", ACTION_IMAGE_BLOCKER, ACTION_STRING_IMAGE_BLOCKER, CGI_PREFIX "send-banner?type=pattern") #if DEFINE_ACTION_ALIAS /* * Alternative spellings */ DEFINE_ACTION_STRING ("hide-referer", ACTION_HIDE_REFERER, ACTION_STRING_REFERER) DEFINE_ACTION_BOOL ("prevent-keeping-cookies", ACTION_SESSION_COOKIES_ONLY) /* * Pre-3.0.7 (pseudo) compatibility */ DEFINE_ACTION_MULTI ("filter-client-headers", ACTION_MULTI_CLIENT_HEADER_FILTER) DEFINE_ACTION_MULTI ("filter-server-headers", ACTION_MULTI_SERVER_HEADER_FILTER) #endif /* if DEFINE_ACTION_ALIAS */ #undef DEFINE_ACTION_MULTI #undef DEFINE_ACTION_STRING #undef DEFINE_ACTION_BOOL #undef DEFINE_ACTION_ALIAS #undef DEFINE_CGI_PARAM_CUSTOM #undef DEFINE_CGI_PARAM_RADIO #undef DEFINE_CGI_PARAM_NO_RADIO privoxy-3.0.28-stable/./actions.c000640 001751 000000 00000170632 13412210415 016265 0ustar00fkwheel000000 000000 /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/actions.c,v $ * * Purpose : Declares functions to work with actions files * * Copyright : Written by and Copyright (C) 2001-2016 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and * Junkbusters Corporation. http://www.junkbusters.com * * 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. * * The GNU General Public License should be included with * this file. If not, you can view it at * http://www.gnu.org/copyleft/gpl.html * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *********************************************************************/ #include "config.h" #include #include #include #include #ifdef FEATURE_PTHREAD #include #endif #include "project.h" #include "jcc.h" #include "list.h" #include "actions.h" #include "miscutil.h" #include "errlog.h" #include "loaders.h" #include "encode.h" #include "urlmatch.h" #include "cgi.h" #include "ssplit.h" #include "filters.h" /* * We need the main list of options. * * First, we need a way to tell between boolean, string, and multi-string * options. For string and multistring options, we also need to be * able to tell the difference between a "+" and a "-". (For bools, * the "+"/"-" information is encoded in "add" and "mask"). So we use * an enumerated type (well, the preprocessor equivalent). Here are * the values: */ enum action_value_type { AV_NONE = 0, /* +opt -opt */ AV_ADD_STRING = 1, /* +stropt{string} */ AV_REM_STRING = 2, /* -stropt */ AV_ADD_MULTI = 3, /* +multiopt{string} +multiopt{string2} */ AV_REM_MULTI = 4 /* -multiopt{string} -multiopt */ }; /* * We need a structure to hold the name, flag changes, * type, and string index. */ struct action_name { const char * name; unsigned long mask; /* a bit set to "0" = remove action */ unsigned long add; /* a bit set to "1" = add action */ enum action_value_type value_type; /* an AV_... constant */ int index; /* index into strings[] or multi[] */ }; /* * And with those building blocks in place, here's the array. */ static const struct action_name action_names[] = { /* * Well actually there's no data here - it's in actionlist.h * This keeps it together to make it easy to change. * * Here's the macros used to format it: */ #define DEFINE_ACTION_MULTI(name,index) \ { "+" name, ACTION_MASK_ALL, 0, AV_ADD_MULTI, index }, \ { "-" name, ACTION_MASK_ALL, 0, AV_REM_MULTI, index }, #define DEFINE_ACTION_STRING(name,flag,index) \ { "+" name, ACTION_MASK_ALL, flag, AV_ADD_STRING, index }, \ { "-" name, ~flag, 0, AV_REM_STRING, index }, #define DEFINE_ACTION_BOOL(name,flag) \ { "+" name, ACTION_MASK_ALL, flag }, \ { "-" name, ~flag, 0 }, #define DEFINE_ACTION_ALIAS 1 /* Want aliases please */ #include "actionlist.h" #undef DEFINE_ACTION_MULTI #undef DEFINE_ACTION_STRING #undef DEFINE_ACTION_BOOL #undef DEFINE_ACTION_ALIAS { NULL, 0, 0 } /* End marker */ }; #ifndef FUZZ static #endif int load_one_actions_file(struct client_state *csp, int fileid); /********************************************************************* * * Function : merge_actions * * Description : Merge two actions together. * Similar to "dest += src". * * Parameters : * 1 : dest = Actions to modify. * 2 : src = Action to add. * * Returns : JB_ERR_OK or JB_ERR_MEMORY * *********************************************************************/ jb_err merge_actions (struct action_spec *dest, const struct action_spec *src) { int i; jb_err err; dest->mask &= src->mask; dest->add &= src->mask; dest->add |= src->add; for (i = 0; i < ACTION_STRING_COUNT; i++) { char * str = src->string[i]; if (str) { freez(dest->string[i]); dest->string[i] = strdup_or_die(str); } } for (i = 0; i < ACTION_MULTI_COUNT; i++) { if (src->multi_remove_all[i]) { /* Remove everything from dest */ list_remove_all(dest->multi_remove[i]); dest->multi_remove_all[i] = 1; err = list_duplicate(dest->multi_add[i], src->multi_add[i]); } else if (dest->multi_remove_all[i]) { /* * dest already removes everything, so we only need to worry * about what we add. */ list_remove_list(dest->multi_add[i], src->multi_remove[i]); err = list_append_list_unique(dest->multi_add[i], src->multi_add[i]); } else { /* No "remove all"s to worry about. */ list_remove_list(dest->multi_add[i], src->multi_remove[i]); err = list_append_list_unique(dest->multi_remove[i], src->multi_remove[i]); if (!err) err = list_append_list_unique(dest->multi_add[i], src->multi_add[i]); } if (err) { return err; } } return JB_ERR_OK; } /********************************************************************* * * Function : copy_action * * Description : Copy an action_specs. * Similar to "dest = src". * * Parameters : * 1 : dest = Destination of copy. * 2 : src = Source for copy. * * Returns : JB_ERR_OK or JB_ERR_MEMORY * *********************************************************************/ jb_err copy_action (struct action_spec *dest, const struct action_spec *src) { int i; jb_err err = JB_ERR_OK; free_action(dest); memset(dest, '\0', sizeof(*dest)); dest->mask = src->mask; dest->add = src->add; for (i = 0; i < ACTION_STRING_COUNT; i++) { char * str = src->string[i]; if (str) { str = strdup_or_die(str); dest->string[i] = str; } } for (i = 0; i < ACTION_MULTI_COUNT; i++) { dest->multi_remove_all[i] = src->multi_remove_all[i]; err = list_duplicate(dest->multi_remove[i], src->multi_remove[i]); if (err) { return err; } err = list_duplicate(dest->multi_add[i], src->multi_add[i]); if (err) { return err; } } return err; } /********************************************************************* * * Function : free_action_spec * * Description : Frees an action_spec and the memory used by it. * * Parameters : * 1 : src = Source to free. * * Returns : N/A * *********************************************************************/ void free_action_spec(struct action_spec *src) { free_action(src); freez(src); } /********************************************************************* * * Function : free_action * * Description : Destroy an action_spec. Frees memory used by it, * except for the memory used by the struct action_spec * itself. * * Parameters : * 1 : src = Source to free. * * Returns : N/A * *********************************************************************/ void free_action (struct action_spec *src) { int i; if (src == NULL) { return; } for (i = 0; i < ACTION_STRING_COUNT; i++) { freez(src->string[i]); } for (i = 0; i < ACTION_MULTI_COUNT; i++) { destroy_list(src->multi_remove[i]); destroy_list(src->multi_add[i]); } memset(src, '\0', sizeof(*src)); } /********************************************************************* * * Function : get_action_token * * Description : Parses a line for the first action. * Modifies its input array, doesn't allocate memory. * e.g. given: * *line=" +abc{def} -ghi " * Returns: * *line=" -ghi " * *name="+abc" * *value="def" * * Parameters : * 1 : line = [in] The line containing the action. * [out] Start of next action on line, or * NULL if we reached the end of line before * we found an action. * 2 : name = [out] Start of action name, null * terminated. NULL on EOL * 3 : value = [out] Start of action value, null * terminated. NULL if none or EOL. * * Returns : JB_ERR_OK => Ok * JB_ERR_PARSE => Mismatched {} (line was trashed anyway) * *********************************************************************/ jb_err get_action_token(char **line, char **name, char **value) { char * str = *line; char ch; /* set default returns */ *line = NULL; *name = NULL; *value = NULL; /* Eat any leading whitespace */ while ((*str == ' ') || (*str == '\t')) { str++; } if (*str == '\0') { return 0; } if (*str == '{') { /* null name, just value is prohibited */ return JB_ERR_PARSE; } *name = str; /* parse option */ while (((ch = *str) != '\0') && (ch != ' ') && (ch != '\t') && (ch != '{')) { if (ch == '}') { /* error, '}' without '{' */ return JB_ERR_PARSE; } str++; } *str = '\0'; if (ch != '{') { /* no value */ if (ch == '\0') { /* EOL - be careful not to run off buffer */ *line = str; } else { /* More to parse next time. */ *line = str + 1; } return JB_ERR_OK; } str++; *value = str; /* The value ends with the first non-escaped closing curly brace */ while ((str = strchr(str, '}')) != NULL) { if (str[-1] == '\\') { /* Overwrite the '\' so the action doesn't see it. */ string_move(str-1, str); continue; } break; } if (str == NULL) { /* error */ *value = NULL; return JB_ERR_PARSE; } /* got value */ *str = '\0'; *line = str + 1; chomp(*value); return JB_ERR_OK; } /********************************************************************* * * Function : action_used_to_be_valid * * Description : Checks if unrecognized actions were valid in earlier * releases. * * Parameters : * 1 : action = The string containing the action to check. * * Returns : True if yes, otherwise false. * *********************************************************************/ static int action_used_to_be_valid(const char *action) { static const char * const formerly_valid_actions[] = { "inspect-jpegs", "kill-popups", "send-vanilla-wafer", "send-wafer", "treat-forbidden-connects-like-blocks", "vanilla-wafer", "wafer" }; unsigned int i; for (i = 0; i < SZ(formerly_valid_actions); i++) { if (0 == strcmpic(action, formerly_valid_actions[i])) { return TRUE; } } return FALSE; } /********************************************************************* * * Function : get_actions * * Description : Parses a list of actions. * * Parameters : * 1 : line = The string containing the actions. * Will be written to by this function. * 2 : alias_list = Custom alias list, or NULL for none. * 3 : cur_action = Where to store the action. Caller * allocates memory. * * Returns : JB_ERR_OK => Ok * JB_ERR_PARSE => Parse error (line was trashed anyway) * nonzero => Out of memory (line was trashed anyway) * *********************************************************************/ jb_err get_actions(char *line, struct action_alias * alias_list, struct action_spec *cur_action) { jb_err err; init_action(cur_action); cur_action->mask = ACTION_MASK_ALL; while (line) { char * option = NULL; char * value = NULL; err = get_action_token(&line, &option, &value); if (err) { return err; } if (option) { /* handle option in 'option' */ /* Check for standard action name */ const struct action_name * action = action_names; while ((action->name != NULL) && (0 != strcmpic(action->name, option))) { action++; } if (action->name != NULL) { /* Found it */ cur_action->mask &= action->mask; cur_action->add &= action->mask; cur_action->add |= action->add; switch (action->value_type) { case AV_NONE: if (value != NULL) { log_error(LOG_LEVEL_ERROR, "Action %s does not take parameters but %s was given.", action->name, value); return JB_ERR_PARSE; } break; case AV_ADD_STRING: { /* add single string. */ if ((value == NULL) || (*value == '\0')) { if (0 == strcmpic(action->name, "+block")) { /* * XXX: Temporary backwards compatibility hack. * XXX: should include line number. */ value = "No reason specified."; log_error(LOG_LEVEL_ERROR, "block action without reason found. This may " "become a fatal error in future versions."); } else { return JB_ERR_PARSE; } } /* FIXME: should validate option string here */ freez (cur_action->string[action->index]); cur_action->string[action->index] = strdup(value); if (NULL == cur_action->string[action->index]) { return JB_ERR_MEMORY; } break; } case AV_REM_STRING: { /* remove single string. */ freez (cur_action->string[action->index]); break; } case AV_ADD_MULTI: { /* append multi string. */ struct list * remove_p = cur_action->multi_remove[action->index]; struct list * add_p = cur_action->multi_add[action->index]; if ((value == NULL) || (*value == '\0')) { return JB_ERR_PARSE; } list_remove_item(remove_p, value); err = enlist_unique(add_p, value, 0); if (err) { return err; } break; } case AV_REM_MULTI: { /* remove multi string. */ struct list * remove_p = cur_action->multi_remove[action->index]; struct list * add_p = cur_action->multi_add[action->index]; if ((value == NULL) || (*value == '\0') || ((*value == '*') && (value[1] == '\0'))) { /* * no option, or option == "*". * * Remove *ALL*. */ list_remove_all(remove_p); list_remove_all(add_p); cur_action->multi_remove_all[action->index] = 1; } else { /* Valid option - remove only 1 option */ if (!cur_action->multi_remove_all[action->index]) { /* there isn't a catch-all in the remove list already */ err = enlist_unique(remove_p, value, 0); if (err) { return err; } } list_remove_item(add_p, value); } break; } default: /* Shouldn't get here unless there's memory corruption. */ assert(0); return JB_ERR_PARSE; } } else { /* try user aliases. */ const struct action_alias * alias = alias_list; while ((alias != NULL) && (0 != strcmpic(alias->name, option))) { alias = alias->next; } if (alias != NULL) { /* Found it */ merge_actions(cur_action, alias->action); } else if (((size_t)2 < strlen(option)) && action_used_to_be_valid(option+1)) { log_error(LOG_LEVEL_ERROR, "Action '%s' is no longer valid " "in this Privoxy release. Ignored.", option+1); } else if (((size_t)2 < strlen(option)) && 0 == strcmpic(option+1, "hide-forwarded-for-headers")) { log_error(LOG_LEVEL_FATAL, "The action 'hide-forwarded-for-headers' " "is no longer valid in this Privoxy release. " "Use 'change-x-forwarded-for' instead."); } else { /* Bad action name */ /* * XXX: This is a fatal error and Privoxy will later on exit * in load_one_actions_file() because of an "invalid line". * * It would be preferable to name the offending option in that * error message, but currently there is no way to do that and * we have to live with two error messages for basically the * same reason. */ log_error(LOG_LEVEL_ERROR, "Unknown action or alias: %s", option); return JB_ERR_PARSE; } } } } return JB_ERR_OK; } /********************************************************************* * * Function : init_current_action * * Description : Zero out an action. * * Parameters : * 1 : dest = An uninitialized current_action_spec. * * Returns : N/A * *********************************************************************/ void init_current_action (struct current_action_spec *dest) { memset(dest, '\0', sizeof(*dest)); dest->flags = ACTION_MOST_COMPATIBLE; } /********************************************************************* * * Function : init_action * * Description : Zero out an action. * * Parameters : * 1 : dest = An uninitialized action_spec. * * Returns : N/A * *********************************************************************/ void init_action (struct action_spec *dest) { memset(dest, '\0', sizeof(*dest)); } /********************************************************************* * * Function : merge_current_action * * Description : Merge two actions together. * Similar to "dest += src". * Differences between this and merge_actions() * is that this one doesn't allocate memory for * strings (so "src" better be in memory for at least * as long as "dest" is, and you'd better free * "dest" using "free_current_action"). * Also, there is no mask or remove lists in dest. * (If we're applying it to a URL, we don't need them) * * Parameters : * 1 : dest = Current actions, to modify. * 2 : src = Action to add. * * Returns 0 : no error * !=0 : error, probably JB_ERR_MEMORY. * *********************************************************************/ jb_err merge_current_action (struct current_action_spec *dest, const struct action_spec *src) { int i; jb_err err = JB_ERR_OK; dest->flags &= src->mask; dest->flags |= src->add; for (i = 0; i < ACTION_STRING_COUNT; i++) { char * str = src->string[i]; if (str) { str = strdup_or_die(str); freez(dest->string[i]); dest->string[i] = str; } } for (i = 0; i < ACTION_MULTI_COUNT; i++) { if (src->multi_remove_all[i]) { /* Remove everything from dest, then add src->multi_add */ err = list_duplicate(dest->multi[i], src->multi_add[i]); if (err) { return err; } } else { list_remove_list(dest->multi[i], src->multi_remove[i]); err = list_append_list_unique(dest->multi[i], src->multi_add[i]); if (err) { return err; } } } return err; } /********************************************************************* * * Function : update_action_bits_for_tag * * Description : Updates the action bits based on the action sections * whose tag patterns match a provided tag. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : tag = The tag on which the update should be based on * * Returns : 0 if no tag matched, or * 1 otherwise * *********************************************************************/ int update_action_bits_for_tag(struct client_state *csp, const char *tag) { struct file_list *fl; struct url_actions *b; int updated = 0; int i; assert(tag); assert(list_contains_item(csp->tags, tag)); /* Run through all action files, */ for (i = 0; i < MAX_AF_FILES; i++) { if (((fl = csp->actions_list[i]) == NULL) || ((b = fl->f) == NULL)) { /* Skip empty files */ continue; } /* and through all the action patterns, */ for (b = b->next; NULL != b; b = b->next) { /* skip everything but TAG patterns, */ if (!(b->url->flags & PATTERN_SPEC_TAG_PATTERN)) { continue; } /* and check if one of the tag patterns matches the tag, */ if (0 == regexec(b->url->pattern.tag_regex, tag, 0, NULL, 0)) { /* if it does, update the action bit map, */ if (merge_current_action(csp->action, b->action)) { log_error(LOG_LEVEL_ERROR, "Out of memory while changing action bits"); } /* and signal the change. */ updated = 1; } } } return updated; } /********************************************************************* * * Function : check_negative_tag_patterns * * Description : Updates the action bits based on NO-*-TAG patterns. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : flag = The tag pattern type * * Returns : JB_ERR_OK in case off success, or * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err check_negative_tag_patterns(struct client_state *csp, unsigned int flag) { struct list_entry *tag; struct file_list *fl; struct url_actions *b = NULL; int i; for (i = 0; i < MAX_AF_FILES; i++) { fl = csp->actions_list[i]; if ((fl == NULL) || ((b = fl->f) == NULL)) { continue; } for (b = b->next; NULL != b; b = b->next) { int tag_found = 0; if (0 == (b->url->flags & flag)) { continue; } for (tag = csp->tags->first; NULL != tag; tag = tag->next) { if (0 == regexec(b->url->pattern.tag_regex, tag->str, 0, NULL, 0)) { /* * The pattern matches at least one tag, thus the action * section doesn't apply and we don't need to look at the * other tags. */ tag_found = 1; break; } } if (!tag_found) { /* * The pattern doesn't match any tags, * thus the action section applies. */ if (merge_current_action(csp->action, b->action)) { log_error(LOG_LEVEL_ERROR, "Out of memory while changing action bits"); return JB_ERR_MEMORY; } log_error(LOG_LEVEL_HEADER, "Updated action bits based on: %s", b->url->spec); } } } return JB_ERR_OK; } /********************************************************************* * * Function : free_current_action * * Description : Free memory used by a current_action_spec. * Does not free the current_action_spec itself. * * Parameters : * 1 : src = Source to free. * * Returns : N/A * *********************************************************************/ void free_current_action(struct current_action_spec *src) { int i; for (i = 0; i < ACTION_STRING_COUNT; i++) { freez(src->string[i]); } for (i = 0; i < ACTION_MULTI_COUNT; i++) { destroy_list(src->multi[i]); } memset(src, '\0', sizeof(*src)); } static struct file_list *current_actions_file[MAX_AF_FILES] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; #ifdef FEATURE_GRACEFUL_TERMINATION /********************************************************************* * * Function : unload_current_actions_file * * Description : Unloads current actions file - reset to state at * beginning of program. * * Parameters : None * * Returns : N/A * *********************************************************************/ void unload_current_actions_file(void) { int i; for (i = 0; i < MAX_AF_FILES; i++) { if (current_actions_file[i]) { current_actions_file[i]->unloader = unload_actions_file; current_actions_file[i] = NULL; } } } #endif /* FEATURE_GRACEFUL_TERMINATION */ /********************************************************************* * * Function : unload_actions_file * * Description : Unloads an actions module. * * Parameters : * 1 : file_data = the data structure associated with the * actions file. * * Returns : N/A * *********************************************************************/ void unload_actions_file(void *file_data) { struct url_actions * next; struct url_actions * cur = (struct url_actions *)file_data; while (cur != NULL) { next = cur->next; free_pattern_spec(cur->url); if ((next == NULL) || (next->action != cur->action)) { /* * As the action settings might be shared, * we can only free them if the current * url pattern is the last one, or if the * next one is using different settings. */ free_action_spec(cur->action); } freez(cur); cur = next; } } /********************************************************************* * * Function : free_alias_list * * Description : Free memory used by a list of aliases. * * Parameters : * 1 : alias_list = Linked list to free. * * Returns : N/A * *********************************************************************/ void free_alias_list(struct action_alias *alias_list) { while (alias_list != NULL) { struct action_alias * next = alias_list->next; alias_list->next = NULL; freez(alias_list->name); free_action(alias_list->action); free(alias_list); alias_list = next; } } /********************************************************************* * * Function : load_action_files * * Description : Read and parse all the action files and add to files * list. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * * Returns : 0 => Ok, everything else is an error. * *********************************************************************/ int load_action_files(struct client_state *csp) { int i; int result; for (i = 0; i < MAX_AF_FILES; i++) { if (csp->config->actions_file[i]) { result = load_one_actions_file(csp, i); if (result) { return result; } } else if (current_actions_file[i]) { current_actions_file[i]->unloader = unload_actions_file; current_actions_file[i] = NULL; } } return 0; } /********************************************************************* * * Function : filter_type_to_string * * Description : Converts a filter type enum into a string. * * Parameters : * 1 : filter_type = filter_type as enum * * Returns : Pointer to static string. * *********************************************************************/ static const char *filter_type_to_string(enum filter_type filter_type) { switch (filter_type) { case FT_CONTENT_FILTER: return "content filter"; case FT_CLIENT_HEADER_FILTER: return "client-header filter"; case FT_SERVER_HEADER_FILTER: return "server-header filter"; case FT_CLIENT_HEADER_TAGGER: return "client-header tagger"; case FT_SERVER_HEADER_TAGGER: return "server-header tagger"; #ifdef FEATURE_EXTERNAL_FILTERS case FT_EXTERNAL_CONTENT_FILTER: return "external content filter"; #endif case FT_INVALID_FILTER: return "invalid filter type"; } return "unknown filter type"; } /********************************************************************* * * Function : referenced_filters_are_missing * * Description : Checks if any filters of a certain type referenced * in an action spec are missing. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : cur_action = The action spec to check. * 3 : multi_index = The index where to look for the filter. * 4 : filter_type = The filter type the caller is interested in. * * Returns : 0 => All referenced filters exist, everything else is an error. * *********************************************************************/ static int referenced_filters_are_missing(const struct client_state *csp, const struct action_spec *cur_action, int multi_index, enum filter_type filter_type) { struct list_entry *filtername; for (filtername = cur_action->multi_add[multi_index]->first; filtername; filtername = filtername->next) { if (NULL == get_filter(csp, filtername->str, filter_type)) { log_error(LOG_LEVEL_ERROR, "Missing %s '%s'", filter_type_to_string(filter_type), filtername->str); return 1; } } return 0; } /********************************************************************* * * Function : action_spec_is_valid * * Description : Should eventually figure out if an action spec * is valid, but currently only checks that the * referenced filters are accounted for. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : cur_action = The action spec to check. * * Returns : 0 => No problems detected, everything else is an error. * *********************************************************************/ static int action_spec_is_valid(struct client_state *csp, const struct action_spec *cur_action) { struct { int multi_index; enum filter_type filter_type; } filter_map[] = { {ACTION_MULTI_FILTER, FT_CONTENT_FILTER}, {ACTION_MULTI_CLIENT_HEADER_FILTER, FT_CLIENT_HEADER_FILTER}, {ACTION_MULTI_SERVER_HEADER_FILTER, FT_SERVER_HEADER_FILTER}, {ACTION_MULTI_CLIENT_HEADER_TAGGER, FT_CLIENT_HEADER_TAGGER}, {ACTION_MULTI_SERVER_HEADER_TAGGER, FT_SERVER_HEADER_TAGGER} }; int errors = 0; int i; for (i = 0; i < SZ(filter_map); i++) { errors += referenced_filters_are_missing(csp, cur_action, filter_map[i].multi_index, filter_map[i].filter_type); } return errors; } /********************************************************************* * * Function : load_one_actions_file * * Description : Read and parse a action file and add to files * list. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : fileid = File index to load. * * Returns : 0 => Ok, everything else is an error. * *********************************************************************/ #ifndef FUZZ static #endif int load_one_actions_file(struct client_state *csp, int fileid) { /* * Parser mode. * Note: Keep these in the order they occur in the file, they are * sometimes tested with <= */ enum { MODE_START_OF_FILE = 1, MODE_SETTINGS = 2, MODE_DESCRIPTION = 3, MODE_ALIAS = 4, MODE_ACTIONS = 5 } mode; FILE *fp; struct url_actions *last_perm; struct url_actions *perm; char *buf; struct file_list *fs; struct action_spec * cur_action = NULL; int cur_action_used = 0; struct action_alias * alias_list = NULL; unsigned long linenum = 0; mode = MODE_START_OF_FILE; if (!check_file_changed(current_actions_file[fileid], csp->config->actions_file[fileid], &fs)) { /* No need to load */ csp->actions_list[fileid] = current_actions_file[fileid]; return 0; } if (!fs) { log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': %E. " "Note that beginning with Privoxy 3.0.7, actions files have to be specified " "with their complete file names.", csp->config->actions_file[fileid]); return 1; /* never get here */ } fs->f = last_perm = zalloc_or_die(sizeof(*last_perm)); if ((fp = fopen(csp->config->actions_file[fileid], "r")) == NULL) { log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error opening file: %E", csp->config->actions_file[fileid]); return 1; /* never get here */ } log_error(LOG_LEVEL_INFO, "Loading actions file: %s", csp->config->actions_file[fileid]); while (read_config_line(fp, &linenum, &buf) != NULL) { if (*buf == '{') { /* It's a header block */ if (buf[1] == '{') { /* It's {{settings}} or {{alias}} */ size_t len = strlen(buf); char * start = buf + 2; char * end = buf + len - 1; if ((len < (size_t)5) || (*end-- != '}') || (*end-- != '}')) { /* too short */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': invalid line (%lu): %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } /* Trim leading and trailing whitespace. */ end[1] = '\0'; chomp(start); if (*start == '\0') { /* too short */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': invalid line (%lu): {{ }}", csp->config->actions_file[fileid], linenum); return 1; /* never get here */ } /* * An actionsfile can optionally contain the following blocks. * They *MUST* be in this order, to simplify processing: * * {{settings}} * name=value... * * {{description}} * ...free text, format TBD, but no line may start with a '{'... * * {{alias}} * name=actions... * * The actual actions must be *after* these special blocks. * None of these special blocks may be repeated. * */ if (0 == strcmpic(start, "settings")) { /* it's a {{settings}} block */ if (mode >= MODE_SETTINGS) { /* {{settings}} must be first thing in file and must only * appear once. */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': line %lu: {{settings}} must only appear once, and it must be before anything else.", csp->config->actions_file[fileid], linenum); } mode = MODE_SETTINGS; } else if (0 == strcmpic(start, "description")) { /* it's a {{description}} block */ if (mode >= MODE_DESCRIPTION) { /* {{description}} is a singleton and only {{settings}} may proceed it */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': line %lu: {{description}} must only appear once, and only a {{settings}} block may be above it.", csp->config->actions_file[fileid], linenum); } mode = MODE_DESCRIPTION; } else if (0 == strcmpic(start, "alias")) { /* it's an {{alias}} block */ if (mode >= MODE_ALIAS) { /* {{alias}} must be first thing in file, possibly after * {{settings}} and {{description}} * * {{alias}} must only appear once. * * Note that these are new restrictions introduced in * v2.9.10 in order to make actionsfile editing simpler. * (Otherwise, reordering actionsfile entries without * completely rewriting the file becomes non-trivial) */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': line %lu: {{alias}} must only appear once, and it must be before all actions.", csp->config->actions_file[fileid], linenum); } mode = MODE_ALIAS; } else { /* invalid {{something}} block */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': invalid line (%lu): {{%s}}", csp->config->actions_file[fileid], linenum, start); return 1; /* never get here */ } } else { /* It's an actions block */ char *actions_buf; char * end; /* set mode */ mode = MODE_ACTIONS; /* free old action */ if (cur_action) { if (!cur_action_used) { free_action_spec(cur_action); } cur_action = NULL; } cur_action_used = 0; cur_action = zalloc_or_die(sizeof(*cur_action)); init_action(cur_action); /* * Copy the buffer before messing with it as we may need the * unmodified version in for the fatal error messages. Given * that this is not a common event, we could instead simply * read the line again. * * buf + 1 to skip the leading '{' */ actions_buf = end = strdup_or_die(buf + 1); /* check we have a trailing } and then trim it */ if (strlen(actions_buf)) { end += strlen(actions_buf) - 1; } if (*end != '}') { /* No closing } */ fclose(fp); freez(actions_buf); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': " "Missing trailing '}' in action section starting at line (%lu): %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } *end = '\0'; /* trim any whitespace immediately inside {} */ chomp(actions_buf); if (get_actions(actions_buf, alias_list, cur_action)) { /* error */ fclose(fp); freez(actions_buf); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': " "can't completely parse the action section starting at line (%lu): %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } if (action_spec_is_valid(csp, cur_action)) { log_error(LOG_LEVEL_ERROR, "Invalid action section in file '%s', " "starting at line %lu: %s", csp->config->actions_file[fileid], linenum, buf); } freez(actions_buf); } } else if (mode == MODE_SETTINGS) { /* * Part of the {{settings}} block. * For now only serves to check if the file's minimum Privoxy * version requirement is met, but we may want to read & check * permissions when we go multi-user. */ if (!strncmp(buf, "for-privoxy-version=", 20)) { char *version_string, *fields[3]; int num_fields; version_string = strdup_or_die(buf + 20); num_fields = ssplit(version_string, ".", fields, SZ(fields)); if (num_fields < 1 || atoi(fields[0]) == 0) { log_error(LOG_LEVEL_ERROR, "While loading actions file '%s': invalid line (%lu): %s", csp->config->actions_file[fileid], linenum, buf); } else if ( (atoi(fields[0]) > VERSION_MAJOR) || ((num_fields > 1) && (atoi(fields[1]) > VERSION_MINOR)) || ((num_fields > 2) && (atoi(fields[2]) > VERSION_POINT))) { fclose(fp); log_error(LOG_LEVEL_FATAL, "Actions file '%s', line %lu requires newer Privoxy version: %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } free(version_string); } } else if (mode == MODE_DESCRIPTION) { /* * Part of the {{description}} block. * Ignore for now. */ } else if (mode == MODE_ALIAS) { /* * define an alias */ char actions_buf[BUFFER_SIZE]; struct action_alias * new_alias; char * start = strchr(buf, '='); char * end = start; if ((start == NULL) || (start == buf)) { log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': invalid alias line (%lu): %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } new_alias = zalloc_or_die(sizeof(*new_alias)); /* Eat any the whitespace before the '=' */ end--; while ((*end == ' ') || (*end == '\t')) { /* * we already know we must have at least 1 non-ws char * at start of buf - no need to check */ end--; } end[1] = '\0'; /* Eat any the whitespace after the '=' */ start++; while ((*start == ' ') || (*start == '\t')) { start++; } if (*start == '\0') { log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': invalid alias line (%lu): %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } new_alias->name = strdup_or_die(buf); strlcpy(actions_buf, start, sizeof(actions_buf)); if (get_actions(actions_buf, alias_list, new_alias->action)) { /* error */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': invalid alias line (%lu): %s = %s", csp->config->actions_file[fileid], linenum, buf, start); return 1; /* never get here */ } /* add to list */ new_alias->next = alias_list; alias_list = new_alias; } else if (mode == MODE_ACTIONS) { /* it's an URL pattern */ /* allocate a new node */ perm = zalloc_or_die(sizeof(*perm)); perm->action = cur_action; cur_action_used = 1; /* Save the URL pattern */ if (create_pattern_spec(perm->url, buf)) { fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': line %lu: cannot create URL or TAG pattern from: %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } /* add it to the list */ last_perm->next = perm; last_perm = perm; } else if (mode == MODE_START_OF_FILE) { /* oops - please have a {} line as 1st line in file. */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': line %lu should begin with a '{': %s", csp->config->actions_file[fileid], linenum, buf); return 1; /* never get here */ } else { /* How did we get here? This is impossible! */ fclose(fp); log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': INTERNAL ERROR - mode = %d", csp->config->actions_file[fileid], mode); return 1; /* never get here */ } freez(buf); } fclose(fp); if (!cur_action_used) { free_action_spec(cur_action); } free_alias_list(alias_list); /* the old one is now obsolete */ if (current_actions_file[fileid]) { current_actions_file[fileid]->unloader = unload_actions_file; } fs->next = files->next; files->next = fs; current_actions_file[fileid] = fs; csp->actions_list[fileid] = fs; return(0); } /********************************************************************* * * Function : actions_to_text * * Description : Converts a actionsfile entry from the internal * structure into a text line. The output is split * into one line for each action with line continuation. * * Parameters : * 1 : action = The action to format. * * Returns : A string. Caller must free it. * NULL on out-of-memory error. * *********************************************************************/ char * actions_to_text(const struct action_spec *action) { unsigned long mask = action->mask; unsigned long add = action->add; char *result = strdup_or_die(""); struct list_entry * lst; /* sanity - prevents "-feature +feature" */ mask |= add; #define DEFINE_ACTION_BOOL(__name, __bit) \ if (!(mask & __bit)) \ { \ string_append(&result, " -" __name " \\\n"); \ } \ else if (add & __bit) \ { \ string_append(&result, " +" __name " \\\n"); \ } #define DEFINE_ACTION_STRING(__name, __bit, __index) \ if (!(mask & __bit)) \ { \ string_append(&result, " -" __name " \\\n"); \ } \ else if (add & __bit) \ { \ string_append(&result, " +" __name "{"); \ string_append(&result, action->string[__index]); \ string_append(&result, "} \\\n"); \ } #define DEFINE_ACTION_MULTI(__name, __index) \ if (action->multi_remove_all[__index]) \ { \ string_append(&result, " -" __name " \\\n"); \ } \ else \ { \ lst = action->multi_remove[__index]->first; \ while (lst) \ { \ string_append(&result, " -" __name "{"); \ string_append(&result, lst->str); \ string_append(&result, "} \\\n"); \ lst = lst->next; \ } \ } \ lst = action->multi_add[__index]->first; \ while (lst) \ { \ string_append(&result, " +" __name "{"); \ string_append(&result, lst->str); \ string_append(&result, "} \\\n"); \ lst = lst->next; \ } #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */ #include "actionlist.h" #undef DEFINE_ACTION_MULTI #undef DEFINE_ACTION_STRING #undef DEFINE_ACTION_BOOL #undef DEFINE_ACTION_ALIAS return result; } /********************************************************************* * * Function : actions_to_html * * Description : Converts a actionsfile entry from numeric form * ("mask" and "add") to a
-separated HTML string * in which each action is linked to its chapter in * the user manual. * * Parameters : * 1 : csp = Client state (for config) * 2 : action = Action spec to be converted * * Returns : A string. Caller must free it. * NULL on out-of-memory error. * *********************************************************************/ char * actions_to_html(const struct client_state *csp, const struct action_spec *action) { unsigned long mask = action->mask; unsigned long add = action->add; char *result = strdup_or_die(""); struct list_entry * lst; /* sanity - prevents "-feature +feature" */ mask |= add; #define DEFINE_ACTION_BOOL(__name, __bit) \ if (!(mask & __bit)) \ { \ string_append(&result, "\n
-"); \ string_join(&result, add_help_link(__name, csp->config)); \ } \ else if (add & __bit) \ { \ string_append(&result, "\n
+"); \ string_join(&result, add_help_link(__name, csp->config)); \ } #define DEFINE_ACTION_STRING(__name, __bit, __index) \ if (!(mask & __bit)) \ { \ string_append(&result, "\n
-"); \ string_join(&result, add_help_link(__name, csp->config)); \ } \ else if (add & __bit) \ { \ string_append(&result, "\n
+"); \ string_join(&result, add_help_link(__name, csp->config)); \ string_append(&result, "{"); \ string_join(&result, html_encode(action->string[__index])); \ string_append(&result, "}"); \ } #define DEFINE_ACTION_MULTI(__name, __index) \ if (action->multi_remove_all[__index]) \ { \ string_append(&result, "\n
-"); \ string_join(&result, add_help_link(__name, csp->config)); \ } \ else \ { \ lst = action->multi_remove[__index]->first; \ while (lst) \ { \ string_append(&result, "\n
-"); \ string_join(&result, add_help_link(__name, csp->config)); \ string_append(&result, "{"); \ string_join(&result, html_encode(lst->str)); \ string_append(&result, "}"); \ lst = lst->next; \ } \ } \ lst = action->multi_add[__index]->first; \ while (lst) \ { \ string_append(&result, "\n
+"); \ string_join(&result, add_help_link(__name, csp->config)); \ string_append(&result, "{"); \ string_join(&result, html_encode(lst->str)); \ string_append(&result, "}"); \ lst = lst->next; \ } #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */ #include "actionlist.h" #undef DEFINE_ACTION_MULTI #undef DEFINE_ACTION_STRING #undef DEFINE_ACTION_BOOL #undef DEFINE_ACTION_ALIAS /* trim leading
*/ if (result && *result) { char * s = result; result = strdup(result + 5); free(s); } return result; } /********************************************************************* * * Function : current_actions_to_html * * Description : Converts a curren action spec to a
separated HTML * text in which each action is linked to its chapter in * the user manual. * * Parameters : * 1 : csp = Client state (for config) * 2 : action = Current action spec to be converted * * Returns : A string. Caller must free it. * NULL on out-of-memory error. * *********************************************************************/ char *current_action_to_html(const struct client_state *csp, const struct current_action_spec *action) { unsigned long flags = action->flags; struct list_entry * lst; char *result = strdup_or_die(""); char *active = strdup_or_die(""); char *inactive = strdup_or_die(""); #define DEFINE_ACTION_BOOL(__name, __bit) \ if (flags & __bit) \ { \ string_append(&active, "\n
+"); \ string_join(&active, add_help_link(__name, csp->config)); \ } \ else \ { \ string_append(&inactive, "\n
-"); \ string_join(&inactive, add_help_link(__name, csp->config)); \ } #define DEFINE_ACTION_STRING(__name, __bit, __index) \ if (flags & __bit) \ { \ string_append(&active, "\n
+"); \ string_join(&active, add_help_link(__name, csp->config)); \ string_append(&active, "{"); \ string_join(&active, html_encode(action->string[__index])); \ string_append(&active, "}"); \ } \ else \ { \ string_append(&inactive, "\n
-"); \ string_join(&inactive, add_help_link(__name, csp->config)); \ } #define DEFINE_ACTION_MULTI(__name, __index) \ lst = action->multi[__index]->first; \ if (lst == NULL) \ { \ string_append(&inactive, "\n
-"); \ string_join(&inactive, add_help_link(__name, csp->config)); \ } \ else \ { \ while (lst) \ { \ string_append(&active, "\n
+"); \ string_join(&active, add_help_link(__name, csp->config)); \ string_append(&active, "{"); \ string_join(&active, html_encode(lst->str)); \ string_append(&active, "}"); \ lst = lst->next; \ } \ } #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */ #include "actionlist.h" #undef DEFINE_ACTION_MULTI #undef DEFINE_ACTION_STRING #undef DEFINE_ACTION_BOOL #undef DEFINE_ACTION_ALIAS if (active != NULL) { string_append(&result, active); freez(active); } string_append(&result, "\n
"); if (inactive != NULL) { string_append(&result, inactive); freez(inactive); } return result; } /********************************************************************* * * Function : action_to_line_of_text * * Description : Converts a action spec to a single text line * listing the enabled actions. * * Parameters : * 1 : action = Current action spec to be converted * * Returns : A string. Caller must free it. * Out-of-memory errors are fatal. * *********************************************************************/ char *actions_to_line_of_text(const struct current_action_spec *action) { char buffer[200]; struct list_entry *lst; char *active; const unsigned long flags = action->flags; active = strdup_or_die(""); #define DEFINE_ACTION_BOOL(__name, __bit) \ if (flags & __bit) \ { \ snprintf(buffer, sizeof(buffer), "+%s ", __name); \ string_append(&active, buffer); \ } \ #define DEFINE_ACTION_STRING(__name, __bit, __index) \ if (flags & __bit) \ { \ snprintf(buffer, sizeof(buffer), "+%s{%s} ", \ __name, action->string[__index]); \ string_append(&active, buffer); \ } \ #define DEFINE_ACTION_MULTI(__name, __index) \ lst = action->multi[__index]->first; \ while (lst != NULL) \ { \ snprintf(buffer, sizeof(buffer), "+%s{%s} ", \ __name, lst->str); \ string_append(&active, buffer); \ lst = lst->next; \ } \ #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */ #include "actionlist.h" #undef DEFINE_ACTION_MULTI #undef DEFINE_ACTION_STRING #undef DEFINE_ACTION_BOOL #undef DEFINE_ACTION_ALIAS if (active == NULL) { log_error(LOG_LEVEL_FATAL, "Out of memory in action_to_line_of_text()"); } return active; } privoxy-3.0.28-stable/./actions.h000640 001751 000000 00000007406 13412210415 016270 0ustar00fkwheel000000 000000 #ifndef ACTIONS_H_INCLUDED #define ACTIONS_H_INCLUDED /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/actions.h,v $ * * Purpose : Declares functions to work with actions files * Functions declared include: FIXME * * Copyright : Written by and Copyright (C) 2001-2007 members of the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and * Junkbusters Corporation. http://www.junkbusters.com * * 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. * * The GNU General Public License should be included with * this file. If not, you can view it at * http://www.gnu.org/copyleft/gpl.html * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *********************************************************************/ struct action_spec; struct current_action_spec; struct client_state; /* This structure is used to hold user-defined aliases */ struct action_alias { const char * name; struct action_spec action[1]; struct action_alias * next; }; extern jb_err get_actions (char *line, struct action_alias * alias_list, struct action_spec *cur_action); extern void free_alias_list(struct action_alias *alias_list); extern void init_action(struct action_spec *dest); extern void free_action(struct action_spec *src); extern jb_err merge_actions (struct action_spec *dest, const struct action_spec *src); extern int update_action_bits_for_tag(struct client_state *csp, const char *tag); extern jb_err check_negative_tag_patterns(struct client_state *csp, unsigned int flag); extern jb_err copy_action (struct action_spec *dest, const struct action_spec *src); extern char * actions_to_text (const struct action_spec *action); extern char * actions_to_html (const struct client_state *csp, const struct action_spec *action); extern void init_current_action (struct current_action_spec *dest); extern void free_current_action (struct current_action_spec *src); extern jb_err merge_current_action (struct current_action_spec *dest, const struct action_spec *src); extern char * current_action_to_html(const struct client_state *csp, const struct current_action_spec *action); extern char * actions_to_line_of_text(const struct current_action_spec *action); extern jb_err get_action_token(char **line, char **name, char **value); extern void unload_actions_file(void *file_data); extern int load_action_files(struct client_state *csp); #ifdef FUZZ extern int load_one_actions_file(struct client_state *csp, int fileid); #endif #ifdef FEATURE_GRACEFUL_TERMINATION void unload_current_actions_file(void); #endif #endif /* ndef ACTIONS_H_INCLUDED */ /* Local Variables: tab-width: 3 end: */ privoxy-3.0.28-stable/./AUTHORS000640 001751 000000 00000006173 13412210415 015527 0ustar00fkwheel000000 000000 Authors of Privoxy v2.9.x and 3.x =========================================================================== Current Privoxy Team: Fabian Keil, lead developer David Schmidt Lee Rian Roland Rosenfeld Ian Silvester Former Privoxy Team Members: Johny Agotnes Rodrigo Barbosa Moritz Barsnick Hal Burgiss Ian Cummings Brian Dessent Jon Foster Karsten Hopp Alexander Lazic Daniel Leite Gábor Lipták Adam Lock Guy Laroche Justin McMurtry Mark Miller Gerry Murphy Andreas Oesterhelt Haroon Rafique Georg Sauthoff Thomas Steudten Jörg Strohmayer Rodney Stromlund Sviatoslav Sviridov Sarantis Paskalis Stefan Waldherr Thanks to the many people who have tested Privoxy, reported bugs, provided patches, made suggestions, donated or contributed in some other way. These include (in alphabetical order): Rustam Abdullaev Clint Adams Anatoly Arzhnikov Ken Arromdee Natxo Asenjo Devin Bayer Havard Berland David Binderman David Bo Gergely Bor Francois Botha Reiner Buehl Andrew J. Caines Clifford Caoile Edward Carrel Pak Chan Wan-Teh Chang Sam Chen Ramkumar Chinchani Billy Crook Frédéric Crozat Matthew Daley Michael T. Davis Markus Dittrich Mattes Dolak Matthias Drochner Peter E. Florian Effenberger Markus Elfring Ryan Farmer Matthew Fischer Dean Gaudet Stephen Gildea John McGowan Danny Goossen Lizik Grelier Daniel Griscom Felix Gröbert Bernard Guillot Jeff H. Tim H. Aaron Hamid Darel Henman Magnus Holmgren Eric M. Hopper Ralf Horstmann Stefan Huehner Basil Hussain Peter Hyman Derek Jennings Andrew Jones Julien Joubert Ralf Jungblut Petr Kadlec Robert Klemme Steven Kolins Korda Stefan Kurtz Zeno Kugy David Laight Bert van Leeuwen Don Libes Paul Lieverse Han Liu Toby Lyward Wil Mahan Jindrich Makovicka Raphael Marichez Francois Marier Angelina Matson Jonathan McKenzie David Mediavilla Raphael Moll J. Momberger Mathew Murphy Amuro Namie Mark Nelson Tobias Netzel Adam Piggott Petr Písar Dan Price Roberto Ragusa Félix Rauch Kai Raven Marvin Renich Chris John Riley Maynard Riley Andreas Rutkauskas Bart Schelstraete Gregory Seidman Atman Sense Chung-chieh Shan Johan Sintorn Benjamin C. Wiley Sittler Simon South Dan Stahlke Oliver Stoeneberg Rick Sykes Spinor S. Peter Thoenen Marc Thomas Martin Thomas Reuben Thomas Guybrush Threepwood Joel Verhagen Bobby G. Vinyard Jochen Voss David Wagner Glenn Washburn Song Weijia Jörg Weinmann Darren Wiebe Anduin Withers Eduard Wulff Yang Xia Jarry Xu Oliver Yeoh Yossi Zahn Jamie Zawinski Privoxy is based in part on code originally developed by Junkbusters Corp. and Anonymous Coders. Privoxy heavily relies on Philip Hazel's PCRE. The code to filter compressed content makes use of zlib which is written by Jean-loup Gailly and Mark Adler. On systems that lack snprintf(), Privoxy is using a version written by Mark Martinec. On systems that lack strptime(), Privoxy is using the one from the GNU C Library written by Ulrich Drepper. If we've missed you off this list, please let us know! Privoxy team. https://www.privoxy.org/ privoxy-3.0.28-stable/./cgi.c000640 001751 000000 00000223657 13412210415 015375 0ustar00fkwheel000000 000000 /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgi.c,v $ * * Purpose : Declares functions to intercept request, generate * html or gif answers, and to compose HTTP resonses. * This only contains the framework functions, the * actual handler functions are declared elsewhere. * * Copyright : Written by and Copyright (C) 2001-2017 * members of the Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and * Junkbusters Corporation. http://www.junkbusters.com * * 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. * * The GNU General Public License should be included with * this file. If not, you can view it at * http://www.gnu.org/copyleft/gpl.html * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * **********************************************************************/ #include "config.h" #include #include #include #include #include #include #include #ifdef FEATURE_COMPRESSION #include #endif #include "project.h" #include "cgi.h" #include "list.h" #include "encode.h" #include "ssplit.h" #include "errlog.h" #include "filters.h" #include "miscutil.h" #include "cgisimple.h" #include "jbsockets.h" #if defined(FEATURE_CGI_EDIT_ACTIONS) || defined(FEATURE_TOGGLE) #include "cgiedit.h" #endif /* defined(FEATURE_CGI_EDIT_ACTIONS) || defined (FEATURE_TOGGLE) */ /* loadcfg.h is for global_toggle_state only */ #include "loadcfg.h" /* jcc.h is for mutex semaphore globals only */ #include "jcc.h" /* * List of CGI functions: name, handler, description * Note: Do NOT use single quotes in the description; * this will break the dynamic "blocked" template! */ static const struct cgi_dispatcher cgi_dispatchers[] = { { "", cgi_default, "Privoxy main page", TRUE }, #ifdef FEATURE_GRACEFUL_TERMINATION { "die", cgi_die, "Shut down - Do not deploy this build in a production environment, " "this is a one click Denial Of Service attack!!!", FALSE }, #endif { "show-status", cgi_show_status, #ifdef FEATURE_CGI_EDIT_ACTIONS "View & change the current configuration", #else "View the current configuration", #endif TRUE }, #ifdef FEATURE_CLIENT_TAGS /* * This is marked as harmless because despite the description * used in the menu the actual toggling is done through another * path ("/toggle-client-tag"). */ { "client-tags", cgi_show_client_tags, "View or toggle the tags that can be set based on the clients address", TRUE }, #endif { "show-request", cgi_show_request, "View the request headers", TRUE }, { "show-url-info", cgi_show_url_info, "Look up which actions apply to a URL and why", TRUE }, #ifdef FEATURE_TOGGLE { "toggle", cgi_toggle, "Toggle Privoxy on or off", FALSE }, #endif /* def FEATURE_TOGGLE */ #ifdef FEATURE_CLIENT_TAGS { "toggle-client-tag", cgi_toggle_client_tag, NULL, FALSE }, #endif #ifdef FEATURE_CGI_EDIT_ACTIONS { "edit-actions", /* Edit the actions list */ cgi_edit_actions, NULL, FALSE }, { "eaa", /* Shortcut for edit-actions-add-url-form */ cgi_edit_actions_add_url_form, NULL, FALSE }, { "eau", /* Shortcut for edit-actions-url-form */ cgi_edit_actions_url_form, NULL, FALSE }, { "ear", /* Shortcut for edit-actions-remove-url-form */ cgi_edit_actions_remove_url_form, NULL, FALSE }, { "eal", /* Shortcut for edit-actions-list */ cgi_edit_actions_list, NULL, FALSE }, { "eafu", /* Shortcut for edit-actions-for-url */ cgi_edit_actions_for_url, NULL, FALSE }, { "eas", /* Shortcut for edit-actions-submit */ cgi_edit_actions_submit, NULL, FALSE }, { "easa", /* Shortcut for edit-actions-section-add */ cgi_edit_actions_section_add, NULL, FALSE }, { "easr", /* Shortcut for edit-actions-section-remove */ cgi_edit_actions_section_remove, NULL, FALSE }, { "eass", /* Shortcut for edit-actions-section-swap */ cgi_edit_actions_section_swap, NULL, FALSE }, { "edit-actions-for-url", cgi_edit_actions_for_url, NULL, FALSE /* Edit the actions for (a) specified URL(s) */ }, { "edit-actions-list", cgi_edit_actions_list, NULL, TRUE /* Edit the actions list */ }, { "edit-actions-submit", cgi_edit_actions_submit, NULL, FALSE /* Change the actions for (a) specified URL(s) */ }, { "edit-actions-url", cgi_edit_actions_url, NULL, FALSE /* Change a URL pattern in the actionsfile */ }, { "edit-actions-url-form", cgi_edit_actions_url_form, NULL, FALSE /* Form to change a URL pattern in the actionsfile */ }, { "edit-actions-add-url", cgi_edit_actions_add_url, NULL, FALSE /* Add a URL pattern to the actionsfile */ }, { "edit-actions-add-url-form", cgi_edit_actions_add_url_form, NULL, FALSE /* Form to add a URL pattern to the actionsfile */ }, { "edit-actions-remove-url", cgi_edit_actions_remove_url, NULL, FALSE /* Remove a URL pattern from the actionsfile */ }, { "edit-actions-remove-url-form", cgi_edit_actions_remove_url_form, NULL, FALSE /* Form to remove a URL pattern from the actionsfile */ }, { "edit-actions-section-add", cgi_edit_actions_section_add, NULL, FALSE /* Remove a section from the actionsfile */ }, { "edit-actions-section-remove", cgi_edit_actions_section_remove, NULL, FALSE /* Remove a section from the actionsfile */ }, { "edit-actions-section-swap", cgi_edit_actions_section_swap, NULL, FALSE /* Swap two sections in the actionsfile */ }, #endif /* def FEATURE_CGI_EDIT_ACTIONS */ { "error-favicon.ico", cgi_send_error_favicon, NULL, TRUE /* Sends the favicon image for error pages. */ }, { "favicon.ico", cgi_send_default_favicon, NULL, TRUE /* Sends the default favicon image. */ }, { "robots.txt", cgi_robots_txt, NULL, TRUE /* Sends a robots.txt file to tell robots to go away. */ }, { "send-banner", cgi_send_banner, NULL, TRUE /* Send a built-in image */ }, { "send-stylesheet", cgi_send_stylesheet, NULL, FALSE /* Send templates/cgi-style.css */ }, { "t", cgi_transparent_image, NULL, TRUE /* Send a transparent image (short name) */ }, { "url-info-osd.xml", cgi_send_url_info_osd, NULL, TRUE /* Send templates/url-info-osd.xml */ }, { "user-manual", cgi_send_user_manual, NULL, TRUE /* Send user-manual */ }, { NULL, /* NULL Indicates end of list and default page */ cgi_error_404, NULL, TRUE /* Unknown CGI page */ } }; /* * Built-in images for ad replacement * * Hint: You can encode your own images like this: * cat your-image | perl -e 'while (read STDIN, $c, 1) { printf("\\%.3o", unpack("C", $c)); }' */ #ifdef FEATURE_NO_GIFS /* * Checkerboard pattern, as a PNG. */ const char image_pattern_data[] = "\211\120\116\107\015\012\032\012\000\000\000\015\111\110\104" "\122\000\000\000\004\000\000\000\004\010\006\000\000\000\251" "\361\236\176\000\000\000\006\142\113\107\104\000\000\000\000" "\000\000\371\103\273\177\000\000\000\033\111\104\101\124\010" "\327\143\140\140\140\060\377\377\377\077\003\234\106\341\060" "\060\230\063\020\124\001\000\161\021\031\241\034\364\030\143" "\000\000\000\000\111\105\116\104\256\102\140\202"; /* * 1x1 transparant PNG. */ const char image_blank_data[] = "\211\120\116\107\015\012\032\012\000\000\000\015\111\110\104\122" "\000\000\000\001\000\000\000\001\001\003\000\000\000\045\333\126" "\312\000\000\000\003\120\114\124\105\377\377\377\247\304\033\310" "\000\000\000\001\164\122\116\123\000\100\346\330\146\000\000\000" "\001\142\113\107\104\000\210\005\035\110\000\000\000\012\111\104" "\101\124\170\001\143\140\000\000\000\002\000\001\163\165\001\030" "\000\000\000\000\111\105\116\104\256\102\140\202"; #else /* * Checkerboard pattern, as a GIF. */ const char image_pattern_data[] = "\107\111\106\070\071\141\004\000\004\000\200\000\000\310\310" "\310\377\377\377\041\376\016\111\040\167\141\163\040\141\040" "\142\141\156\156\145\162\000\041\371\004\001\012\000\001\000" "\054\000\000\000\000\004\000\004\000\000\002\005\104\174\147" "\270\005\000\073"; /* * 1x1 transparant GIF. */ const char image_blank_data[] = "GIF89a\001\000\001\000\200\000\000\377\377\377\000\000" "\000!\371\004\001\000\000\000\000,\000\000\000\000\001" "\000\001\000\000\002\002D\001\000;"; #endif const size_t image_pattern_length = sizeof(image_pattern_data) - 1; const size_t image_blank_length = sizeof(image_blank_data) - 1; #ifdef FEATURE_COMPRESSION /* * Minimum length which a buffer has to reach before * we bother to (re-)compress it. Completely arbitrary. */ const size_t LOWER_LENGTH_LIMIT_FOR_COMPRESSION = 1024U; #endif static struct http_response cgi_error_memory_response[1]; static struct http_response *dispatch_known_cgi(struct client_state * csp, const char * path); static struct map *parse_cgi_parameters(char *argstring); /********************************************************************* * * Function : dispatch_cgi * * Description : Checks if a request URL has either the magical * hostname CGI_SITE_1_HOST (usually http://p.p/) or * matches CGI_SITE_2_HOST CGI_SITE_2_PATH (usually * http://config.privoxy.org/). If so, it passes * the (rest of the) path onto dispatch_known_cgi, which * calls the relevant CGI handler function. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * * Returns : http_response if match, NULL if nonmatch or handler fail * *********************************************************************/ struct http_response *dispatch_cgi(struct client_state *csp) { const char *host = csp->http->host; const char *path = csp->http->path; /* * Should we intercept ? */ /* Note: "example.com" and "example.com." are equivalent hostnames. */ /* Either the host matches CGI_SITE_1_HOST ..*/ if ( ( (0 == strcmpic(host, CGI_SITE_1_HOST)) || (0 == strcmpic(host, CGI_SITE_1_HOST "."))) && (path[0] == '/')) { /* ..then the path will all be for us. Remove leading '/' */ path++; } /* Or it's the host part CGI_SITE_2_HOST, and the path CGI_SITE_2_PATH */ else if (( (0 == strcmpic(host, CGI_SITE_2_HOST)) || (0 == strcmpic(host, CGI_SITE_2_HOST "."))) && (0 == strncmpic(path, CGI_SITE_2_PATH, strlen(CGI_SITE_2_PATH)))) { /* take everything following CGI_SITE_2_PATH */ path += strlen(CGI_SITE_2_PATH); if (*path == '/') { /* skip the forward slash after CGI_SITE_2_PATH */ path++; } else if (*path != '\0') { /* * weirdness: URL is /configXXX, where XXX is some string * Do *NOT* intercept. */ return NULL; } } else { /* Not a CGI */ return NULL; } if (strcmpic(csp->http->gpc, "GET") && strcmpic(csp->http->gpc, "HEAD")) { log_error(LOG_LEVEL_ERROR, "CGI request with unsupported method received: %s", csp->http->gpc); /* * The CGI pages currently only support GET and HEAD requests. * * If the client used a different method, ditch any data following * the current headers to reduce the likelihood of parse errors * with the following request. */ csp->client_iob->eod = csp->client_iob->cur; } /* * This is a CGI call. */ return dispatch_known_cgi(csp, path); } /********************************************************************* * * Function : grep_cgi_referrer * * Description : Ugly provisorical fix that greps the value of the * referer HTTP header field out of a linked list of * strings like found at csp->headers. Will disappear * in Privoxy 3.1. * * FIXME: csp->headers ought to be csp->http->headers * FIXME: Parsing all client header lines should * happen right after the request is received! * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * * Returns : pointer to value (no copy!), or NULL if none found. * *********************************************************************/ static char *grep_cgi_referrer(const struct client_state *csp) { struct list_entry *p; for (p = csp->headers->first; p != NULL; p = p->next) { if (p->str == NULL) continue; if (strncmpic(p->str, "Referer: ", 9) == 0) { return ((p->str) + 9); } } return NULL; } /********************************************************************* * * Function : referrer_is_safe * * Description : Decides whether we trust the Referer for * CGI pages which are only meant to be reachable * through Privoxy's web interface directly. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * * Returns : TRUE if the referrer is safe, or * FALSE if the referrer is unsafe or not set. * *********************************************************************/ static int referrer_is_safe(const struct client_state *csp) { char *referrer; static const char alternative_prefix[] = "http://" CGI_SITE_1_HOST "/"; const char *trusted_cgi_referrer = csp->config->trusted_cgi_referrer; referrer = grep_cgi_referrer(csp); if (NULL == referrer) { /* No referrer, no access */ log_error(LOG_LEVEL_ERROR, "Denying access to %s. No referrer found.", csp->http->url); } else if ((0 == strncmp(referrer, CGI_PREFIX, sizeof(CGI_PREFIX)-1) || (0 == strncmp(referrer, alternative_prefix, strlen(alternative_prefix))))) { /* Trustworthy referrer */ log_error(LOG_LEVEL_CGI, "Granting access to %s, referrer %s is trustworthy.", csp->http->url, referrer); return TRUE; } else if ((trusted_cgi_referrer != NULL) && (0 == strncmp(referrer, trusted_cgi_referrer, strlen(trusted_cgi_referrer)))) { /* * After some more testing this block should be merged with * the previous one or the log level should bedowngraded. */ log_error(LOG_LEVEL_INFO, "Granting access to %s based on trusted referrer %s", csp->http->url, referrer); return TRUE; } else { /* Untrustworthy referrer */ log_error(LOG_LEVEL_ERROR, "Denying access to %s, referrer %s isn't trustworthy.", csp->http->url, referrer); } return FALSE; } /********************************************************************* * * Function : dispatch_known_cgi * * Description : Processes a CGI once dispatch_cgi has determined that * it matches one of the magic prefixes. Parses the path * as a cgi name plus query string, prepares a map that * maps CGI parameter names to their values, initializes * the http_response struct, and calls the relevant CGI * handler function. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : path = Path of CGI, with the CGI prefix removed. * Should not have a leading "/". * * Returns : http_response, or NULL on handler failure or out of * memory. * *********************************************************************/ static struct http_response *dispatch_known_cgi(struct client_state * csp, const char * path) { const struct cgi_dispatcher *d; struct map *param_list; struct http_response *rsp; char *query_args_start; char *path_copy; jb_err err; if (NULL == (path_copy = strdup(path))) { return cgi_error_memory(); } query_args_start = path_copy; while (*query_args_start && *query_args_start != '?' && *query_args_start != '/') { query_args_start++; } if (*query_args_start == '/') { *query_args_start++ = '\0'; param_list = new_map(); err = map(param_list, "file", 1, url_decode(query_args_start), 0); if (JB_ERR_OK != err) { free(param_list); free(path_copy); return cgi_error_memory(); } } else { if (*query_args_start == '?') { *query_args_start++ = '\0'; } if (NULL == (param_list = parse_cgi_parameters(query_args_start))) { free(path_copy); return cgi_error_memory(); } } /* * At this point: * path_copy = CGI call name * param_list = CGI params, as map */ /* Get mem for response or fail*/ if (NULL == (rsp = alloc_http_response())) { free(path_copy); free_map(param_list); return cgi_error_memory(); } /* * Find and start the right CGI function */ d = cgi_dispatchers; for (;;) { if ((d->name == NULL) || (strcmp(path_copy, d->name) == 0)) { /* * If the called CGI is either harmless, or referred * from a trusted source, start it. */ if (d->harmless || referrer_is_safe(csp)) { err = (d->handler)(csp, rsp, param_list); } else { /* * Else, modify toggle calls so that they only display * the status, and deny all other calls. */ if (0 == strcmp(path_copy, "toggle")) { unmap(param_list, "set"); err = (d->handler)(csp, rsp, param_list); } else { err = cgi_error_disabled(csp, rsp); } } free(path_copy); free_map(param_list); if (err == JB_ERR_CGI_PARAMS) { err = cgi_error_bad_param(csp, rsp); } if (err && (err != JB_ERR_MEMORY)) { /* Unexpected error! Shouldn't get here */ log_error(LOG_LEVEL_ERROR, "Unexpected CGI error %d in top-level handler. " "Please file a bug report!", err); err = cgi_error_unknown(csp, rsp, err); } if (!err) { /* It worked */ rsp->crunch_reason = CGI_CALL; return finish_http_response(csp, rsp); } else { /* Error in handler, probably out-of-memory */ free_http_response(rsp); return cgi_error_memory(); } } d++; } } /********************************************************************* * * Function : parse_cgi_parameters * * Description : Parse a URL-encoded argument string into name/value * pairs and store them in a struct map list. * * Parameters : * 1 : argstring = string to be parsed. Will be trashed. * * Returns : pointer to param list, or NULL if out of memory. * *********************************************************************/ static struct map *parse_cgi_parameters(char *argstring) { char *p; char **vector; int pairs, i; struct map *cgi_params; /* * XXX: This estimate is guaranteed to be high enough as we * let ssplit() ignore empty fields, but also a bit wasteful. * The same hack is used in get_last_url() so it looks like * a real solution is needed. */ size_t max_segments = strlen(argstring) / 2; if (max_segments == 0) { /* * XXX: If the argstring is empty, there's really * no point in creating a param list, but currently * other parts of Privoxy depend on the list's existence. */ max_segments = 1; } vector = malloc_or_die(max_segments * sizeof(char *)); cgi_params = new_map(); /* * IE 5 does, of course, violate RFC 2316 Sect 4.1 and sends * the fragment identifier along with the request, so we must * cut it off here, so it won't pollute the CGI params: */ if (NULL != (p = strchr(argstring, '#'))) { *p = '\0'; } pairs = ssplit(argstring, "&", vector, max_segments); assert(pairs != -1); if (pairs == -1) { freez(vector); free_map(cgi_params); return NULL; } for (i = 0; i < pairs; i++) { if ((NULL != (p = strchr(vector[i], '='))) && (*(p+1) != '\0')) { *p = '\0'; if (map(cgi_params, url_decode(vector[i]), 0, url_decode(++p), 0)) { freez(vector); free_map(cgi_params); return NULL; } } } freez(vector); return cgi_params; } /********************************************************************* * * Function : get_char_param * * Description : Get a single-character parameter passed to a CGI * function. * * Parameters : * 1 : parameters = map of cgi parameters * 2 : param_name = The name of the parameter to read * * Returns : Uppercase character on success, '\0' on error. * *********************************************************************/ char get_char_param(const struct map *parameters, const char *param_name) { char ch; assert(parameters); assert(param_name); ch = *(lookup(parameters, param_name)); if ((ch >= 'a') && (ch <= 'z')) { ch = (char)(ch - 'a' + 'A'); } return ch; } /********************************************************************* * * Function : get_string_param * * Description : Get a string paramater, to be used as an * ACTION_STRING or ACTION_MULTI paramater. * Validates the input to prevent stupid/malicious * users from corrupting their action file. * * Parameters : * 1 : parameters = map of cgi parameters * 2 : param_name = The name of the parameter to read * 3 : pparam = destination for paramater. Allocated as * part of the map "parameters", so don't free it. * Set to NULL if not specified. * * Returns : JB_ERR_OK on success, or if the paramater * was not specified. * JB_ERR_MEMORY on out-of-memory. * JB_ERR_CGI_PARAMS if the paramater is not valid. * *********************************************************************/ jb_err get_string_param(const struct map *parameters, const char *param_name, const char **pparam) { const char *param; const char *s; char ch; assert(parameters); assert(param_name); assert(pparam); *pparam = NULL; param = lookup(parameters, param_name); if (!*param) { return JB_ERR_OK; } if (strlen(param) >= CGI_PARAM_LEN_MAX) { /* * Too long. * * Note that the length limit is arbitrary, it just seems * sensible to limit it to *something*. There's no * technical reason for any limit at all. */ return JB_ERR_CGI_PARAMS; } /* Check every character to see if it's legal */ s = param; while ((ch = *s++) != '\0') { if (((unsigned char)ch < (unsigned char)' ') || (ch == '}')) { /* Probable hack attempt, or user accidentally used '}'. */ return JB_ERR_CGI_PARAMS; } } /* Success */ *pparam = param; return JB_ERR_OK; } /********************************************************************* * * Function : get_number_param * * Description : Get a non-negative integer from the parameters * passed to a CGI function. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : parameters = map of cgi parameters * 3 : name = Name of CGI parameter to read * 4 : pvalue = destination for value. * Set to -1 on error. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if the parameter was not specified * or is not valid. * *********************************************************************/ jb_err get_number_param(struct client_state *csp, const struct map *parameters, char *name, unsigned *pvalue) { const char *param; char *endptr; assert(csp); assert(parameters); assert(name); assert(pvalue); *pvalue = 0; param = lookup(parameters, name); if (!*param) { return JB_ERR_CGI_PARAMS; } *pvalue = (unsigned int)strtol(param, &endptr, 0); if (*endptr != '\0') { return JB_ERR_CGI_PARAMS; } return JB_ERR_OK; } /********************************************************************* * * Function : error_response * * Description : returns an http_response that explains the reason * why a request failed. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : templatename = Which template should be used for the answer * * Returns : A http_response. If we run out of memory, this * will be cgi_error_memory(). * *********************************************************************/ struct http_response *error_response(struct client_state *csp, const char *templatename) { jb_err err; struct http_response *rsp; struct map *exports = default_exports(csp, NULL); char *path = NULL; if (exports == NULL) { return cgi_error_memory(); } if (NULL == (rsp = alloc_http_response())) { free_map(exports); return cgi_error_memory(); } #ifdef FEATURE_FORCE_LOAD if (csp->flags & CSP_FLAG_FORCED) { path = strdup(FORCE_PREFIX); } else #endif /* def FEATURE_FORCE_LOAD */ { path = strdup(""); } err = string_append(&path, csp->http->path); if (!err) err = map(exports, "host", 1, html_encode(csp->http->host), 0); if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0); if (!err) err = map(exports, "path", 1, html_encode_and_free_original(path), 0); if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); if (!err) { err = map(exports, "host-ip", 1, html_encode(csp->http->host_ip_addr_str), 0); if (err) { /* Some failures, like "404 no such domain", don't have an IP address. */ err = map(exports, "host-ip", 1, html_encode(csp->http->host), 0); } } if (err) { free_map(exports); free_http_response(rsp); return cgi_error_memory(); } if (!strcmp(templatename, "no-such-domain")) { rsp->status = strdup("404 No such domain"); rsp->crunch_reason = NO_SUCH_DOMAIN; } else if (!strcmp(templatename, "forwarding-failed")) { const struct forward_spec *fwd = forward_url(csp, csp->http); char *socks_type = NULL; if (fwd == NULL) { log_error(LOG_LEVEL_FATAL, "gateway spec is NULL. This shouldn't happen!"); /* Never get here - LOG_LEVEL_FATAL causes program exit */ } /* * XXX: While the template is called forwarding-failed, * it currently only handles socks forwarding failures. */ assert(fwd != NULL); assert(fwd->type != SOCKS_NONE); /* * Map failure reason, forwarding type and forwarder. */ if (NULL == csp->error_message) { /* * Either we forgot to record the failure reason, * or the memory allocation failed. */ log_error(LOG_LEVEL_ERROR, "Socks failure reason missing."); csp->error_message = strdup("Failure reason missing. Check the log file for details."); } if (!err) err = map(exports, "gateway", 1, fwd->gateway_host, 1); /* * XXX: this is almost the same code as in cgi_show_url_info() * and thus should be factored out and shared. */ switch (fwd->type) { case SOCKS_4: socks_type = "socks4-"; break; case SOCKS_4A: socks_type = "socks4a-"; break; case SOCKS_5: socks_type = "socks5-"; break; case SOCKS_5T: socks_type = "socks5t-"; break; default: log_error(LOG_LEVEL_FATAL, "Unknown socks type: %d.", fwd->type); } if (!err) err = map(exports, "forwarding-type", 1, socks_type, 1); if (!err) err = map(exports, "error-message", 1, html_encode(csp->error_message), 0); if ((NULL == csp->error_message) || err) { free_map(exports); free_http_response(rsp); return cgi_error_memory(); } rsp->status = strdup("503 Forwarding failure"); rsp->crunch_reason = FORWARDING_FAILED; } else if (!strcmp(templatename, "connect-failed")) { rsp->status = strdup("503 Connect failed"); rsp->crunch_reason = CONNECT_FAILED; } else if (!strcmp(templatename, "connection-timeout")) { rsp->status = strdup("504 Connection timeout"); rsp->crunch_reason = CONNECTION_TIMEOUT; } else if (!strcmp(templatename, "no-server-data")) { rsp->status = strdup("502 No data received from server or forwarder"); rsp->crunch_reason = NO_SERVER_DATA; } if (rsp->status == NULL) { free_map(exports); free_http_response(rsp); return cgi_error_memory(); } err = template_fill_for_cgi(csp, templatename, exports, rsp); if (err) { free_http_response(rsp); return cgi_error_memory(); } return finish_http_response(csp, rsp); } /********************************************************************* * * Function : cgi_error_disabled * * Description : CGI function that is called to generate an error * response if the actions editor or toggle CGI are * accessed despite having being disabled at compile- * or run-time, or if the user followed an untrusted link * to access a unsafe CGI feature that is only reachable * through Privoxy directly. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = http_response data structure for output * * CGI Parameters : none * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err cgi_error_disabled(const struct client_state *csp, struct http_response *rsp) { struct map *exports; assert(csp); assert(rsp); rsp->status = strdup_or_die("403 Request not trusted or feature disabled"); if (NULL == (exports = default_exports(csp, "cgi-error-disabled"))) { return JB_ERR_MEMORY; } if (map(exports, "url", 1, html_encode(csp->http->url), 0)) { /* Not important enough to do anything */ log_error(LOG_LEVEL_ERROR, "Failed to fill in url."); } return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp); } /********************************************************************* * * Function : cgi_init_error_messages * * Description : Call at the start of the program to initialize * the error message used by cgi_error_memory(). * * Parameters : N/A * * Returns : N/A * *********************************************************************/ void cgi_init_error_messages(void) { memset(cgi_error_memory_response, '\0', sizeof(*cgi_error_memory_response)); cgi_error_memory_response->head = "HTTP/1.0 500 Internal Privoxy Error\r\n" "Content-Type: text/html\r\n" "\r\n"; cgi_error_memory_response->body = "\n" "\n" " 500 Internal Privoxy Error\n" " " "\n" "\n" "

500 Internal Privoxy Error

\n" "

Privoxy ran out of memory while processing your request.

\n" "

Please contact your proxy administrator, or try again later

\n" "\n" "\n"; cgi_error_memory_response->head_length = strlen(cgi_error_memory_response->head); cgi_error_memory_response->content_length = strlen(cgi_error_memory_response->body); cgi_error_memory_response->crunch_reason = OUT_OF_MEMORY; } /********************************************************************* * * Function : cgi_error_memory * * Description : Called if a CGI function runs out of memory. * Returns a statically-allocated error response. * * Parameters : N/A * * Returns : http_response data structure for output. This is * statically allocated, for obvious reasons. * *********************************************************************/ struct http_response *cgi_error_memory(void) { /* assert that it's been initialized. */ assert(cgi_error_memory_response->head); return cgi_error_memory_response; } /********************************************************************* * * Function : cgi_error_no_template * * Description : Almost-CGI function that is called if a template * cannot be loaded. Note this is not a true CGI, * it takes a template name rather than a map of * parameters. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = http_response data structure for output * 3 : template_name = Name of template that could not * be loaded. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err cgi_error_no_template(const struct client_state *csp, struct http_response *rsp, const char *template_name) { static const char status[] = "500 Internal Privoxy Error"; static const char body_prefix[] = "\n" "\n" " 500 Internal Privoxy Error\n" " " "\n" "\n" "

500 Internal Privoxy Error

\n" "

Privoxy encountered an error while processing your request:

\n" "

Could not load template file "; static const char body_suffix[] = " or one of its included components.

\n" "

Please contact your proxy administrator.

\n" "

If you are the proxy administrator, please put the required file(s)" "in the (confdir)/templates directory. The " "location of the (confdir) directory " "is specified in the main Privoxy config " "file. (It's typically the Privoxy install directory" #ifndef _WIN32 ", or /etc/privoxy/" #endif /* ndef _WIN32 */ ").

\n" "\n" "\n"; const size_t body_size = strlen(body_prefix) + strlen(template_name) + strlen(body_suffix) + 1; assert(csp); assert(rsp); assert(template_name); /* Reset rsp, if needed */ freez(rsp->status); freez(rsp->head); freez(rsp->body); rsp->content_length = 0; rsp->head_length = 0; rsp->is_static = 0; rsp->body = malloc_or_die(body_size); strlcpy(rsp->body, body_prefix, body_size); strlcat(rsp->body, template_name, body_size); strlcat(rsp->body, body_suffix, body_size); rsp->status = strdup(status); if (rsp->status == NULL) { return JB_ERR_MEMORY; } return JB_ERR_OK; } /********************************************************************* * * Function : cgi_error_unknown * * Description : Almost-CGI function that is called if an unexpected * error occurs in the top-level CGI dispatcher. * In this context, "unexpected" means "anything other * than JB_ERR_MEMORY or JB_ERR_CGI_PARAMS" - CGIs are * expected to handle all other errors internally, * since they can give more relavent error messages * that way. * * Note this is not a true CGI, it takes an error * code rather than a map of parameters. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = http_response data structure for output * 3 : error_to_report = Error code to report. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err cgi_error_unknown(const struct client_state *csp, struct http_response *rsp, jb_err error_to_report) { static const char status[] = "500 Internal Privoxy Error"; static const char body_prefix[] = "\n" "\n" " 500 Internal Privoxy Error\n" " " "\n" "\n" "

500 Internal Privoxy Error

\n" "

Privoxy encountered an error while processing your request:

\n" "

Unexpected internal error: "; static const char body_suffix[] = "

\n" "

Please " "" "file a bug report.

\n" "\n" "\n"; /* Includes room for larger error numbers in the future. */ const size_t body_size = sizeof(body_prefix) + sizeof(body_suffix) + 5; assert(csp); assert(rsp); /* Reset rsp, if needed */ freez(rsp->status); freez(rsp->head); freez(rsp->body); rsp->content_length = 0; rsp->head_length = 0; rsp->is_static = 0; rsp->crunch_reason = INTERNAL_ERROR; rsp->body = malloc_or_die(body_size); snprintf(rsp->body, body_size, "%s%d%s", body_prefix, error_to_report, body_suffix); rsp->status = strdup(status); if (rsp->status == NULL) { return JB_ERR_MEMORY; } return JB_ERR_OK; } /********************************************************************* * * Function : cgi_error_bad_param * * Description : CGI function that is called if the parameters * (query string) for a CGI were wrong. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = http_response data structure for output * * CGI Parameters : none * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err cgi_error_bad_param(const struct client_state *csp, struct http_response *rsp) { struct map *exports; assert(csp); assert(rsp); if (NULL == (exports = default_exports(csp, NULL))) { return JB_ERR_MEMORY; } return template_fill_for_cgi(csp, "cgi-error-bad-param", exports, rsp); } /********************************************************************* * * Function : cgi_redirect * * Description : CGI support function to generate a HTTP redirect * message * * Parameters : * 1 : rsp = http_response data structure for output * 2 : target = string with the target URL * * CGI Parameters : None * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err cgi_redirect (struct http_response * rsp, const char *target) { jb_err err; assert(rsp); assert(target); err = enlist_unique_header(rsp->headers, "Location", target); rsp->status = strdup("302 Local Redirect from Privoxy"); if (rsp->status == NULL) { return JB_ERR_MEMORY; } return err; } /********************************************************************* * * Function : add_help_link * * Description : Produce a copy of the string given as item, * embedded in an HTML link to its corresponding * section (item name in uppercase) in the actions * chapter of the user manual, (whose URL is given in * the config and defaults to our web site). * * FIXME: I currently only work for actions, and would * like to be generalized for other topics. * * Parameters : * 1 : item = item (will NOT be free()d.) * It is assumed to be HTML-safe. * 2 : config = The current configuration. * * Returns : String with item embedded in link, or NULL on * out-of-memory * *********************************************************************/ char *add_help_link(const char *item, struct configuration_spec *config) { char *result; if (!item) return NULL; result = strdup("usermanual, "file://", 7) || !strncmpic(config->usermanual, "http", 4)) { string_append(&result, config->usermanual); } else { string_append(&result, "http://"); string_append(&result, CGI_SITE_2_HOST); string_append(&result, "/user-manual/"); } string_append(&result, ACTIONS_HELP_PREFIX); string_join (&result, string_toupper(item)); string_append(&result, "\">"); string_append(&result, item); string_append(&result, ""); return result; } /********************************************************************* * * Function : get_http_time * * Description : Get the time in a format suitable for use in a * HTTP header - e.g.: * "Sun, 06 Nov 1994 08:49:37 GMT" * * Parameters : * 1 : time_offset = Time returned will be current time * plus this number of seconds. * 2 : buf = Destination for result. * 3 : buffer_size = Size of the buffer above. Must be big * enough to hold 29 characters plus a * trailing zero. * * Returns : N/A * *********************************************************************/ void get_http_time(int time_offset, char *buf, size_t buffer_size) { struct tm *t; time_t current_time; #if defined(HAVE_GMTIME_R) struct tm dummy; #endif assert(buf); assert(buffer_size > (size_t)29); time(¤t_time); current_time += time_offset; /* get and save the gmt */ #if HAVE_GMTIME_R t = gmtime_r(¤t_time, &dummy); #elif defined(MUTEX_LOCKS_AVAILABLE) privoxy_mutex_lock(&gmtime_mutex); t = gmtime(¤t_time); privoxy_mutex_unlock(&gmtime_mutex); #else t = gmtime(¤t_time); #endif strftime(buf, buffer_size, "%a, %d %b %Y %H:%M:%S GMT", t); } /********************************************************************* * * Function : get_locale_time * * Description : Get the time in a date(1)-like format * according to the current locale - e.g.: * "Fri Aug 29 19:37:12 CEST 2008" * * XXX: Should we allow the user to change the format? * * Parameters : * 1 : buf = Destination for result. * 2 : buffer_size = Size of the buffer above. Must be big * enough to hold 29 characters plus a * trailing zero. * * Returns : N/A * *********************************************************************/ static void get_locale_time(char *buf, size_t buffer_size) { struct tm *timeptr; time_t current_time; #if defined(HAVE_LOCALTIME_R) struct tm dummy; #endif assert(buf); assert(buffer_size > (size_t)29); time(¤t_time); #if HAVE_LOCALTIME_R timeptr = localtime_r(¤t_time, &dummy); #elif defined(MUTEX_LOCKS_AVAILABLE) privoxy_mutex_lock(&localtime_mutex); timeptr = localtime(¤t_time); privoxy_mutex_unlock(&localtime_mutex); #else timeptr = localtime(¤t_time); #endif strftime(buf, buffer_size, "%a %b %d %X %Z %Y", timeptr); } #ifdef FEATURE_COMPRESSION /********************************************************************* * * Function : compress_buffer * * Description : Compresses the content of a buffer with zlib's deflate * Allocates a new buffer for the result, free'ing it is * up to the caller. * * Parameters : * 1 : buffer = buffer whose content should be compressed * 2 : buffer_length = length of the buffer * 3 : compression_level = compression level for compress2() * * Returns : NULL on error, otherwise a pointer to the compressed * content of the input buffer. * *********************************************************************/ char *compress_buffer(char *buffer, size_t *buffer_length, int compression_level) { char *compressed_buffer; uLongf new_length; assert(-1 <= compression_level && compression_level <= 9); /* Let zlib figure out the maximum length of the compressed data */ new_length = compressBound((uLongf)*buffer_length); compressed_buffer = malloc_or_die(new_length); if (Z_OK != compress2((Bytef *)compressed_buffer, &new_length, (Bytef *)buffer, *buffer_length, compression_level)) { log_error(LOG_LEVEL_ERROR, "compress2() failed. Buffer size: %d, compression level: %d.", new_length, compression_level); freez(compressed_buffer); return NULL; } log_error(LOG_LEVEL_RE_FILTER, "Compressed content from %d to %d bytes. Compression level: %d", *buffer_length, new_length, compression_level); *buffer_length = (size_t)new_length; return compressed_buffer; } #endif /********************************************************************* * * Function : finish_http_response * * Description : Fill in the missing headers in an http response, * and flatten the headers to an http head. * For HEAD requests the body is freed once * the Content-Length header is set. * * Parameters : * 1 : rsp = pointer to http_response to be processed * * Returns : A http_response, usually the rsp parameter. * On error, free()s rsp and returns cgi_error_memory() * *********************************************************************/ struct http_response *finish_http_response(struct client_state *csp, struct http_response *rsp) { char buf[BUFFER_SIZE]; jb_err err; /* Special case - do NOT change this statically allocated response, * which is ready for output anyway. */ if (rsp == cgi_error_memory_response) { return rsp; } /* * Fill in the HTTP Status, using HTTP/1.1 * unless the client asked for HTTP/1.0. */ snprintf(buf, sizeof(buf), "%s %s", strcmpic(csp->http->ver, "HTTP/1.0") ? "HTTP/1.1" : "HTTP/1.0", rsp->status ? rsp->status : "200 OK"); err = enlist_first(rsp->headers, buf); /* * Set the Content-Length */ if (rsp->content_length == 0) { rsp->content_length = rsp->body ? strlen(rsp->body) : 0; } #ifdef FEATURE_COMPRESSION if (!err && (csp->flags & CSP_FLAG_CLIENT_SUPPORTS_DEFLATE) && (rsp->content_length > LOWER_LENGTH_LIMIT_FOR_COMPRESSION)) { char *compressed_content; compressed_content = compress_buffer(rsp->body, &rsp->content_length, csp->config->compression_level); if (NULL != compressed_content) { freez(rsp->body); rsp->body = compressed_content; err = enlist_unique_header(rsp->headers, "Content-Encoding", "deflate"); } } #endif if (!err) { snprintf(buf, sizeof(buf), "Content-Length: %d", (int)rsp->content_length); /* * Signal serve() that the client will be able to figure out * the end of the response without having to close the connection. */ csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET; err = enlist(rsp->headers, buf); } if (0 == strcmpic(csp->http->gpc, "head")) { /* * The client only asked for the head. Dispose * the body and log an offensive message. * * While it may seem to be a bit inefficient to * prepare the body if it isn't needed, it's the * only way to get the Content-Length right for * dynamic pages. We could have disposed the body * earlier, but not without duplicating the * Content-Length setting code above. */ log_error(LOG_LEVEL_CGI, "Preparing to give head to %s.", csp->ip_addr_str); freez(rsp->body); rsp->content_length = 0; } if (strncmpic(rsp->status, "302", 3)) { /* * If it's not a redirect without any content, * set the Content-Type to text/html if it's * not already specified. */ if (!err) err = enlist_unique(rsp->headers, "Content-Type: text/html", 13); } /* * Fill in the rest of the default headers: * * Date: set to current date/time. * Last-Modified: set to date/time the page was last changed. * Expires: set to date/time page next needs reloading. * Cache-Control: set to "no-cache" if applicable. * * See http://www.w3.org/Protocols/rfc2068/rfc2068 */ if (rsp->is_static) { /* * Set Expires to about 10 min into the future so it'll get reloaded * occasionally, e.g. if Privoxy gets upgraded. */ if (!err) { get_http_time(0, buf, sizeof(buf)); err = enlist_unique_header(rsp->headers, "Date", buf); } /* Some date in the past. */ if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", "Sat, 17 Jun 2000 12:00:00 GMT"); if (!err) { get_http_time(10 * 60, buf, sizeof(buf)); /* 10 * 60sec = 10 minutes */ err = enlist_unique_header(rsp->headers, "Expires", buf); } } else if (!strncmpic(rsp->status, "302", 3)) { get_http_time(0, buf, sizeof(buf)); if (!err) err = enlist_unique_header(rsp->headers, "Date", buf); } else { /* * Setting "Cache-Control" to "no-cache" and "Expires" to * the current time doesn't exactly forbid caching, it just * requires the client to revalidate the cached copy. * * If a temporary problem occurs and the user tries again after * getting Privoxy's error message, a compliant browser may set the * If-Modified-Since header with the content of the error page's * Last-Modified header. More often than not, the document on the server * is older than Privoxy's error message, the server would send status code * 304 and the browser would display the outdated error message again and again. * * For documents delivered with status code 403, 404 and 503 we set "Last-Modified" * to Tim Berners-Lee's birthday, which predates the age of any page on the web * and can be safely used to "revalidate" without getting a status code 304. * * There is no need to let the useless If-Modified-Since header reach the * server, it is therefore stripped by client_if_modified_since in parsers.c. */ if (!err) err = enlist_unique_header(rsp->headers, "Cache-Control", "no-cache"); get_http_time(0, buf, sizeof(buf)); if (!err) err = enlist_unique_header(rsp->headers, "Date", buf); if (!strncmpic(rsp->status, "403", 3) || !strncmpic(rsp->status, "404", 3) || !strncmpic(rsp->status, "502", 3) || !strncmpic(rsp->status, "503", 3) || !strncmpic(rsp->status, "504", 3)) { if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", "Wed, 08 Jun 1955 12:00:00 GMT"); } else { if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", buf); } if (!err) err = enlist_unique_header(rsp->headers, "Expires", "Sat, 17 Jun 2000 12:00:00 GMT"); if (!err) err = enlist_unique_header(rsp->headers, "Pragma", "no-cache"); } if (!err && (!(csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) || (csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED))) { err = enlist_unique_header(rsp->headers, "Connection", "close"); } /* * Write the head */ if (err || (NULL == (rsp->head = list_to_text(rsp->headers)))) { free_http_response(rsp); return cgi_error_memory(); } rsp->head_length = strlen(rsp->head); return rsp; } /********************************************************************* * * Function : alloc_http_response * * Description : Allocates a new http_response structure. * * Parameters : N/A * * Returns : pointer to a new http_response, or NULL. * *********************************************************************/ struct http_response *alloc_http_response(void) { return (struct http_response *) zalloc(sizeof(struct http_response)); } /********************************************************************* * * Function : free_http_response * * Description : Free the memory occupied by an http_response * and its depandant structures. * * Parameters : * 1 : rsp = pointer to http_response to be freed * * Returns : N/A * *********************************************************************/ void free_http_response(struct http_response *rsp) { /* * Must special case cgi_error_memory_response, which is never freed. */ if (rsp && (rsp != cgi_error_memory_response)) { freez(rsp->status); freez(rsp->head); freez(rsp->body); destroy_list(rsp->headers); free(rsp); } } /********************************************************************* * * Function : template_load * * Description : CGI support function that loads a given HTML * template, ignoring comment lines and following * #include statements up to a depth of 1. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : template_ptr = Destination for pointer to loaded * template text. * 3 : templatename = name of the HTML template to be used * 4 : recursive = Flag set if this function calls itself * following an #include statament * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * JB_ERR_FILE if the template file cannot be read * *********************************************************************/ jb_err template_load(const struct client_state *csp, char **template_ptr, const char *templatename, int recursive) { jb_err err; char *templates_dir_path; char *full_path; char *file_buffer; char *included_module; const char *p; FILE *fp; char buf[BUFFER_SIZE]; assert(csp); assert(template_ptr); assert(templatename); *template_ptr = NULL; /* Validate template name. Paranoia. */ for (p = templatename; *p != 0; p++) { if ( ((*p < 'a') || (*p > 'z')) && ((*p < 'A') || (*p > 'Z')) && ((*p < '0') || (*p > '9')) && (*p != '-') && (*p != '.')) { /* Illegal character */ return JB_ERR_FILE; } } /* * Generate full path using either templdir * or confdir/templates as base directory. */ if (NULL != csp->config->templdir) { templates_dir_path = strdup(csp->config->templdir); } else { templates_dir_path = make_path(csp->config->confdir, "templates"); } if (templates_dir_path == NULL) { log_error(LOG_LEVEL_ERROR, "Out of memory while generating template path for %s.", templatename); return JB_ERR_MEMORY; } full_path = make_path(templates_dir_path, templatename); free(templates_dir_path); if (full_path == NULL) { log_error(LOG_LEVEL_ERROR, "Out of memory while generating full template path for %s.", templatename); return JB_ERR_MEMORY; } /* Allocate buffer */ file_buffer = strdup(""); if (file_buffer == NULL) { log_error(LOG_LEVEL_ERROR, "Not enough free memory to buffer %s.", full_path); free(full_path); return JB_ERR_MEMORY; } /* Open template file */ if (NULL == (fp = fopen(full_path, "r"))) { log_error(LOG_LEVEL_ERROR, "Cannot open template file %s: %E", full_path); free(full_path); free(file_buffer); return JB_ERR_FILE; } free(full_path); /* * Read the file, ignoring comments, and honoring #include * statements, unless we're already called recursively. * * XXX: The comment handling could break with lines lengths > sizeof(buf). * This is unlikely in practise. */ while (fgets(buf, sizeof(buf), fp)) { if (!recursive && !strncmp(buf, "#include ", 9)) { if (JB_ERR_OK != (err = template_load(csp, &included_module, chomp(buf + 9), 1))) { free(file_buffer); fclose(fp); return err; } if (string_join(&file_buffer, included_module)) { fclose(fp); return JB_ERR_MEMORY; } continue; } /* skip lines starting with '#' */ if (*buf == '#') { continue; } if (string_append(&file_buffer, buf)) { fclose(fp); return JB_ERR_MEMORY; } } fclose(fp); *template_ptr = file_buffer; return JB_ERR_OK; } /********************************************************************* * * Function : template_fill * * Description : CGI support function that fills in a pre-loaded * HTML template by replacing @name@ with value using * pcrs, for each item in the output map. * * Note that a leading '$' character in the export map's * values will be stripped and toggle on backreference * interpretation. * * Parameters : * 1 : template_ptr = IN: Template to be filled out. * Will be free()d. * OUT: Filled out template. * Caller must free(). * 2 : exports = map with fill in symbol -> name pairs * * Returns : JB_ERR_OK on success (and for uncritical errors) * JB_ERR_MEMORY on out-of-memory error * *********************************************************************/ jb_err template_fill(char **template_ptr, const struct map *exports) { struct map_entry *m; pcrs_job *job; char buf[BUFFER_SIZE]; char *tmp_out_buffer; char *file_buffer; size_t size; int error; const char *flags; assert(template_ptr); assert(*template_ptr); assert(exports); file_buffer = *template_ptr; size = strlen(file_buffer) + 1; /* * Assemble pcrs joblist from exports map */ for (m = exports->first; m != NULL; m = m->next) { if (*m->name == '$') { /* * First character of name is '$', so remove this flag * character and allow backreferences ($1 etc) in the * "replace with" text. */ snprintf(buf, sizeof(buf), "%s", m->name + 1); flags = "sigU"; } else { /* * Treat the "replace with" text as a literal string - * no quoting needed, no backreferences allowed. * ("Trivial" ['T'] flag). */ flags = "sigTU"; /* Enclose name in @@ */ snprintf(buf, sizeof(buf), "@%s@", m->name); } log_error(LOG_LEVEL_CGI, "Substituting: s/%s/%s/%s", buf, m->value, flags); /* Make and run job. */ job = pcrs_compile(buf, m->value, flags, &error); if (job == NULL) { if (error == PCRS_ERR_NOMEM) { free(file_buffer); *template_ptr = NULL; return JB_ERR_MEMORY; } else { log_error(LOG_LEVEL_ERROR, "Error compiling template fill job %s: %d", m->name, error); /* Hope it wasn't important and silently ignore the invalid job */ } } else { error = pcrs_execute(job, file_buffer, size, &tmp_out_buffer, &size); pcrs_free_job(job); if (NULL == tmp_out_buffer) { *template_ptr = NULL; return JB_ERR_MEMORY; } if (error < 0) { /* * Substitution failed, keep the original buffer, * log the problem and ignore it. * * The user might see some unresolved @CGI_VARIABLES@, * but returning a special CGI error page seems unreasonable * and could mask more important error messages. */ free(tmp_out_buffer); log_error(LOG_LEVEL_ERROR, "Failed to execute s/%s/%s/%s. %s", buf, m->value, flags, pcrs_strerror(error)); } else { /* Substitution succeeded, use modified buffer. */ free(file_buffer); file_buffer = tmp_out_buffer; } } } /* * Return */ *template_ptr = file_buffer; return JB_ERR_OK; } /********************************************************************* * * Function : template_fill_for_cgi * * Description : CGI support function that loads a HTML template * and fills it in. Handles file-not-found errors * by sending a HTML error message. For convenience, * this function also frees the passed "exports" map. * * Parameters : * 1 : csp = Client state * 2 : templatename = name of the HTML template to be used * 3 : exports = map with fill in symbol -> name pairs. * Will be freed by this function. * 4 : rsp = Response structure to fill in. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error * *********************************************************************/ jb_err template_fill_for_cgi(const struct client_state *csp, const char *templatename, struct map *exports, struct http_response *rsp) { jb_err err; assert(csp); assert(templatename); assert(exports); assert(rsp); err = template_load(csp, &rsp->body, templatename, 0); if (err == JB_ERR_FILE) { err = cgi_error_no_template(csp, rsp, templatename); } else if (err == JB_ERR_OK) { err = template_fill(&rsp->body, exports); } free_map(exports); return err; } /********************************************************************* * * Function : default_exports * * Description : returns a struct map list that contains exports * which are common to all CGI functions. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : caller = name of CGI who calls us and which should * be excluded from the generated menu. May be * NULL. * Returns : NULL if no memory, else a new map. Caller frees. * *********************************************************************/ struct map *default_exports(const struct client_state *csp, const char *caller) { char buf[30]; jb_err err; struct map * exports; int local_help_exists = 0; char *ip_address = NULL; char *port = NULL; char *hostname = NULL; assert(csp); exports = new_map(); if (csp->config->hostname) { get_host_information(csp->cfd, &ip_address, &port, NULL); hostname = strdup(csp->config->hostname); } else { get_host_information(csp->cfd, &ip_address, &port, &hostname); } err = map(exports, "version", 1, html_encode(VERSION), 0); get_locale_time(buf, sizeof(buf)); if (!err) err = map(exports, "time", 1, html_encode(buf), 0); if (!err) err = map(exports, "my-ip-address", 1, html_encode(ip_address ? ip_address : "unknown"), 0); freez(ip_address); if (!err) err = map(exports, "my-port", 1, html_encode(port ? port : "unknown"), 0); freez(port); if (!err) err = map(exports, "my-hostname", 1, html_encode(hostname ? hostname : "unknown"), 0); freez(hostname); if (!err) err = map(exports, "homepage", 1, html_encode(HOME_PAGE_URL), 0); if (!err) err = map(exports, "default-cgi", 1, html_encode(CGI_PREFIX), 0); if (!err) err = map(exports, "menu", 1, make_menu(caller, csp->config->feature_flags), 0); if (!err) err = map(exports, "code-status", 1, CODE_STATUS, 1); if (!strncmpic(csp->config->usermanual, "file://", 7) || !strncmpic(csp->config->usermanual, "http", 4)) { /* Manual is located somewhere else, just link to it. */ if (!err) err = map(exports, "user-manual", 1, html_encode(csp->config->usermanual), 0); } else { /* Manual is delivered by Privoxy. */ if (!err) err = map(exports, "user-manual", 1, html_encode(CGI_PREFIX"user-manual/"), 0); } if (!err) err = map(exports, "actions-help-prefix", 1, ACTIONS_HELP_PREFIX ,1); #ifdef FEATURE_TOGGLE if (!err) err = map_conditional(exports, "enabled-display", global_toggle_state); #else if (!err) err = map_block_killer(exports, "can-toggle"); #endif if (!strcmp(CODE_STATUS, "stable")) { if (!err) err = map_block_killer(exports, "unstable"); } if (csp->config->admin_address != NULL) { if (!err) err = map(exports, "admin-address", 1, html_encode(csp->config->admin_address), 0); local_help_exists = 1; } else { if (!err) err = map_block_killer(exports, "have-adminaddr-info"); } if (csp->config->proxy_info_url != NULL) { if (!err) err = map(exports, "proxy-info-url", 1, html_encode(csp->config->proxy_info_url), 0); local_help_exists = 1; } else { if (!err) err = map_block_killer(exports, "have-proxy-info"); } if (local_help_exists == 0) { if (!err) err = map_block_killer(exports, "have-help-info"); } if (err) { free_map(exports); return NULL; } return exports; } /********************************************************************* * * Function : map_block_killer * * Description : Convenience function. * Adds a "killer" for the conditional HTML-template * block , i.e. a substitution of the regex * "if--start.*if--end" to the given * export list. * * Parameters : * 1 : exports = map to extend * 2 : name = name of conditional block * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err map_block_killer(struct map *exports, const char *name) { char buf[1000]; /* Will do, since the names are hardwired */ assert(exports); assert(name); assert(strlen(name) < (size_t)490); snprintf(buf, sizeof(buf), "if-%s-start.*if-%s-end", name, name); return map(exports, buf, 1, "", 1); } /********************************************************************* * * Function : map_block_keep * * Description : Convenience function. Removes the markers used * by map-block-killer, to save a few bytes. * i.e. removes "@if--start@" and "@if--end@" * * Parameters : * 1 : exports = map to extend * 2 : name = name of conditional block * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err map_block_keep(struct map *exports, const char *name) { jb_err err; char buf[500]; /* Will do, since the names are hardwired */ assert(exports); assert(name); assert(strlen(name) < (size_t)490); snprintf(buf, sizeof(buf), "if-%s-start", name); err = map(exports, buf, 1, "", 1); if (err) { return err; } snprintf(buf, sizeof(buf), "if-%s-end", name); return map(exports, buf, 1, "", 1); } /********************************************************************* * * Function : map_conditional * * Description : Convenience function. * Adds an "if-then-else" for the conditional HTML-template * block , i.e. a substitution of the form: * @if--then@ * True text * @else-not-@ * False text * @endif-@ * * The control structure and one of the alternatives * will be hidden. * * Parameters : * 1 : exports = map to extend * 2 : name = name of conditional block * 3 : choose_first = nonzero for first, zero for second. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ jb_err map_conditional(struct map *exports, const char *name, int choose_first) { char buf[1000]; /* Will do, since the names are hardwired */ jb_err err; assert(exports); assert(name); assert(strlen(name) < (size_t)480); snprintf(buf, sizeof(buf), (choose_first ? "else-not-%s@.*@endif-%s" : "if-%s-then@.*@else-not-%s"), name, name); err = map(exports, buf, 1, "", 1); if (err) { return err; } snprintf(buf, sizeof(buf), (choose_first ? "if-%s-then" : "endif-%s"), name); return map(exports, buf, 1, "", 1); } /********************************************************************* * * Function : make_menu * * Description : Returns an HTML-formatted menu of the available * unhidden CGIs, excluding the one given in * and the toggle CGI if toggling is disabled. * * Parameters : * 1 : self = name of CGI to leave out, can be NULL for * complete listing. * 2 : feature_flags = feature bitmap from csp->config * * * Returns : menu string, or NULL on out-of-memory error. * *********************************************************************/ char *make_menu(const char *self, const unsigned feature_flags) { const struct cgi_dispatcher *d; char *result = strdup(""); if (self == NULL) { self = "NO-SUCH-CGI!"; } /* List available unhidden CGI's and export as "other-cgis" */ for (d = cgi_dispatchers; d->name; d++) { #ifdef FEATURE_TOGGLE if (!(feature_flags & RUNTIME_FEATURE_CGI_TOGGLE) && !strcmp(d->name, "toggle")) { /* * Suppress the toggle link if remote toggling is disabled. */ continue; } #endif /* def FEATURE_TOGGLE */ if (d->description && strcmp(d->name, self)) { char *html_encoded_prefix; /* * Line breaks would be great, but break * the "blocked" template's JavaScript. */ string_append(&result, "
  • name); string_append(&result, "\">"); string_append(&result, d->description); string_append(&result, "
  • "); } } return result; } /********************************************************************* * * Function : dump_map * * Description : HTML-dump a map for debugging (as table) * * Parameters : * 1 : the_map = map to dump * * Returns : string with HTML * *********************************************************************/ char *dump_map(const struct map *the_map) { struct map_entry *cur_entry; char *ret = strdup(""); string_append(&ret, "\n"); for (cur_entry = the_map->first; (cur_entry != NULL) && (ret != NULL); cur_entry = cur_entry->next) { string_append(&ret, "\n"); } string_append(&ret, "
    "); string_join (&ret, html_encode(cur_entry->name)); string_append(&ret, ""); string_join (&ret, html_encode(cur_entry->value)); string_append(&ret, "
    \n"); return ret; } /* Local Variables: tab-width: 3 end: */ privoxy-3.0.28-stable/./cgi.h000640 001751 000000 00000012325 13412210415 015366 0ustar00fkwheel000000 000000 #ifndef CGI_H_INCLUDED #define CGI_H_INCLUDED /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgi.h,v $ * * Purpose : Declares functions to intercept request, generate * html or gif answers, and to compose HTTP resonses. * * Functions declared include: * * * Copyright : Written by and Copyright (C) 2001-2009 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and * Junkbusters Corporation. http://www.junkbusters.com * * 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. * * The GNU General Public License should be included with * this file. If not, you can view it at * http://www.gnu.org/copyleft/gpl.html * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * **********************************************************************/ #include "project.h" /* * Main dispatch function */ extern struct http_response *dispatch_cgi(struct client_state *csp); /* Not exactly a CGI */ extern struct http_response *error_response(struct client_state *csp, const char *templatename); /* * CGI support functions */ extern struct http_response * alloc_http_response(void); extern void free_http_response(struct http_response *rsp); extern struct http_response *finish_http_response(struct client_state *csp, struct http_response *rsp); extern struct map * default_exports(const struct client_state *csp, const char *caller); extern jb_err map_block_killer (struct map *exports, const char *name); extern jb_err map_block_keep (struct map *exports, const char *name); extern jb_err map_conditional (struct map *exports, const char *name, int choose_first); extern jb_err template_load(const struct client_state *csp, char ** template_ptr, const char *templatename, int recursive); extern jb_err template_fill(char ** template_ptr, const struct map *exports); extern jb_err template_fill_for_cgi(const struct client_state *csp, const char *templatename, struct map *exports, struct http_response *rsp); extern void cgi_init_error_messages(void); extern struct http_response *cgi_error_memory(void); extern jb_err cgi_redirect (struct http_response * rsp, const char *target); extern jb_err cgi_error_no_template(const struct client_state *csp, struct http_response *rsp, const char *template_name); extern jb_err cgi_error_bad_param(const struct client_state *csp, struct http_response *rsp); extern jb_err cgi_error_disabled(const struct client_state *csp, struct http_response *rsp); extern jb_err cgi_error_unknown(const struct client_state *csp, struct http_response *rsp, jb_err error_to_report); extern jb_err get_number_param(struct client_state *csp, const struct map *parameters, char *name, unsigned *pvalue); extern jb_err get_string_param(const struct map *parameters, const char *param_name, const char **pparam); extern char get_char_param(const struct map *parameters, const char *param_name); #ifdef FEATURE_COMPRESSION /* * Minimum length which a buffer has to reach before * we bother to (re-)compress it. Completely arbitrary. */ extern const size_t LOWER_LENGTH_LIMIT_FOR_COMPRESSION; extern char *compress_buffer(char *buffer, size_t *buffer_length, int compression_level); #endif /* * Text generators */ extern void get_http_time(int time_offset, char *buf, size_t buffer_size); extern char *add_help_link(const char *item, struct configuration_spec *config); extern char *make_menu(const char *self, const unsigned feature_flags); extern char *dump_map(const struct map *the_map); /* * Ad replacement images */ extern const char image_pattern_data[]; extern const size_t image_pattern_length; extern const char image_blank_data[]; extern const size_t image_blank_length; #endif /* ndef CGI_H_INCLUDED */ /* Local Variables: tab-width: 3 end: */ privoxy-3.0.28-stable/./cgiedit.c000640 001751 000000 00000407053 13412210415 016235 0ustar00fkwheel000000 000000 /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgiedit.c,v $ * * Purpose : CGI-based actionsfile editor. * * NOTE: The CGIs in this file use parameter names * such as "f" and "s" which are really *BAD* choices. * However, I'm trying to save bytes in the * edit-actions-list HTML page - the standard actions * file generated a 550kbyte page, which is ridiculous. * * Stick to the short names in this file for consistency. * * Copyright : Written by and Copyright (C) 2001-2014 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and * Junkbusters Corporation. http://www.junkbusters.com * * 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. * * The GNU General Public License should be included with * this file. If not, you can view it at * http://www.gnu.org/copyleft/gpl.html * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * **********************************************************************/ #include "config.h" /* * FIXME: Following includes copied from cgi.c - which are actually needed? */ #include #include #include #include #include #include #include #include "project.h" #include "cgi.h" #include "cgiedit.h" #include "cgisimple.h" #include "list.h" #include "encode.h" #include "actions.h" #include "miscutil.h" #include "errlog.h" #include "loaders.h" #ifdef FEATURE_TOGGLE /* loadcfg.h is for global_toggle_state only */ #include "loadcfg.h" #endif /* def FEATURE_TOGGLE */ #include "urlmatch.h" #ifdef FEATURE_CGI_EDIT_ACTIONS /** * A line in an editable_file. */ struct file_line { /** Next entry in the linked list */ struct file_line * next; /** The raw data, to write out if this line is unmodified. */ char * raw; /** Comments and/or whitespace to put before this line if it's modified and then written out. */ char * prefix; /** The actual data, as a string. Line continuation and comment removal are performed on the data read from file before it's stored here, so it will be a single line of data. */ char * unprocessed; /** The type of data on this line. One of the FILE_LINE_xxx constants. */ int type; /** The actual data, processed into some sensible data type. */ union { /** An action specification. */ struct action_spec action[1]; /** A name=value pair. */ struct { /** The name in the name=value pair. */ char * name; /** The value in the name=value pair, as a string. */ char * svalue; /** The value in the name=value pair, as an integer. */ int ivalue; } setting; } data; }; /** This file_line has not been processed yet. */ #define FILE_LINE_UNPROCESSED 1 /** This file_line is blank. Can only appear at the end of a file, due to the way the parser works. */ #define FILE_LINE_BLANK 2 /** This file_line says {{alias}}. */ #define FILE_LINE_ALIAS_HEADER 3 /** This file_line defines an alias. */ #define FILE_LINE_ALIAS_ENTRY 4 /** This file_line defines an {action}. */ #define FILE_LINE_ACTION 5 /** This file_line specifies a URL pattern. */ #define FILE_LINE_URL 6 /** This file_line says {{settings}}. */ #define FILE_LINE_SETTINGS_HEADER 7 /** This file_line is in a {{settings}} block. */ #define FILE_LINE_SETTINGS_ENTRY 8 /** This file_line says {{description}}. */ #define FILE_LINE_DESCRIPTION_HEADER 9 /** This file_line is in a {{description}} block. */ #define FILE_LINE_DESCRIPTION_ENTRY 10 /* * Number of file modification time mismatches * before the CGI editor gets turned off. */ #define ACCEPTABLE_TIMESTAMP_MISMATCHES 3 /** * A configuration file, in a format that can be edited and written back to * disk. */ struct editable_file { struct file_line * lines; /**< The contents of the file. A linked list of lines. */ const char * filename; /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */ unsigned identifier; /**< The file name's position in csp->config->actions_file[]. */ const char * version_str; /**< Last modification time, as a string. For CGI param. */ /**< Can be used in URL without using url_param(). */ unsigned version; /**< Last modification time - prevents chaos with the browser's "back" button. Note that this is a time_t cast to an unsigned. When comparing, always cast the time_t to an unsigned, and *NOT* vice-versa. This may lose the top few bits, but they're not significant anyway. */ int newline; /**< Newline convention - one of the NEWLINE_xxx constants. Note that changing this after the file has been read in will cause a mess. */ struct file_line * parse_error; /**< On parse error, this is the offending line. */ const char * parse_error_text; /**< On parse error, this is the problem. (Statically allocated) */ }; /** * Information about the filter types. * Used for macro replacement in cgi_edit_actions_for_url. */ struct filter_type_info { const int multi_action_index; /**< The multi action index as defined in project.h */ const char *macro_name; /**< Name of the macro that has to be replaced with the prepared templates. For example "content-filter-params" */ const char *type; /**< Name of the filter type, for example "server-header-filter". */ /* XXX: check if these two can be combined. */ const char *disable_all_option; /**< Name of the catch-all radio option that has to be checked or unchecked for this filter type. */ const char *disable_all_param; /**< Name of the parameter that causes all filters of this type to be disabled. */ const char *abbr_type; /**< Abbreviation of the filter type, usually the first or second character capitalized */ const char *anchor; /**< Anchor for the User Manual link, for example "SERVER-HEADER-FILTER" */ }; /* Accessed by index, keep the order in the way the FT_ macros are defined. */ static const struct filter_type_info filter_type_info[] = { { ACTION_MULTI_FILTER, "content-filter-params", "filter", "filter-all", "filter_all", "F", "FILTER" }, { ACTION_MULTI_CLIENT_HEADER_FILTER, "client-header-filter-params", "client-header-filter", "client-header-filter-all", "client_header_filter_all", "C", "CLIENT-HEADER-FILTER" }, { ACTION_MULTI_SERVER_HEADER_FILTER, "server-header-filter-params", "server-header-filter", "server-header-filter-all", "server_header_filter_all", "S", "SERVER-HEADER-FILTER" }, { ACTION_MULTI_CLIENT_HEADER_TAGGER, "client-header-tagger-params", "client-header-tagger", "client-header-tagger-all", "client_header_tagger_all", "L", "CLIENT-HEADER-TAGGER" }, { ACTION_MULTI_SERVER_HEADER_TAGGER, "server-header-tagger-params", "server-header-tagger", "server-header-tagger-all", "server_header_tagger_all", "E", "SERVER-HEADER-TAGGER" }, #ifdef FEATURE_EXTERNAL_FILTERS { ACTION_MULTI_EXTERNAL_FILTER, "external-content-filter-params", "external-filter", "external-content-filter-all", "external_content_filter_all", "E", "EXTERNAL-CONTENT-FILTER" }, #endif }; /* FIXME: Following non-static functions should be prototyped in .h or made static */ /* Functions to read and write arbitrary config files */ jb_err edit_read_file(struct client_state *csp, const struct map *parameters, int require_version, struct editable_file **pfile); jb_err edit_write_file(struct editable_file * file); void edit_free_file(struct editable_file * file); /* Functions to read and write actions files */ jb_err edit_parse_actions_file(struct editable_file * file); jb_err edit_read_actions_file(struct client_state *csp, struct http_response *rsp, const struct map *parameters, int require_version, struct editable_file **pfile); /* Error handlers */ jb_err cgi_error_modified(struct client_state *csp, struct http_response *rsp, const char *filename); jb_err cgi_error_parse(struct client_state *csp, struct http_response *rsp, struct editable_file *file); jb_err cgi_error_file(struct client_state *csp, struct http_response *rsp, const char *filename); jb_err cgi_error_file_read_only(struct client_state *csp, struct http_response *rsp, const char *filename); /* Internal arbitrary config file support functions */ static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline); static void edit_free_file_lines(struct file_line * first_line); /* Internal actions file support functions */ static int match_actions_file_header_line(const char * line, const char * name); static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue); /* Internal parameter parsing functions */ static jb_err get_url_spec_param(struct client_state *csp, const struct map *parameters, const char *name, char **pvalue); /* Internal actionsfile <==> HTML conversion functions */ static jb_err map_radio(struct map * exports, const char * optionname, const char * values, int value); static jb_err actions_to_radio(struct map * exports, const struct action_spec *action); static jb_err actions_from_radio(const struct map * parameters, struct action_spec *action); static jb_err map_copy_parameter_html(struct map *out, const struct map *in, const char *name); static jb_err get_file_name_param(struct client_state *csp, const struct map *parameters, const char *param_name, const char **pfilename); /* Internal convenience functions */ static char *section_target(const unsigned sectionid); /********************************************************************* * * Function : section_target * * Description : Given an unsigned (section id) n, produce a dynamically * allocated string of the form #l, for use in link * targets. * * XXX: The hash should be moved into the templates * to make this function more generic and render * stringify() obsolete. * * Parameters : * 1 : sectionid = start line number of section * * Returns : String with link target, or NULL if out of * memory * *********************************************************************/ static char *section_target(const unsigned sectionid) { char buf[30]; snprintf(buf, sizeof(buf), "#l%u", sectionid); return(strdup(buf)); } /********************************************************************* * * Function : stringify * * Description : Convert a number into a dynamically allocated string. * * Parameters : * 1 : number = The number to convert. * * Returns : String with link target, or NULL if out of memory * *********************************************************************/ static char *stringify(const unsigned number) { char buf[6]; snprintf(buf, sizeof(buf), "%u", number); return strdup(buf); } /********************************************************************* * * Function : map_copy_parameter_html * * Description : Copy a CGI parameter from one map to another, HTML * encoding it. * * Parameters : * 1 : out = target map * 2 : in = source map * 3 : name = name of cgi parameter to copy * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if the parameter doesn't exist * in the source map * *********************************************************************/ static jb_err map_copy_parameter_html(struct map *out, const struct map *in, const char *name) { const char * value; jb_err err; assert(out); assert(in); assert(name); value = lookup(in, name); err = map(out, name, 1, html_encode(value), 0); if (err) { /* Out of memory */ return err; } else if (*value == '\0') { return JB_ERR_CGI_PARAMS; } else { return JB_ERR_OK; } } /********************************************************************* * * Function : cgi_edit_actions_url_form * * Description : CGI function that displays a form for * edit-actions-url * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = http_response data structure for output * 3 : parameters = map of cgi parameters * * CGI Parameters * i : (action index) Identifies the file to edit * v : (version) File's last-modified time * p : (pattern) Line number of pattern to edit * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if the CGI parameters are not * specified or not valid. * *********************************************************************/ jb_err cgi_edit_actions_url_form(struct client_state *csp, struct http_response *rsp, const struct map *parameters) { struct map * exports; unsigned patternid; struct editable_file * file; struct file_line * cur_line; unsigned line_number; unsigned section_start_line_number = 0; jb_err err; assert(csp); assert(rsp); assert(parameters); if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) { return cgi_error_disabled(csp, rsp); } err = get_number_param(csp, parameters, "p", &patternid); if (err) { return err; } err = edit_read_actions_file(csp, rsp, parameters, 1, &file); if (err) { /* No filename specified, can't read file, modified, or out of memory. */ return (err == JB_ERR_FILE ? JB_ERR_OK : err); } cur_line = file->lines; for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++) { if (cur_line->type == FILE_LINE_ACTION) { section_start_line_number = line_number; } cur_line = cur_line->next; } if ( (cur_line == NULL) || (line_number != patternid) || (patternid < 1U) || (cur_line->type != FILE_LINE_URL)) { /* Invalid "patternid" parameter */ edit_free_file(file); return JB_ERR_CGI_PARAMS; } if (NULL == (exports = default_exports(csp, NULL))) { edit_free_file(file); return JB_ERR_MEMORY; } err = map(exports, "f", 1, stringify(file->identifier), 0); if (!err) err = map(exports, "v", 1, file->version_str, 1); if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0); if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0); if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0); edit_free_file(file); if (err) { free_map(exports); return err; } return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp); } /********************************************************************* * * Function : cgi_edit_actions_add_url_form * * Description : CGI function that displays a form for * edit-actions-url * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = http_response data structure for output * 3 : parameters = map of cgi parameters * * CGI Parameters : * f : (filename) Identifies the file to edit * v : (version) File's last-modified time * s : (section) Line number of section to edit * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if the CGI parameters are not * specified or not valid. * *********************************************************************/ jb_err cgi_edit_actions_add_url_form(struct client_state *csp, struct http_response *rsp, const struct map *parameters) { struct map *exports; jb_err err; assert(csp); assert(rsp); assert(parameters); if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) { return cgi_error_disabled(csp, rsp); } if (NULL == (exports = default_exports(csp, NULL))) { return JB_ERR_MEMORY; } err = map_copy_parameter_html(exports, parameters, "f"); if (!err) err = map_copy_parameter_html(exports, parameters, "v"); if (!err) err = map_copy_parameter_html(exports, parameters, "s"); if (err) { free_map(exports); return err; } return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp); } /********************************************************************* * * Function : cgi_edit_actions_remove_url_form * * Description : CGI function that displays a form for * edit-actions-url * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = http_response data structure for output * 3 : parameters = map of cgi parameters * * CGI Parameters : * f : (number) The action file identifier. * v : (version) File's last-modified time * p : (pattern) Line number of pattern to edit * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if the CGI parameters are not * specified or not valid. * *********************************************************************/ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp, struct http_response *rsp, const struct map *parameters) { struct map * exports; unsigned patternid; struct editable_file * file; struct file_line * cur_line; unsigned line_number; unsigned section_start_line_number = 0; jb_err err; assert(csp); assert(rsp); assert(parameters); if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) { return cgi_error_disabled(csp, rsp); } err = get_number_param(csp, parameters, "p", &patternid); if (err) { return err; } err = edit_read_actions_file(csp, rsp, parameters, 1, &file); if (err) { /* No filename specified, can't read file, modified, or out of memory. */ return (err == JB_ERR_FILE ? JB_ERR_OK : err); } cur_line = file->lines; for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++) { if (cur_line->type == FILE_LINE_ACTION) { section_start_line_number = line_number; } cur_line = cur_line->next; } if ( (cur_line == NULL) || (line_number != patternid) || (patternid < 1U) || (cur_line->type != FILE_LINE_URL)) { /* Invalid "patternid" parameter */ edit_free_file(file); return JB_ERR_CGI_PARAMS; } if (NULL == (exports = default_exports(csp, NULL))) { edit_free_file(file); return JB_ERR_MEMORY; } err = map(exports, "f", 1, stringify(file->identifier), 0); if (!err) err = map(exports, "v", 1, file->version_str, 1); if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0); if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0); if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0); if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0); edit_free_file(file); if (err) { free_map(exports); return err; } return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp); } /********************************************************************* * * Function : edit_write_file * * Description : Write a complete file to disk. * * Parameters : * 1 : file = File to write. * * Returns : JB_ERR_OK on success * JB_ERR_FILE on error writing to file. * JB_ERR_MEMORY on out of memory * *********************************************************************/ jb_err edit_write_file(struct editable_file * file) { FILE * fp; struct file_line * cur_line; struct stat statbuf[1]; char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of digits in time_t, assuming this is a 64-bit machine, plus null terminator, plus one for paranoia */ assert(file); assert(file->filename); if (NULL == (fp = fopen(file->filename, "wb"))) { return JB_ERR_FILE; } cur_line = file->lines; while (cur_line != NULL) { if (cur_line->raw) { if (fputs(cur_line->raw, fp) < 0) { fclose(fp); return JB_ERR_FILE; } } else { if (cur_line->prefix) { if (fputs(cur_line->prefix, fp) < 0) { fclose(fp); return JB_ERR_FILE; } } if (cur_line->unprocessed) { if (NULL != strchr(cur_line->unprocessed, '#')) { /* Must quote '#' characters */ int numhash = 0; size_t len; char * src; char * dest; char * str; /* Count number of # characters, so we know length of output string */ src = cur_line->unprocessed; while (NULL != (src = strchr(src, '#'))) { numhash++; src++; } assert(numhash > 0); /* Allocate new memory for string */ len = strlen(cur_line->unprocessed) + (size_t)numhash; str = malloc_or_die(len + 1); /* Copy string but quote hashes */ src = cur_line->unprocessed; dest = str; while (*src) { if (*src == '#') { *dest++ = '\\'; numhash--; assert(numhash >= 0); } *dest++ = *src++; } *dest = '\0'; assert(numhash == 0); assert(strlen(str) == len); assert(str == dest - len); assert(src - len <= cur_line->unprocessed); if ((strlen(str) != len) || (numhash != 0)) { /* * Escaping didn't work as expected, go spread the news. * Only reached in non-debugging builds. */ log_error(LOG_LEVEL_ERROR, "Looks like hash escaping failed. %s might be corrupted now.", file->filename); } if (fputs(str, fp) < 0) { free(str); fclose(fp); return JB_ERR_FILE; } free(str); } else { /* Can write without quoting '#' characters. */ if (fputs(cur_line->unprocessed, fp) < 0) { fclose(fp); return JB_ERR_FILE; } } if (fputs(NEWLINE(file->newline), fp) < 0) { fclose(fp); return JB_ERR_FILE; } } else { /* FIXME: Write data from file->data->whatever */ assert(0); } } cur_line = cur_line->next; } fclose(fp); /* Update the version stamp in the file structure, since we just * wrote to the file & changed it's date. */ if (stat(file->filename, statbuf) < 0) { /* Error, probably file not found. */ return JB_ERR_FILE; } file->version = (unsigned)statbuf->st_mtime; /* Correct file->version_str */ freez(file->version_str); snprintf(version_buf, sizeof(version_buf), "%u", file->version); version_buf[sizeof(version_buf)-1] = '\0'; file->version_str = strdup_or_die(version_buf); return JB_ERR_OK; } /********************************************************************* * * Function : edit_free_file * * Description : Free a complete file in memory. * * Parameters : * 1 : file = Data structure to free. * * Returns : N/A * *********************************************************************/ void edit_free_file(struct editable_file * file) { if (!file) { /* Silently ignore NULL pointer */ return; } edit_free_file_lines(file->lines); freez(file->version_str); file->version = 0; file->parse_error_text = NULL; /* Statically allocated */ file->parse_error = NULL; free(file); } /********************************************************************* * * Function : edit_free_file_lines * * Description : Free an entire linked list of file lines. * * Parameters : * 1 : first_line = Data structure to free. * * Returns : N/A * *********************************************************************/ static void edit_free_file_lines(struct file_line * first_line) { struct file_line * next_line; while (first_line != NULL) { next_line = first_line->next; first_line->next = NULL; freez(first_line->raw); freez(first_line->prefix); freez(first_line->unprocessed); switch(first_line->type) { case 0: /* special case if memory zeroed */ case FILE_LINE_UNPROCESSED: case FILE_LINE_BLANK: case FILE_LINE_ALIAS_HEADER: case FILE_LINE_SETTINGS_HEADER: case FILE_LINE_DESCRIPTION_HEADER: case FILE_LINE_DESCRIPTION_ENTRY: case FILE_LINE_ALIAS_ENTRY: case FILE_LINE_URL: /* No data is stored for these */ break; case FILE_LINE_ACTION: free_action(first_line->data.action); break; case FILE_LINE_SETTINGS_ENTRY: freez(first_line->data.setting.name); freez(first_line->data.setting.svalue); break; default: /* Should never happen */ assert(0); break; } first_line->type = 0; /* paranoia */ free(first_line); first_line = next_line; } } /********************************************************************* * * Function : match_actions_file_header_line * * Description : Match an actions file {{header}} line * * Parameters : * 1 : line = String from file * 2 : name = Header to match against * * Returns : 0 iff they match. * *********************************************************************/ static int match_actions_file_header_line(const char * line, const char * name) { size_t len; assert(line); assert(name); /* Look for "{{" */ if ((line[0] != '{') || (line[1] != '{')) { return 1; } line += 2; /* Look for optional whitespace */ while ((*line == ' ') || (*line == '\t')) { line++; } /* Look for the specified name (case-insensitive) */ len = strlen(name); if (0 != strncmpic(line, name, len)) { return 1; } line += len; /* Look for optional whitespace */ while ((*line == ' ') || (*line == '\t')) { line++; } /* Look for "}}" and end of string*/ if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0')) { return 1; } /* It matched!! */ return 0; } /********************************************************************* * * Function : match_actions_file_header_line * * Description : Match an actions file {{header}} line * * Parameters : * 1 : line = String from file. Must not start with * whitespace (else infinite loop!) * 2 : pname = Destination for name * 2 : pvalue = Destination for value * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_PARSE if there's no "=" sign, or if there's * nothing before the "=" sign (but empty * values *after* the "=" sign are legal). * *********************************************************************/ static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue) { const char * name_end; const char * value_start; size_t name_len; assert(line); assert(pname); assert(pvalue); assert(*line != ' '); assert(*line != '\t'); *pname = NULL; *pvalue = NULL; value_start = strchr(line, '='); if ((value_start == NULL) || (value_start == line)) { return JB_ERR_PARSE; } name_end = value_start - 1; /* Eat any whitespace before the '=' */ while ((*name_end == ' ') || (*name_end == '\t')) { /* * we already know we must have at least 1 non-ws char * at start of buf - no need to check */ name_end--; } name_len = (size_t)(name_end - line) + 1; /* Length excluding \0 */ *pname = malloc_or_die(name_len + 1); strncpy(*pname, line, name_len); (*pname)[name_len] = '\0'; /* Eat any the whitespace after the '=' */ value_start++; while ((*value_start == ' ') || (*value_start == '\t')) { value_start++; } if (NULL == (*pvalue = strdup(value_start))) { free(*pname); *pname = NULL; return JB_ERR_MEMORY; } return JB_ERR_OK; } /********************************************************************* * * Function : edit_parse_actions_file * * Description : Parse an actions file in memory. * * Passed linked list must have the "data" member * zeroed, and must contain valid "next" and * "unprocessed" fields. The "raw" and "prefix" * fields are ignored, and "type" is just overwritten. * * Note that on error the file may have been * partially parsed. * * Parameters : * 1 : file = Actions file to be parsed in-place. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_PARSE on error * *********************************************************************/ jb_err edit_parse_actions_file(struct editable_file * file) { struct file_line * cur_line; size_t len; const char * text; /* Text from a line */ char * name; /* For lines of the form name=value */ char * value; /* For lines of the form name=value */ struct action_alias * alias_list = NULL; jb_err err = JB_ERR_OK; /* alias_list contains the aliases defined in this file. * It might be better to use the "file_line.data" fields * in the relavent places instead. */ cur_line = file->lines; /* A note about blank line support: Blank lines should only * ever occur as the last line in the file. This function * is more forgiving than that - FILE_LINE_BLANK can occur * anywhere. */ /* Skip leading blanks. Should only happen if file is * empty (which is valid, but pointless). */ while ((cur_line != NULL) && (cur_line->unprocessed[0] == '\0')) { /* Blank line */ cur_line->type = FILE_LINE_BLANK; cur_line = cur_line->next; } if ((cur_line != NULL) && (cur_line->unprocessed[0] != '{')) { /* File doesn't start with a header */ file->parse_error = cur_line; file->parse_error_text = "First (non-comment) line of the file must contain a header."; return JB_ERR_PARSE; } if ((cur_line != NULL) && (0 == match_actions_file_header_line(cur_line->unprocessed, "settings"))) { cur_line->type = FILE_LINE_SETTINGS_HEADER; cur_line = cur_line->next; while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{')) { if (cur_line->unprocessed[0]) { cur_line->type = FILE_LINE_SETTINGS_ENTRY; err = split_line_on_equals(cur_line->unprocessed, &cur_line->data.setting.name, &cur_line->data.setting.svalue); if (err == JB_ERR_MEMORY) { return err; } else if (err != JB_ERR_OK) { /* Line does not contain a name=value pair */ file->parse_error = cur_line; file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one."; return JB_ERR_PARSE; } } else { cur_line->type = FILE_LINE_BLANK; } cur_line = cur_line->next; } } if ((cur_line != NULL) && (0 == match_actions_file_header_line(cur_line->unprocessed, "description"))) { cur_line->type = FILE_LINE_DESCRIPTION_HEADER; cur_line = cur_line->next; while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{')) { if (cur_line->unprocessed[0]) { cur_line->type = FILE_LINE_DESCRIPTION_ENTRY; } else { cur_line->type = FILE_LINE_BLANK; } cur_line = cur_line->next; } } if ((cur_line != NULL) && (0 == match_actions_file_header_line(cur_line->unprocessed, "alias"))) { cur_line->type = FILE_LINE_ALIAS_HEADER; cur_line = cur_line->next; while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{')) { if (cur_line->unprocessed[0]) { /* define an alias */ struct action_alias * new_alias; cur_line->type = FILE_LINE_ALIAS_ENTRY; err = split_line_on_equals(cur_line->unprocessed, &name, &value); if (err == JB_ERR_MEMORY) { free_alias_list(alias_list); return err; } else if (err != JB_ERR_OK) { /* Line does not contain a name=value pair */ file->parse_error = cur_line; file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one."; free_alias_list(alias_list); return JB_ERR_PARSE; } new_alias = zalloc_or_die(sizeof(*new_alias)); err = get_actions(value, alias_list, new_alias->action); if (err) { /* Invalid action or out of memory */ free(name); free(value); free(new_alias); free_alias_list(alias_list); if (err == JB_ERR_MEMORY) { return err; } else { /* Line does not contain a name=value pair */ file->parse_error = cur_line; file->parse_error_text = "This alias does not specify a valid set of actions."; return JB_ERR_PARSE; } } free(value); new_alias->name = name; /* add to list */ new_alias->next = alias_list; alias_list = new_alias; } else { cur_line->type = FILE_LINE_BLANK; } cur_line = cur_line->next; } } /* Header done, process the main part of the file */ while (cur_line != NULL) { /* At this point, (cur_line->unprocessed[0] == '{') */ assert(cur_line->unprocessed[0] == '{'); text = cur_line->unprocessed + 1; len = strlen(text) - 1; if (text[len] != '}') { /* No closing } on header */ free_alias_list(alias_list); file->parse_error = cur_line; file->parse_error_text = "Headers starting with '{' must have a " "closing bracket ('}'). Headers starting with two brackets ('{{') " "must close with two brackets ('}}')."; return JB_ERR_PARSE; } if (text[0] == '{') { /* An invalid {{ header. */ free_alias_list(alias_list); file->parse_error = cur_line; file->parse_error_text = "Unknown or unexpected two-bracket header. " "Please remember that the system (two-bracket) headers must " "appear in the order {{settings}}, {{description}}, {{alias}}, " "and must appear before any actions (one-bracket) headers. " "Also note that system headers may not be repeated."; return JB_ERR_PARSE; } while ((*text == ' ') || (*text == '\t')) { text++; len--; } while ((len > (size_t)0) && ((text[len - 1] == ' ') || (text[len - 1] == '\t'))) { len--; } cur_line->type = FILE_LINE_ACTION; /* Remove {} and make copy */ value = malloc_or_die(len + 1); strncpy(value, text, len); value[len] = '\0'; /* Get actions */ err = get_actions(value, alias_list, cur_line->data.action); if (err) { /* Invalid action or out of memory */ free(value); free_alias_list(alias_list); if (err == JB_ERR_MEMORY) { return err; } else { /* Line does not contain a name=value pair */ file->parse_error = cur_line; file->parse_error_text = "This header does not specify a valid set of actions."; return JB_ERR_PARSE; } } /* Done with string - it was clobbered anyway */ free(value); /* Process next line */ cur_line = cur_line->next; /* Loop processing URL patterns */ while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{')) { if (cur_line->unprocessed[0]) { /* Could parse URL here, but this isn't currently needed */ cur_line->type = FILE_LINE_URL; } else { cur_line->type = FILE_LINE_BLANK; } cur_line = cur_line->next; } } /* End main while(cur_line != NULL) loop */ free_alias_list(alias_list); return JB_ERR_OK; } /********************************************************************* * * Function : edit_read_file_lines * * Description : Read all the lines of a file into memory. * Handles whitespace, comments and line continuation. * * Parameters : * 1 : fp = File to read from. On return, this will be * at EOF but it will not have been closed. * 2 : pfile = Destination for a linked list of file_lines. * Will be set to NULL on error. * 3 : newline = How to handle newlines. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * *********************************************************************/ jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline) { struct file_line * first_line; /* Keep for return value or to free */ struct file_line * cur_line; /* Current line */ struct file_line * prev_line; /* Entry with prev_line->next = cur_line */ jb_err rval; assert(fp); assert(pfile); *pfile = NULL; cur_line = first_line = zalloc_or_die(sizeof(struct file_line)); cur_line->type = FILE_LINE_UNPROCESSED; rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL); if (rval) { /* Out of memory or empty file. */ /* Note that empty file is not an error we propagate up */ free(cur_line); return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval); } do { prev_line = cur_line; cur_line = prev_line->next = zalloc_or_die(sizeof(struct file_line)); cur_line->type = FILE_LINE_UNPROCESSED; rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL); if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE)) { /* Out of memory */ edit_free_file_lines(first_line); return JB_ERR_MEMORY; } } while (rval != JB_ERR_FILE); /* EOF */ /* We allocated one too many - free it */ prev_line->next = NULL; free(cur_line); *pfile = first_line; return JB_ERR_OK; } /********************************************************************* * * Function : edit_read_file * * Description : Read a complete file into memory. * Handles CGI parameter parsing. If requested, also * checks the file's modification timestamp. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : parameters = map of cgi parameters. * 3 : require_version = true to check "ver" parameter. * 4 : pfile = Destination for the file. Will be set * to NULL on error. * * CGI Parameters : * f : The action file identifier. * ver : (Only if require_version is nonzero) * Timestamp of the actions file. If wrong, this * function fails with JB_ERR_MODIFIED. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if "filename" was not specified * or is not valid. * JB_ERR_FILE if the file cannot be opened or * contains no data * JB_ERR_MODIFIED if version checking was requested and * failed - the file was modified outside * of this CGI editor instance. * *********************************************************************/ jb_err edit_read_file(struct client_state *csp, const struct map *parameters, int require_version, struct editable_file **pfile) { struct file_line * lines; FILE * fp; jb_err err; const char *filename = NULL; struct editable_file * file; unsigned version = 0; struct stat statbuf[1]; char version_buf[22]; int newline = NEWLINE_UNKNOWN; unsigned i; assert(csp); assert(parameters); assert(pfile); *pfile = NULL; err = get_number_param(csp, parameters, "f", &i); if ((JB_ERR_OK == err) && (i < MAX_AF_FILES) && (NULL != csp->config->actions_file[i])) { filename = csp->config->actions_file[i]; } else if (JB_ERR_CGI_PARAMS == err) { /* * Probably an old-school URL like * http://config.privoxy.org/edit-actions-list?f=default */ get_file_name_param(csp, parameters, "f", &filename); } if (NULL == filename || stat(filename, statbuf) < 0) { /* Error, probably file not found. */ return JB_ERR_FILE; } version = (unsigned) statbuf->st_mtime; if (require_version) { unsigned specified_version; err = get_number_param(csp, parameters, "v", &specified_version); if (err) { return err; } if (version != specified_version) { return JB_ERR_MODIFIED; } } if (NULL == (fp = fopen(filename,"rb"))) { return JB_ERR_FILE; } err = edit_read_file_lines(fp, &lines, &newline); fclose(fp); if (err) { return err; } file = zalloc_or_die(sizeof(*file)); file->lines = lines; file->newline = newline; file->filename = filename; file->version = version; file->identifier = i; /* Correct file->version_str */ freez(file->version_str); snprintf(version_buf, sizeof(version_buf), "%u", file->version); version_buf[sizeof(version_buf)-1] = '\0'; file->version_str = strdup_or_die(version_buf); *pfile = file; return JB_ERR_OK; } /********************************************************************* * * Function : edit_read_actions_file * * Description : Read a complete actions file into memory. * Handles CGI parameter parsing. If requested, also * checks the file's modification timestamp. * * If this function detects an error in the categories * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE, * then it handles it by filling in the specified * response structure and returning JB_ERR_FILE. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : rsp = HTTP response. Only filled in on error. * 2 : parameters = map of cgi parameters. * 3 : require_version = true to check "ver" parameter. * 4 : pfile = Destination for the file. Will be set * to NULL on error. * * CGI Parameters : * f : The actions file identifier. * ver : (Only if require_version is nonzero) * Timestamp of the actions file. If wrong, this * function fails with JB_ERR_MODIFIED. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if "filename" was not specified * or is not valid. * JB_ERR_FILE if the file does not contain valid data, * or if file cannot be opened or * contains no data, or if version * checking was requested and failed. * *********************************************************************/ jb_err edit_read_actions_file(struct client_state *csp, struct http_response *rsp, const struct map *parameters, int require_version, struct editable_file **pfile) { jb_err err; struct editable_file *file; static int acceptable_failures = ACCEPTABLE_TIMESTAMP_MISMATCHES - 1; assert(csp); assert(parameters); assert(pfile); *pfile = NULL; err = edit_read_file(csp, parameters, require_version, &file); if (err) { /* Try to handle if possible */ if (err == JB_ERR_FILE) { err = cgi_error_file(csp, rsp, lookup(parameters, "f")); } else if (err == JB_ERR_MODIFIED) { assert(require_version); err = cgi_error_modified(csp, rsp, lookup(parameters, "f")); log_error(LOG_LEVEL_ERROR, "Blocking CGI edit request due to modification time mismatch."); if (acceptable_failures > 0) { log_error(LOG_LEVEL_INFO, "The CGI editor will be turned off after another %d mismatche(s).", acceptable_failures); acceptable_failures--; } else { log_error(LOG_LEVEL_INFO, "Timestamp mismatch limit reached, turning CGI editor off. " "Reload the configuration file to re-enable it."); csp->config->feature_flags &= ~RUNTIME_FEATURE_CGI_EDIT_ACTIONS; } } if (err == JB_ERR_OK) { /* * Signal to higher-level CGI code that there was a problem but we * handled it, they should just return JB_ERR_OK. */ err = JB_ERR_FILE; } return err; } err = edit_parse_actions_file(file); if (err) { if (err == JB_ERR_PARSE) { err = cgi_error_parse(csp, rsp, file); if (err == JB_ERR_OK) { /* * Signal to higher-level CGI code that there was a problem but we * handled it, they should just return JB_ERR_OK. */ err = JB_ERR_FILE; } } edit_free_file(file); return err; } *pfile = file; return JB_ERR_OK; } /********************************************************************* * * Function : get_file_name_param * * Description : Get the name of the file to edit from the parameters * passed to a CGI function using the old syntax. * This function handles security checks and only * accepts files that Privoxy already knows. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : parameters = map of cgi parameters * 3 : param_name = The name of the parameter to read * 4 : pfilename = pointer to the filename in * csp->config->actions_file[] if found. Set to NULL on error. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if "filename" was not specified * or is not valid. * *********************************************************************/ static jb_err get_file_name_param(struct client_state *csp, const struct map *parameters, const char *param_name, const char **pfilename) { const char *param; const char suffix[] = ".action"; const char *s; char *name; char *fullpath; char ch; size_t len; size_t name_size; int i; assert(csp); assert(parameters); assert(pfilename); *pfilename = NULL; param = lookup(parameters, param_name); if (!*param) { return JB_ERR_CGI_PARAMS; } len = strlen(param); if (len >= FILENAME_MAX) { /* Too long. */ return JB_ERR_CGI_PARAMS; } /* * Check every character to see if it's legal. * Totally unnecessary but we do it anyway. */ s = param; while ((ch = *s++) != '\0') { if ( ((ch < 'A') || (ch > 'Z')) && ((ch < 'a') || (ch > 'z')) && ((ch < '0') || (ch > '9')) && (ch != '-') && (ch != '_')) { /* Probable hack attempt. */ return JB_ERR_CGI_PARAMS; } } /* Append extension */ name_size = len + strlen(suffix) + 1; name = malloc_or_die(name_size); strlcpy(name, param, name_size); strlcat(name, suffix, name_size); /* Prepend path */ fullpath = make_path(csp->config->confdir, name); free(name); if (fullpath == NULL) { return JB_ERR_MEMORY; } /* Check if the file is known */ for (i = 0; i < MAX_AF_FILES; i++) { if (NULL != csp->config->actions_file[i] && !strcmp(fullpath, csp->config->actions_file[i])) { /* Success */ *pfilename = csp->config->actions_file[i]; freez(fullpath); return JB_ERR_OK; } } freez(fullpath); return JB_ERR_CGI_PARAMS; } /********************************************************************* * * Function : get_url_spec_param * * Description : Get a URL pattern from the parameters * passed to a CGI function. Removes leading/trailing * spaces and validates it. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : parameters = map of cgi parameters * 3 : name = Name of CGI parameter to read * 4 : pvalue = destination for value. Will be malloc()'d. * Set to NULL on error. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory * JB_ERR_CGI_PARAMS if the parameter was not specified * or is not valid. * *********************************************************************/ static jb_err get_url_spec_param(struct client_state *csp, const struct map *parameters, const char *name, char **pvalue) { const char *orig_param; char *param; char *s; struct pattern_spec compiled[1]; jb_err err; assert(csp); assert(parameters); assert(name); assert(pvalue); *pvalue = NULL; orig_param = lookup(parameters, name); if (!*orig_param) { return JB_ERR_CGI_PARAMS; } /* Copy and trim whitespace */ param = strdup(orig_param); if (param == NULL) { return JB_ERR_MEMORY; } chomp(param); /* Must be non-empty, and can't allow 1st character to be '{' */ if (param[0] == '\0' || param[0] == '{') { free(param); return JB_ERR_CGI_PARAMS; } /* Check for embedded newlines */ for (s = param; *s != '\0'; s++) { if ((*s == '\r') || (*s == '\n')) { free(param); return JB_ERR_CGI_PARAMS; } } /* Check that regex is valid */ s = strdup(param); if (s == NULL) { free(param); return JB_ERR_MEMORY; } err = create_pattern_spec(compiled, s); free(s); if (err) { free(param); return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS; } free_pattern_spec(compiled); if (param[strlen(param) - 1] == '\\') { /* * Must protect trailing '\\' from becoming line continuation character. * Two methods: 1) If it's a domain only, add a trailing '/'. * 2) For path, add the do-nothing PCRE expression (?:) to the end */ if (strchr(param, '/') == NULL) { err = string_append(¶m, "/"); } else { err = string_append(¶m, "(?:)"); } if (err) { return err; } /* Check that the modified regex is valid */ s = strdup(param); if (s == NULL) { free(param); return JB_ERR_MEMORY; } err = create_pattern_spec(compiled, s); free(s); if (err) { free(param); return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS; } free_pattern_spec(compiled); } *pvalue = param; return JB_ERR_OK; } /********************************************************************* * * Function : map_radio * * Description : Map a set of radio button values. E.g. if you have * 3 radio buttons, declare them as: *