From d6f6d40eb51a7a7f491c5c027f83bbd7f44c009f Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Thu, 17 Mar 2016 10:42:27 +0000 Subject: [PATCH] Implement client-specific tags ... which allow Privoxy admins to pre-define tags that are set for all requests from clients that previously opted-in through the CGI interface. They are useful in multi-user setups where admins may want to allow users to disable certain actions and filters for themselves without affecting others. In single-user setups they are useful to allow more fine-grained toggling. For example to disable request blocking while still crunching cookies, or to disable experimental filters only. This is an experimental feature, to enable it configure with --enable-client-tags. The syntax and behaviour may change in future versions. Implements TODO list item #144 and #145. Funded by a donation from Robert Klemme. --- GNUmakefile.in | 9 +- cgi.c | 8 +- cgisimple.c | 143 ++++++++- cgisimple.h | 7 +- client-tags.c | 587 +++++++++++++++++++++++++++++++++++++ client-tags.h | 43 +++ configure.in | 14 +- filters.c | 34 ++- filters.h | 5 +- jcc.c | 17 +- jcc.h | 6 +- loadcfg.c | 133 ++++++++- project.h | 26 +- templates/show-client-tags | 149 ++++++++++ templates/show-status | 7 + urlmatch.c | 5 +- 16 files changed, 1170 insertions(+), 23 deletions(-) create mode 100644 client-tags.c create mode 100644 client-tags.h create mode 100644 templates/show-client-tags diff --git a/GNUmakefile.in b/GNUmakefile.in index aabeeddb..9da003a9 100644 --- a/GNUmakefile.in +++ b/GNUmakefile.in @@ -1,6 +1,6 @@ # Note: Makefile is built automatically from Makefile.in # -# $Id: GNUmakefile.in,v 1.234 2016/02/02 13:08:03 fabiankeil Exp $ +# $Id: GNUmakefile.in,v 1.235 2016/02/13 11:18:02 fabiankeil Exp $ # # Written by and Copyright (C) 2001-2014 members of the # Privoxy team. http://www.privoxy.org/ @@ -190,6 +190,9 @@ C_SRC = actions.c cgi.c cgiedit.c cgisimple.c deanimate.c encode.c \ C_OBJS = $(C_SRC:.c=.@OBJEXT@) C_HDRS = $(C_SRC:.c=.h) project.h actionlist.h +CLIENT_TAG_SRC = @FEATURE_CLIENT_TAGS_ONLY@client-tags.c +CLIENT_TAG_OBJS = @FEATURE_CLIENT_TAGS_ONLY@client-tags.@OBJEXT@ + W32_SRC = @WIN_ONLY@w32log.c w32taskbar.c win32.c w32svrapi.c W32_FILES = @WIN_ONLY@w32.res W32_OBJS = @WIN_ONLY@$(W32_SRC:.c=.@OBJEXT@) $(W32_FILES) @@ -221,8 +224,8 @@ SOCKET_LIB = @SOCKET_LIB@ # PThreads library, if needed. PTHREAD_LIB = @PTHREAD_ONLY@@PTHREAD_LIB@ -SRCS = $(C_SRC) $(W32_SRC) $(PCRS_SRC) $(PCRE_SRC) $(REGEX_SRC) -OBJS = $(C_OBJS) $(W32_OBJS) $(PCRS_OBJS) $(PCRE_OBJS) $(REGEX_OBJS) +SRCS = $(C_SRC) $(CLIENT_TAG_SRC) $(W32_SRC) $(PCRS_SRC) $(PCRE_SRC) $(REGEX_SRC) +OBJS = $(C_OBJS) $(CLIENT_TAG_OBJS) $(W32_OBJS) $(PCRS_OBJS) $(PCRE_OBJS) $(REGEX_OBJS) HDRS = $(C_HDRS) $(W32_HDRS) $(PCRS_HDRS) $(PCRE_OBJS) $(REGEX_HDRS) LIBS = @LIBS@ $(W32_LIB) $(SOCKET_LIB) $(PTHREAD_LIB) diff --git a/cgi.c b/cgi.c index 114eae89..14abf8b0 100644 --- a/cgi.c +++ b/cgi.c @@ -1,4 +1,4 @@ -const char cgi_rcs[] = "$Id: cgi.c,v 1.160 2014/10/18 11:31:52 fabiankeil Exp $"; +const char cgi_rcs[] = "$Id: cgi.c,v 1.161 2016/02/26 12:32:26 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgi.c,v $ @@ -100,6 +100,12 @@ static const struct cgi_dispatcher cgi_dispatchers[] = { cgi_show_version, "View the source code version numbers", TRUE }, +#ifdef FEATURE_CLIENT_TAGS + { "show-client-tags", + cgi_show_client_tags, + "Show the tags that can be set based on the client's address (opt-in)", + FALSE }, +#endif { "show-request", cgi_show_request, "View the request headers", diff --git a/cgisimple.c b/cgisimple.c index ad449664..faa70da8 100644 --- a/cgisimple.c +++ b/cgisimple.c @@ -1,4 +1,4 @@ -const char cgisimple_rcs[] = "$Id: cgisimple.c,v 1.134 2016/02/26 12:32:09 fabiankeil Exp $"; +const char cgisimple_rcs[] = "$Id: cgisimple.c,v 1.135 2016/03/04 13:22:22 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgisimple.c,v $ @@ -60,6 +60,9 @@ const char cgisimple_rcs[] = "$Id: cgisimple.c,v 1.134 2016/02/26 12:32:09 fabia #include "parsers.h" #include "urlmatch.h" #include "errlog.h" +#ifdef FEATURE_CLIENT_TAGS +#include "client-tags.h" +#endif const char cgisimple_h_rcs[] = CGISIMPLE_H_VERSION; @@ -271,6 +274,136 @@ jb_err cgi_show_request(struct client_state *csp, } +#ifdef FEATURE_CLIENT_TAGS +/********************************************************************* + * + * Function : cgi_show_client_tags + * + * Description : Shows the tags that can be set based on the client + * address (opt-in). + * + * 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 : none + * + * Returns : JB_ERR_OK on success + * JB_ERR_MEMORY on out-of-memory error. + * + *********************************************************************/ +jb_err cgi_show_client_tags(struct client_state *csp, + struct http_response *rsp, + const struct map *parameters) +{ + struct map *exports; + struct client_tag_spec *this_tag; + jb_err err = JB_ERR_OK; + const char *toggled_tag; + const char *toggle_state; + const char *tag_expires; + time_t time_to_live; + char *client_tags = strdup_or_die(""); + char buf[1000]; + + assert(csp); + assert(rsp); + assert(parameters); + + if (NULL == (exports = default_exports(csp, "show-client-tags"))) + { + return JB_ERR_MEMORY; + } + + toggled_tag = lookup(parameters, "tag"); + if (*toggled_tag != '\0') + { + tag_expires = lookup(parameters, "expires"); + if (*tag_expires == '0') + { + time_to_live = 0; + } + else + { + time_to_live = csp->config->client_tag_lifetime; + } + toggle_state = lookup(parameters, "toggle-state"); + if (*toggle_state == '1') + { + enable_client_specific_tag(csp, toggled_tag, time_to_live); + } + else + { + disable_client_specific_tag(csp, toggled_tag); + } + } + + this_tag = csp->config->client_tags; + if (this_tag->name == NULL) + { + if (!err) err = string_append(&client_tags, "

No tags available.

\n"); + } + else + { + if (!err) + { + err = string_append(&client_tags, "\n" + "\n" + "\n"); + } + while ((this_tag != NULL) && (this_tag->name != NULL)) + { + int tag_state; + + privoxy_mutex_lock(&client_tags_mutex); + tag_state = client_has_requested_tag(csp->ip_addr_str, this_tag->name); + privoxy_mutex_unlock(&client_tags_mutex); + if (!err) err = string_append(&client_tags, "\n"); + if (err) + { + free_map(exports); + return JB_ERR_MEMORY; + } + this_tag = this_tag->next; + } + if (!err) err = string_append(&client_tags, "
Tag nameCurrent stateChange stateDescription
"); + if (!err) err = string_append(&client_tags, this_tag->name); + if (!err) err = string_append(&client_tags, ""); + if (!err) err = string_append(&client_tags, tag_state == 1 ? "Enabled" : "Disabled"); + snprintf(buf, sizeof(buf), + "%s", + this_tag->name, !tag_state, tag_state == 1 ? "Disable" : "Enable"); + if (!err) err = string_append(&client_tags, buf); + if (tag_state == 0) + { + snprintf(buf, sizeof(buf), ". Enable temporarily", + this_tag->name); + if (!err) err = string_append(&client_tags, buf); + } + if (!err) err = string_append(&client_tags, ""); + if (!err) err = string_append(&client_tags, this_tag->description); + if (!err) err = string_append(&client_tags, "
\n"); + } + + if (map(exports, "client-tags", 1, client_tags, 0)) + { + free_map(exports); + return JB_ERR_MEMORY; + } + + if (map(exports, "client-ip-addr", 1, csp->ip_addr_str, 1)) + { + free_map(exports); + return JB_ERR_MEMORY; + } + + return template_fill_for_cgi(csp, "show-client-tags", exports, rsp); +} +#endif /* def FEATURE_CLIENT_TAGS */ + + /********************************************************************* * * Function : cgi_send_banner @@ -1564,6 +1697,14 @@ static jb_err show_defines(struct map *exports) 1, #else 0, +#endif + }, + { + "FEATURE_CLIENT_TAGS", +#ifdef FEATURE_CLIENT_TAGS + 1, +#else + 0, #endif }, { diff --git a/cgisimple.h b/cgisimple.h index 55cafb68..3b66fc73 100644 --- a/cgisimple.h +++ b/cgisimple.h @@ -1,6 +1,6 @@ #ifndef CGISIMPLE_H_INCLUDED #define CGISIMPLE_H_INCLUDED -#define CGISIMPLE_H_VERSION "$Id: cgisimple.h,v 1.18 2011/09/04 11:10:56 fabiankeil Exp $" +#define CGISIMPLE_H_VERSION "$Id: cgisimple.h,v 1.19 2013/11/24 14:23:28 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgisimple.h,v $ @@ -68,6 +68,11 @@ extern jb_err cgi_show_version (struct client_state *csp, extern jb_err cgi_show_request (struct client_state *csp, struct http_response *rsp, const struct map *parameters); +#ifdef FEATURE_CLIENT_TAGS +extern jb_err cgi_show_client_tags(struct client_state *csp, + struct http_response *rsp, + const struct map *parameters); +#endif extern jb_err cgi_transparent_image (struct client_state *csp, struct http_response *rsp, const struct map *parameters); diff --git a/client-tags.c b/client-tags.c new file mode 100644 index 00000000..e04345c8 --- /dev/null +++ b/client-tags.c @@ -0,0 +1,587 @@ +/********************************************************************* + * + * File : $Source: /cvsroot/ijbswa/current/client-tags.c,v $ + * + * Purpose : Functions related to client-specific tags. + * + * Copyright : Copyright (C) 2016 Fabian Keil + * + * 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 "project.h" +#include "list.h" +#include "jcc.h" +#include "miscutil.h" +#include "errlog.h" + +struct client_specific_tag +{ + char *name; + + time_t end_of_life; + + struct client_specific_tag *next; + struct client_specific_tag *prev; +}; + +/** + * This struct represents tags that have been requested by clients + */ +struct requested_tags +{ + char *client; /**< The IP address of the client that requested the tag */ + + /**< List of tags the client requested .... */ + struct client_specific_tag *tags; + + struct requested_tags *next; + struct requested_tags *prev; +}; + +struct requested_tags *requested_tags; +static void remove_tag_for_client(const char *client_address, const char *tag); + +/********************************************************************* + * + * Function : validate_tag_list + * + * Description : Validates the given tag list + * + * Parameters : + * 1 : enabled_tags = The tags to validate + * + * Returns : void + * + *********************************************************************/ +static void validate_tag_list(struct client_specific_tag *enabled_tags) +{ + while (enabled_tags != NULL) + { + if (enabled_tags->name == NULL) + { + assert(enabled_tags->name != NULL); + log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Tag without name detected"); + } + if (enabled_tags->next != NULL) + { + if (enabled_tags->next->prev != enabled_tags) + { + assert(enabled_tags->next->prev == enabled_tags); + log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Invalid backlink detected"); + } + } + enabled_tags = enabled_tags->next; + } +} + +/********************************************************************* + * + * Function : validate_requested_tags + * + * Description : Validates the requested_tags list + * + * Parameters : N/A + * + * Returns : void + * + *********************************************************************/ +static jb_err validate_requested_tags() +{ + struct requested_tags *requested_tag; + + for (requested_tag = requested_tags; requested_tag != NULL; + requested_tag = requested_tag->next) + { + if (requested_tag->client == NULL) + { + assert(requested_tag->client != NULL); + log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Client not registered"); + } + validate_tag_list(requested_tag->tags); + if (requested_tag->next != NULL) + { + if (requested_tag->next->prev != requested_tag) + { + assert(requested_tag->next->prev == requested_tag); + log_error(LOG_LEVEL_FATAL, "validate_requested_tags(): Invalid backlink detected"); + } + } + } + + return TRUE; +} + + +/********************************************************************* + * + * Function : get_client_specific_tag + * + * Description : Returns the data for a client-specific-tag specified + * by name. + * + * Parameters : + * 1 : tag_list = The tag list to check + * 2 : name = The tag name to look up + * + * Returns : Pointer to tag structure or NULL on error. + * + *********************************************************************/ +static struct client_tag_spec *get_client_specific_tag( + struct client_tag_spec *tag_list, const char *name) +{ + struct client_tag_spec *tag; + + for (tag = tag_list; tag != NULL; tag = tag->next) + { + if (tag->name != NULL && !strcmp(tag->name, name)) + { + return tag; + } + } + + log_error(LOG_LEVEL_ERROR, "No such tag: '%s'", name); + + return NULL; + +} + + +/********************************************************************* + * + * Function : get_tags_for_client + * + * Description : Returns the list of tags the client opted-in. + * + * Parameters : + * 1 : client_address = Address of the client + * + * Returns : Pointer to tag structure or NULL on error. + * + *********************************************************************/ +static struct client_specific_tag *get_tags_for_client(const char *client_address) +{ + struct requested_tags *requested_tag; + + for (requested_tag = requested_tags; requested_tag != NULL; + requested_tag = requested_tag->next) + { + if (!strcmp(requested_tag->client, client_address)) + { + return requested_tag->tags; + } + } + + return NULL; +} + + +/********************************************************************* + * + * Function : get_tag_list_for_client + * + * Description : Provides a list of tag names the client opted-in. + * Other tag attributes are not part of the list. + * + * Parameters : + * 1 : tag_list = The list to fill in. + * 2 : client_address = Address of the client + * + * Returns : Pointer to tag list. + * + *********************************************************************/ +void get_tag_list_for_client(struct list *tag_list, + const char *client_address) +{ + struct client_specific_tag *enabled_tags; + const time_t now = time(NULL); + + privoxy_mutex_lock(&client_tags_mutex); + + enabled_tags = get_tags_for_client(client_address); + while (enabled_tags != NULL) + { + if (enabled_tags->end_of_life && (enabled_tags->end_of_life < now)) + { + struct client_specific_tag *next_tag = enabled_tags->next; + log_error(LOG_LEVEL_INFO, + "Tag '%s' for client %s expired %u seconds ago. Deleting it.", + enabled_tags->name, client_address, + (now - enabled_tags->end_of_life)); + remove_tag_for_client(client_address, enabled_tags->name); + enabled_tags = next_tag; + continue; + } + else + { + enlist(tag_list, enabled_tags->name); + } + enabled_tags = enabled_tags->next; + } + + privoxy_mutex_unlock(&client_tags_mutex); +} + + +/********************************************************************* + * + * Function : add_tag_for_client + * + * Description : Adds the tag for the client. + * + * Parameters : + * 1 : client_address = Address of the client + * 2 : tag = The tag to add. + * 3 : time_to_live = 0, or the number of seconds + * the tag remains activated. + * + * Returns : void + * + *********************************************************************/ +static void add_tag_for_client(const char *client_address, + const char *tag, const time_t time_to_live) +{ + struct requested_tags *clients_with_tags; + struct client_specific_tag *enabled_tags; + + validate_requested_tags(); + + if (requested_tags == NULL) + { + /* XXX: Code duplication. */ + requested_tags = zalloc_or_die(sizeof(struct requested_tags)); + requested_tags->client = strdup_or_die(client_address); + requested_tags->tags = zalloc_or_die(sizeof(struct client_specific_tag)); + requested_tags->tags->name = strdup_or_die(tag); + requested_tags->tags->end_of_life = time_to_live ? + (time(NULL) + time_to_live) : 0; + + validate_requested_tags(); + return; + } + else + { + clients_with_tags = requested_tags; + while (clients_with_tags->next != NULL) + { + if (!strcmp(clients_with_tags->client, client_address)) + { + break; + } + clients_with_tags = clients_with_tags->next; + } + if (strcmp(clients_with_tags->client, client_address)) + { + /* Client does not have tags yet, add new structure */ + clients_with_tags->next = zalloc_or_die(sizeof(struct requested_tags)); + clients_with_tags->next->prev = clients_with_tags; + clients_with_tags = clients_with_tags->next; + clients_with_tags->client = strdup_or_die(client_address); + clients_with_tags->tags = zalloc_or_die(sizeof(struct client_specific_tag)); + clients_with_tags->tags->name = strdup_or_die(tag); + clients_with_tags->tags->end_of_life = time_to_live ? + (time(NULL) + time_to_live) : 0; + + validate_requested_tags(); + + return; + } + } + + enabled_tags = clients_with_tags->tags; + while (enabled_tags != NULL) + { + if (enabled_tags->next == NULL) + { + enabled_tags->next = zalloc_or_die(sizeof(struct client_specific_tag)); + enabled_tags->next->name = strdup_or_die(tag); + clients_with_tags->tags->end_of_life = time_to_live ? + (time(NULL) + time_to_live) : 0; + enabled_tags->next->prev = enabled_tags; + break; + } + enabled_tags = enabled_tags->next; + } + + validate_requested_tags(); +} + + +/********************************************************************* + * + * Function : remove_tag_for_client + * + * Description : Removes the tag for the client. + * + * Parameters : + * 1 : client_address = Address of the client + * 2 : tag = The tag to remove. + * + * Returns : void + * + *********************************************************************/ +static void remove_tag_for_client(const char *client_address, const char *tag) +{ + struct requested_tags *clients_with_tags; + struct client_specific_tag *enabled_tags; + + validate_requested_tags(); + + clients_with_tags = requested_tags; + while (clients_with_tags != NULL && clients_with_tags->client != NULL) + { + if (!strcmp(clients_with_tags->client, client_address)) + { + break; + } + clients_with_tags = clients_with_tags->next; + } + + enabled_tags = clients_with_tags->tags; + while (enabled_tags != NULL) + { + if (!strcmp(enabled_tags->name, tag)) + { + if (enabled_tags->next != NULL) + { + enabled_tags->next->prev = enabled_tags->prev; + if (enabled_tags == clients_with_tags->tags) + { + /* Tag is first in line */ + clients_with_tags->tags = enabled_tags->next; + } + } + if (enabled_tags->prev != NULL) + { + /* Tag has preceding tag */ + enabled_tags->prev->next = enabled_tags->next; + } + if (enabled_tags->prev == NULL && enabled_tags->next == NULL) + { + /* Tag is the only one */ + if (clients_with_tags->next != NULL) + { + /* Client has following client */ + clients_with_tags->next->prev = clients_with_tags->prev; + } + if (clients_with_tags->prev != NULL) + { + /* Client has preceding client */ + clients_with_tags->prev->next = clients_with_tags->next; + } + freez(clients_with_tags->client); + if (clients_with_tags == requested_tags) + { + /* Removing last tag */ + freez(requested_tags); + clients_with_tags = requested_tags; + } + else + { + freez(clients_with_tags); + } + } + freez(enabled_tags->name); + freez(enabled_tags); + break; + } + + enabled_tags = enabled_tags->next; + } + + validate_requested_tags(); + +} + + +/********************************************************************* + * + * Function : client_has_requested_tag + * + * Description : Checks whether or not the given client requested + * the tag. + * + * Parameters : + * 1 : client_address = Address of the client + * 2 : tag = Tag to check. + * + * Returns : TRUE or FALSE. + * + *********************************************************************/ +int client_has_requested_tag(const char *client_address, const char *tag) +{ + struct client_specific_tag *enabled_tags; + + enabled_tags = get_tags_for_client(client_address); + + while (enabled_tags != NULL) + { + if (!strcmp(enabled_tags->name, tag)) + { + return TRUE; + } + enabled_tags = enabled_tags->next; + } + + return FALSE; + +} + +/********************************************************************* + * + * Function : enable_client_specific_tag + * + * Description : Enables a client-specific-tag for the client + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : tag_name = The name of the tag to enable + * 3 : time_to_live = If not 0, the number of seconds the + * tag should stay enabled. + * + * Returns : JB_ERR_OK on success, JB_ERR_MEMORY or JB_ERR_PARSE. + * + *********************************************************************/ +jb_err enable_client_specific_tag(struct client_state *csp, + const char *tag_name, const time_t time_to_live) +{ + struct client_tag_spec *tag; + + privoxy_mutex_lock(&client_tags_mutex); + + tag = get_client_specific_tag(csp->config->client_tags, tag_name); + if (tag == NULL) + { + privoxy_mutex_unlock(&client_tags_mutex); + return JB_ERR_PARSE; + } + + if (client_has_requested_tag(csp->ip_addr_str, tag_name)) + { + log_error(LOG_LEVEL_ERROR, + "Tag '%s' already enabled for client '%s'", tag->name, csp->ip_addr_str); + } + else + { + add_tag_for_client(csp->ip_addr_str, tag_name, time_to_live); + log_error(LOG_LEVEL_INFO, + "Tag '%s' enabled for client '%s'", tag->name, csp->ip_addr_str); + } + + privoxy_mutex_unlock(&client_tags_mutex); + + return JB_ERR_OK; + +} + +/********************************************************************* + * + * Function : disable_client_specific_tag + * + * Description : Disables a client-specific-tag for the client + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : tag_name = The name of the tag to disable + * + * Returns : JB_ERR_OK on success, JB_ERR_MEMORY or JB_ERR_PARSE. + * + *********************************************************************/ +jb_err disable_client_specific_tag(struct client_state *csp, const char *tag_name) +{ + struct client_tag_spec *tag; + + privoxy_mutex_lock(&client_tags_mutex); + + tag = get_client_specific_tag(csp->config->client_tags, tag_name); + if (tag == NULL) + { + privoxy_mutex_unlock(&client_tags_mutex); + return JB_ERR_PARSE; + } + + if (client_has_requested_tag(csp->ip_addr_str, tag_name)) + { + remove_tag_for_client(csp->ip_addr_str, tag_name); + log_error(LOG_LEVEL_INFO, + "Tag '%s' disabled for client '%s'", tag->name, csp->ip_addr_str); + } + else + { + log_error(LOG_LEVEL_ERROR, + "Tag '%s' currently not set for client '%s'", + tag->name, csp->ip_addr_str); + } + + privoxy_mutex_unlock(&client_tags_mutex); + return JB_ERR_OK; + +} + + +/********************************************************************* + * + * Function : client_tag_match + * + * Description : Compare a client tag against a client tag pattern. + * + * Parameters : + * 1 : pattern = a TAG pattern + * 2 : tag = Client tag to match + * + * Returns : Nonzero if the tag matches the pattern, else 0. + * + *********************************************************************/ +int client_tag_match(const struct pattern_spec *pattern, + const struct list *tags) +{ + struct list_entry *tag; + + if (!(pattern->flags & PATTERN_SPEC_CLIENT_TAG_PATTERN)) + { + /* + * It's not a client pattern and thus shouldn't + * be matched against client tags. + */ + return 0; + } + + assert(tags); + + for (tag = tags->first; tag != NULL; tag = tag->next) + { + if (0 == regexec(pattern->pattern.tag_regex, tag->str, 0, NULL, 0)) + { + return 1; + } + } + + return 0; + +} diff --git a/client-tags.h b/client-tags.h new file mode 100644 index 00000000..ac6ef0d8 --- /dev/null +++ b/client-tags.h @@ -0,0 +1,43 @@ +#ifndef CLIENT_TAGS_H_INCLUDED +#define CLIENT_TAGS_H_INCLUDED +#define CLIENT_TAGS_H_VERSION "$Id:$" +/********************************************************************* + * + * File : $Source: $ + * + * Purpose : Declares functions for client-specific tags. + * + * Copyright : Copyright (C) 2016 Fabian Keil + * + * 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. + * + *********************************************************************/ + +extern int client_tag_match(const struct pattern_spec *pattern, + const struct list *tags); +extern void get_tag_list_for_client(struct list *tag_list, + const char *client_address); +extern jb_err disable_client_specific_tag(struct client_state *csp, + const char *tag_name); +extern jb_err enable_client_specific_tag(struct client_state *csp, + const char *tag_name, + const time_t time_to_live); +extern int client_has_requested_tag(const char *client_address, + const char *tag); +#endif diff --git a/configure.in b/configure.in index 61152d5a..ec670d02 100644 --- a/configure.in +++ b/configure.in @@ -1,6 +1,6 @@ dnl Process this file with autoconf to produce a configure script. dnl -dnl $Id: configure.in,v 1.190 2016/01/16 12:33:16 fabiankeil Exp $ +dnl $Id: configure.in,v 1.191 2016/02/02 13:08:17 fabiankeil Exp $ dnl dnl Written by and Copyright (C) 2001-2014 the dnl Privoxy team. http://www.privoxy.org/ @@ -32,7 +32,7 @@ dnl ================================================================= dnl AutoConf Initialization dnl ================================================================= -AC_REVISION($Revision: 1.190 $) +AC_REVISION($Revision: 1.191 $) AC_INIT(jcc.c) if test ! -f config.h.in; then @@ -982,6 +982,16 @@ AC_ARG_ENABLE(strptime-sanity-checks, AC_DEFINE(FEATURE_STRPTIME_SANITY_CHECKS) fi]) +AC_ARG_ENABLE(client-tags, +[ --enable-client-tags Enable client-specific tags], +[if test $enableval = yes; then + FEATURE_CLIENT_TAGS_ONLY="" + AC_DEFINE(FEATURE_CLIENT_TAGS,1,[Define to enable client-specific tags.]) + else + FEATURE_CLIENT_TAGS_ONLY="#" +fi]) +AC_SUBST(FEATURE_CLIENT_TAGS_ONLY) + dnl pcre/pcrs is needed for CGI anyway, so dnl the choice is only between static and dnl dynamic: diff --git a/filters.c b/filters.c index 8a83bbca..c3aa3fd3 100644 --- a/filters.c +++ b/filters.c @@ -1,4 +1,4 @@ -const char filters_rcs[] = "$Id: filters.c,v 1.199 2016/01/16 12:33:35 fabiankeil Exp $"; +const char filters_rcs[] = "$Id: filters.c,v 1.200 2016/02/26 12:29:38 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/filters.c,v $ @@ -70,6 +70,9 @@ const char filters_rcs[] = "$Id: filters.c,v 1.199 2016/01/16 12:33:35 fabiankei #include "deanimate.h" #include "urlmatch.h" #include "loaders.h" +#ifdef FEATURE_CLIENT_TAGS +#include "client-tags.h" +#endif #ifdef _WIN32 #include "win32.h" @@ -81,6 +84,12 @@ typedef char *(*filter_function_ptr)(); static filter_function_ptr get_filter_function(const struct client_state *csp); static jb_err remove_chunked_transfer_coding(char *buffer, size_t *size); static jb_err prepare_for_filtering(struct client_state *csp); +static void apply_url_actions(struct current_action_spec *action, + struct http_request *http, +#ifdef FEATURE_CLIENT_TAGS + const struct list *client_tags, +#endif + struct url_actions *b); #ifdef FEATURE_ACL #ifdef HAVE_RFC2553 @@ -2322,13 +2331,16 @@ void get_url_actions(struct client_state *csp, struct http_request *http) return; } +#ifdef FEATURE_CLIENT_TAGS + apply_url_actions(csp->action, http, csp->client_tags, b); +#else apply_url_actions(csp->action, http, b); +#endif } return; } - /********************************************************************* * * Function : apply_url_actions @@ -2338,14 +2350,18 @@ void get_url_actions(struct client_state *csp, struct http_request *http) * Parameters : * 1 : action = Destination. * 2 : http = Current URL - * 3 : b = list of URL actions to apply + * 3 : client_tags = list of client tags + * 4 : b = list of URL actions to apply * * Returns : N/A * *********************************************************************/ -void apply_url_actions(struct current_action_spec *action, - struct http_request *http, - struct url_actions *b) +static void apply_url_actions(struct current_action_spec *action, + struct http_request *http, +#ifdef FEATURE_CLIENT_TAGS + const struct list *client_tags, +#endif + struct url_actions *b) { if (b == NULL) { @@ -2359,6 +2375,12 @@ void apply_url_actions(struct current_action_spec *action, { merge_current_action(action, b->action); } +#ifdef FEATURE_CLIENT_TAGS + if (client_tag_match(b->url, client_tags)) + { + merge_current_action(action, b->action); + } +#endif } } diff --git a/filters.h b/filters.h index ec1663f8..802a3e1d 100644 --- a/filters.h +++ b/filters.h @@ -1,6 +1,6 @@ #ifndef FILTERS_H_INCLUDED #define FILTERS_H_INCLUDED -#define FILTERS_H_VERSION "$Id: filters.h,v 1.45 2013/11/24 14:23:28 fabiankeil Exp $" +#define FILTERS_H_VERSION "$Id: filters.h,v 1.46 2013/12/24 13:32:51 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/filters.h,v $ @@ -75,9 +75,6 @@ extern int connect_port_is_forbidden(const struct client_state *csp); */ extern void get_url_actions(struct client_state *csp, struct http_request *http); -extern void apply_url_actions(struct current_action_spec *action, - struct http_request *http, - struct url_actions *b); extern struct re_filterfile_spec *get_filter(const struct client_state *csp, const char *requested_name, diff --git a/jcc.c b/jcc.c index e0ca39b5..8ab7f872 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.440 2016/01/16 12:33:36 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.441 2016/02/26 12:29:38 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -115,6 +115,9 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.440 2016/01/16 12:33:36 fabiankeil Exp $" #include "cgi.h" #include "loadcfg.h" #include "urlmatch.h" +#ifdef FEATURE_CLIENT_TAGS +#include "client-tags.h" +#endif const char jcc_h_rcs[] = JCC_H_VERSION; const char project_h_rcs[] = PROJECT_H_VERSION; @@ -185,6 +188,9 @@ privoxy_mutex_t connection_reuse_mutex; #ifdef FEATURE_EXTERNAL_FILTERS privoxy_mutex_t external_filter_mutex; #endif +#ifdef FEATURE_CLIENT_TAGS +privoxy_mutex_t client_tags_mutex; +#endif #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) privoxy_mutex_t resolver_mutex; @@ -1842,6 +1848,9 @@ static void chat(struct client_state *csp) http = csp->http; +#if FEATURE_CLIENT_TAGS + get_tag_list_for_client(csp->client_tags, csp->ip_addr_str); +#endif if (receive_client_request(csp) != JB_ERR_OK) { return; @@ -2813,6 +2822,9 @@ static void prepare_csp_for_next_request(struct client_state *csp) free_http_request(csp->http); destroy_list(csp->headers); destroy_list(csp->tags); +#ifdef FEATURE_CLIENT_TAGS + destroy_list(csp->client_tags); +#endif free_current_action(csp->action); if (NULL != csp->fwd) { @@ -3246,6 +3258,9 @@ static void initialize_mutexes(void) #ifdef FEATURE_EXTERNAL_FILTERS privoxy_mutex_init(&external_filter_mutex); #endif +#ifdef FEATURE_CLIENT_TAGS + privoxy_mutex_init(&client_tags_mutex); +#endif /* * XXX: The assumptions below are a bit naive diff --git a/jcc.h b/jcc.h index 57e68f77..43b765fb 100644 --- a/jcc.h +++ b/jcc.h @@ -1,6 +1,6 @@ #ifndef JCC_H_INCLUDED #define JCC_H_INCLUDED -#define JCC_H_VERSION "$Id: jcc.h,v 1.34 2014/06/02 06:19:06 fabiankeil Exp $" +#define JCC_H_VERSION "$Id: jcc.h,v 1.35 2014/06/02 06:22:21 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.h,v $ @@ -83,6 +83,10 @@ extern privoxy_mutex_t connection_reuse_mutex; extern privoxy_mutex_t external_filter_mutex; #endif +#ifdef FEATURE_CLIENT_TAGS +extern privoxy_mutex_t client_tags_mutex; +#endif + #ifndef HAVE_GMTIME_R extern privoxy_mutex_t gmtime_mutex; #endif /* ndef HAVE_GMTIME_R */ diff --git a/loadcfg.c b/loadcfg.c index fa79ba13..c73928d1 100644 --- a/loadcfg.c +++ b/loadcfg.c @@ -1,4 +1,4 @@ -const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.146 2016/02/26 12:29:38 fabiankeil Exp $"; +const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.147 2016/02/26 12:30:59 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/loadcfg.c,v $ @@ -124,6 +124,8 @@ static struct file_list *current_configfile = NULL; #define hash_allow_cgi_request_crunching 258915987U /* "allow-cgi-request-crunching" */ #define hash_buffer_limit 1881726070U /* "buffer-limit */ #define hash_client_header_order 2701453514U /* "client-header-order" */ +#define hash_client_specific_tag 3353703383U /* "client-specific-tag" */ +#define hash_client_tag_lifetime 647957580U /* "client-tag-lifetime" */ #define hash_compression_level 2464423563U /* "compression-level" */ #define hash_confdir 1978389U /* "confdir" */ #define hash_connection_sharing 1348841265U /* "connection-sharing" */ @@ -176,6 +178,9 @@ static struct file_list *current_configfile = NULL; static void savearg(char *command, char *argument, struct configuration_spec * config); +#ifdef FEATURE_CLIENT_TAGS +static void free_client_specific_tags(struct client_tag_spec *tag_list); +#endif /********************************************************************* * @@ -253,6 +258,10 @@ static void unload_configfile (void * data) list_remove_all(config->trust_info); #endif /* def FEATURE_TRUST */ +#ifdef FEATURE_CLIENT_TAGS + free_client_specific_tags(config->client_tags); +#endif + freez(config); } @@ -281,6 +290,85 @@ void unload_current_config_file(void) #endif +#ifdef FEATURE_CLIENT_TAGS +/********************************************************************* + * + * Function : register_tag + * + * Description : Registers a client-specific-tag and its description + * + * Parameters : + * 1 : config: The tag list + * 2 : name: The name of the client-specific-tag + * 3 : description: The human-readable description for the tag + * + * Returns : N/A + * + *********************************************************************/ +static void register_tag(struct client_tag_spec *tag_list, + const char *name, const char *description) +{ + struct client_tag_spec *new_tag; + struct client_tag_spec *last_tag; + + last_tag = tag_list; + while (last_tag->next != NULL) + { + last_tag = last_tag->next; + } + if (last_tag->name == NULL) + { + /* First entry */ + new_tag = last_tag; + } + else + { + new_tag = zalloc_or_die(sizeof(struct client_tag_spec)); + } + new_tag->name = strdup_or_die(name); + new_tag->description = strdup_or_die(description); + if (new_tag != last_tag) + { + last_tag->next = new_tag; + } +} + + +/********************************************************************* + * + * Function : free_client_specific_tags + * + * Description : Frees client-specific tags and their descriptions + * + * Parameters : + * 1 : tag_list: The tag list to free + * + * Returns : N/A + * + *********************************************************************/ +static void free_client_specific_tags(struct client_tag_spec *tag_list) +{ + struct client_tag_spec *this_tag; + struct client_tag_spec *next_tag; + + next_tag = tag_list; + do + { + this_tag = next_tag; + next_tag = next_tag->next; + + freez(this_tag->name); + freez(this_tag->description); + + if (this_tag != tag_list) + { + freez(this_tag); + } + } while (next_tag != NULL); +} +#endif /* def FEATURE_CLIENT_TAGS */ + + /********************************************************************* * * Function : parse_numeric_value @@ -667,6 +755,49 @@ struct configuration_spec * load_config(void) parse_client_header_order(config->ordered_client_headers, arg); break; +/* ************************************************************************* + * client-specific-tag tag-name description + * *************************************************************************/ +#ifdef FEATURE_CLIENT_TAGS + case hash_client_specific_tag: + { + char *name; + char *description; + + name = arg; + description = strstr(arg, " "); + if (description == NULL) + { + log_error(LOG_LEVEL_FATAL, + "client-specific-tag '%s' lacks a description.", name); + } + *description = '\0'; + description++; + register_tag(config->client_tags, name, description); + } + break; +#endif /* def FEATURE_CLIENT_TAGS */ + +/* ************************************************************************* + * client-tag-lifetime ttl + * *************************************************************************/ +#ifdef FEATURE_CLIENT_TAGS + case hash_client_tag_lifetime: + { + int ttl = parse_numeric_value(cmd, arg); + if (0 <= ttl) + { + config->client_tag_lifetime = (unsigned)ttl; + } + else + { + log_error(LOG_LEVEL_FATAL, + "client-tag-lifetime can't be negative."); + } + break; + } +#endif /* def FEATURE_CLIENT_TAGS */ + /* ************************************************************************* * confdir directory-name * *************************************************************************/ diff --git a/project.h b/project.h index 9b548792..f3aa09cc 100644 --- a/project.h +++ b/project.h @@ -1,7 +1,7 @@ #ifndef PROJECT_H_INCLUDED #define PROJECT_H_INCLUDED /** Version string. */ -#define PROJECT_H_VERSION "$Id: project.h,v 1.211 2016/01/16 12:30:28 fabiankeil Exp $" +#define PROJECT_H_VERSION "$Id: project.h,v 1.212 2016/01/16 12:30:43 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/project.h,v $ @@ -400,6 +400,9 @@ struct pattern_spec /** Pattern spec bitmap: It's a NO-RESPONSE-TAG pattern. */ #define PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN 0x00000008UL +/** Pattern spec bitmap: It's a CLIENT-TAG pattern. */ +#define PATTERN_SPEC_CLIENT_TAG_PATTERN 0x00000010UL + /** * An I/O buffer. Holds a string which can be appended to, and can have data * removed from the beginning. @@ -947,6 +950,11 @@ struct client_state /** List of all tags that apply to this request */ struct list tags[1]; +#ifdef FEATURE_CLIENT_TAGS + /** List of all tags that apply to this client (assigned based on address) */ + struct list client_tags[1]; +#endif + /** MIME-Type key, see CT_* above */ unsigned int content_type; @@ -1202,6 +1210,15 @@ struct access_control_list /** Maximum number of loaders (actions, re_filter, ...) */ #define NLOADERS 8 +/** + * This struct represents a client-spcific-tag and it's description + */ +struct client_tag_spec +{ + char *name; /**< Name from "client-specific-tag bla" directive */ + char *description; /**< Description from "client-specific-tag-description " directive */ + struct client_tag_spec *next; /**< The pointer for chaining. */ +}; /** configuration_spec::feature_flags: CGI actions editor. */ #define RUNTIME_FEATURE_CGI_EDIT_ACTIONS 1U @@ -1324,6 +1341,13 @@ struct configuration_spec #endif /* def FEATURE_TRUST */ +#ifdef FEATURE_CLIENT_TAGS + struct client_tag_spec client_tags[1]; + + /* Maximum number of seconds a temporarily enabled tag stays enabled. */ + unsigned int client_tag_lifetime; +#endif /* def FEATURE_CLIENT_TAGS */ + #ifdef FEATURE_ACL /** The access control list (ACL). */ diff --git a/templates/show-client-tags b/templates/show-client-tags new file mode 100644 index 00000000..d4482187 --- /dev/null +++ b/templates/show-client-tags @@ -0,0 +1,149 @@ +########################################################## +# +# Show-Request-CGI Output template for Privoxy. +# +# +# USING HTML TEMPLATES: +# --------------------- +# +# Template files are written win plain HTML, with a few +# additions: +# +# - Lines that start with a '#' character like this one +# are ignored +# +# - Each item in the below list of exported symbols will +# be replaced by dynamically generated text, if they +# are enclosed in '@'-characters. E.g. The string @version@ +# will be replaced by the version number of Privoxy. +# +# - One special application of this is to make whole blocks +# of the HTML template disappear if the condition +# is not given. Simply enclose the block between the two +# strings @if-start and if--end@. The strings +# should be placed in HTML comments (), so the +# html structure won't be messed when the magic happens. +# +# USABLE SYMBOLS IN THIS TEMPLATE: +# -------------------------------- +# +# my-ip-addr: +# The IP-address that the client used to reach this proxy +# my-hostname: +# The hostname associated with my-ip-addr +# admin-address: +# The email address of the pxoxy's administrator, as configured +# in the config file +# default-cgi: +# The URL for the "main menu" builtin CGI of this proxy +# menu: +# List of
  • elements linking to the other available CGIs +# version: +# The version number of the proxy software +# code-status: +# The development status of the proxy software: "alpha", "beta", +# or "stable". +# homepage: +# The URL of the SourceForge ijbswa project, who maintains this +# software. +# client-request: +# The request and headers that the client sent. +# processed-request: +# What we would have rewritten this request to, if this had not +# been intercepted. +# +# CONDITIONAL SYMBOLS FOR THIS TEMPLATE AND THEIR DEPANDANT SYMBOLS: +# ------------------------------------------------------------------ +# +# unstable: +# this is an alpha or beta release of the proxy software +# have-adminaddr-info: +# An e-mail address for the local Privoxy administrator has +# been specified and is available through the "admin-address" +# symbol +# have-proxy-info: +# A URL for online documentation about this proxy has been +# specified and is available through the "proxy-info-url" +# symbol +# have-help-info: +# If either have-proxy-info is true or have-adminaddr-info is +# true, have-help-info is true. Used to conditionally include +# a grey box for any and all help info. +# + + + + + Privoxy@@my-hostname@ + + + + + + + + + + + + + + + + +# This will only appear if CODE_STATUS is "alpha" or "beta". See configure.in + + + + + + + + + + + + + + + + + + + + + + + +
    + +#include mod-title + +
    + +#include mod-unstable-warning + +
    +

    Show client tags

    +

    + Here you see the client tags that are or can be optionally set based for + client with address @client-ip-addr@: +

    + + @client-tags@ + +
    +

    More Privoxy:

    + +
    + +#include mod-support-and-service + +
    + +#include mod-local-help + +
    + + + diff --git a/templates/show-status b/templates/show-status index e0024ede..78349aee 100644 --- a/templates/show-status +++ b/templates/show-status @@ -241,6 +241,13 @@ @endif-FEATURE_CGI_EDIT_ACTIONS@web-based actions file editor@if-FEATURE_CGI_EDIT_ACTIONS-then@@else-not-FEATURE_CGI_EDIT_ACTIONS@@endif-FEATURE_CGI_EDIT_ACTIONS@. + + FEATURE_CLIENT_TAGS + @if-FEATURE_CLIENT_TAGS-then@ Yes @else-not-FEATURE_CLIENT_TAGS@ No @endif-FEATURE_CLIENT_TAGS@ + + Allows clients to request to be tagged. + + FEATURE_COMPRESSION @if-FEATURE_COMPRESSION-then@ Yes @else-not-FEATURE_COMPRESSION@ No @endif-FEATURE_COMPRESSION@ diff --git a/urlmatch.c b/urlmatch.c index 418a949a..4839c2b1 100644 --- a/urlmatch.c +++ b/urlmatch.c @@ -1,4 +1,4 @@ -const char urlmatch_rcs[] = "$Id: urlmatch.c,v 1.86 2015/12/27 12:47:17 fabiankeil Exp $"; +const char urlmatch_rcs[] = "$Id: urlmatch.c,v 1.87 2016/02/26 12:29:39 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/urlmatch.c,v $ @@ -1161,6 +1161,9 @@ jb_err create_pattern_spec(struct pattern_spec *pattern, char *buf) const unsigned flag; } tag_pattern[] = { { "TAG:", 4, PATTERN_SPEC_TAG_PATTERN}, + #ifdef FEATURE_CLIENT_TAGS + { "CLIENT-TAG:", 11, PATTERN_SPEC_CLIENT_TAG_PATTERN}, + #endif { "NO-REQUEST-TAG:", 15, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN}, { "NO-RESPONSE-TAG:", 16, PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN} }; -- 2.39.2