Mention the new mailing lists were appropriate
[privoxy.git] / client-tags.c
1 /*********************************************************************
2  *
3  * File        :  $Source: /cvsroot/ijbswa/current/client-tags.c,v $
4  *
5  * Purpose     :  Functions related to client-specific tags.
6  *
7  * Copyright   :  Copyright (C) 2016 Fabian Keil <fk@fabiankeil.de>
8  *
9  *                This program is free software; you can redistribute it
10  *                and/or modify it under the terms of the GNU General
11  *                Public License as published by the Free Software
12  *                Foundation; either version 2 of the License, or (at
13  *                your option) any later version.
14  *
15  *                This program is distributed in the hope that it will
16  *                be useful, but WITHOUT ANY WARRANTY; without even the
17  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
18  *                PARTICULAR PURPOSE.  See the GNU General Public
19  *                License for more details.
20  *
21  *                The GNU General Public License should be included with
22  *                this file.  If not, you can view it at
23  *                http://www.gnu.org/copyleft/gpl.html
24  *                or write to the Free Software Foundation, Inc., 59
25  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26  *
27  **********************************************************************/
28
29 #include "config.h"
30
31 #ifdef FEATURE_CLIENT_TAGS
32
33 #include <stdio.h>
34 #include <sys/types.h>
35 #include <stdlib.h>
36 #include <ctype.h>
37 #include <string.h>
38 #include <assert.h>
39
40 #include "project.h"
41 #include "list.h"
42 #include "jcc.h"
43 #include "miscutil.h"
44 #include "errlog.h"
45
46 struct client_specific_tag
47 {
48    char *name;
49
50    time_t end_of_life;
51
52    struct client_specific_tag *next;
53    struct client_specific_tag *prev;
54 };
55
56 /**
57  * This struct represents tags that have been requested by clients
58  */
59 struct requested_tags
60 {
61    char *client; /**< The IP address of the client that requested the tag */
62
63    /**< List of tags the client requested .... */
64    struct client_specific_tag *tags;
65
66    struct requested_tags *next;
67    struct requested_tags *prev;
68 };
69
70 struct requested_tags *requested_tags;
71 static void remove_tag_for_client(const char *client_address, const char *tag);
72
73 /*********************************************************************
74  *
75  * Function    :  validate_tag_list
76  *
77  * Description :  Validates the given tag list
78  *
79  * Parameters  :
80  *          1  :  enabled_tags = The tags to validate
81  *
82  * Returns     :  void
83  *
84  *********************************************************************/
85 static void validate_tag_list(struct client_specific_tag *enabled_tags)
86 {
87    while (enabled_tags != NULL)
88    {
89       if (enabled_tags->name == NULL)
90       {
91          assert(enabled_tags->name != NULL);
92          log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Tag without name detected");
93       }
94       if (enabled_tags->next != NULL)
95       {
96          if (enabled_tags->next->prev != enabled_tags)
97          {
98             assert(enabled_tags->next->prev == enabled_tags);
99             log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Invalid backlink detected");
100          }
101       }
102       enabled_tags = enabled_tags->next;
103    }
104 }
105
106 /*********************************************************************
107  *
108  * Function    :  validate_requested_tags
109  *
110  * Description :  Validates the requested_tags list
111  *
112  * Parameters  : N/A
113  *
114  * Returns     :  void
115  *
116  *********************************************************************/
117 static jb_err validate_requested_tags()
118 {
119    struct requested_tags *requested_tag;
120
121    for (requested_tag = requested_tags; requested_tag != NULL;
122         requested_tag = requested_tag->next)
123    {
124       if (requested_tag->client == NULL)
125       {
126          assert(requested_tag->client != NULL);
127          log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Client not registered");
128       }
129       validate_tag_list(requested_tag->tags);
130       if (requested_tag->next != NULL)
131       {
132          if (requested_tag->next->prev != requested_tag)
133          {
134             assert(requested_tag->next->prev == requested_tag);
135             log_error(LOG_LEVEL_FATAL, "validate_requested_tags(): Invalid backlink detected");
136          }
137       }
138    }
139
140    return TRUE;
141 }
142
143
144 /*********************************************************************
145  *
146  * Function    :  get_client_specific_tag
147  *
148  * Description :  Returns the data for a client-specific-tag specified
149  *                by name.
150  *
151  * Parameters  :
152  *          1  :  tag_list = The tag list to check
153  *          2  :  name =     The tag name to look up
154  *
155  * Returns     :  Pointer to tag structure or NULL on error.
156  *
157  *********************************************************************/
158 static struct client_tag_spec *get_client_specific_tag(
159    struct client_tag_spec *tag_list, const char *name)
160 {
161    struct client_tag_spec *tag;
162
163    for (tag = tag_list; tag != NULL; tag = tag->next)
164    {
165       if (tag->name != NULL && !strcmp(tag->name, name))
166       {
167          return tag;
168       }
169    }
170
171    log_error(LOG_LEVEL_ERROR, "No such tag: '%s'", name);
172
173    return NULL;
174
175 }
176
177
178 /*********************************************************************
179  *
180  * Function    :  get_tags_for_client
181  *
182  * Description :  Returns the list of tags the client opted-in.
183  *
184  * Parameters  :
185  *          1  :  client_address = Address of the client
186  *
187  * Returns     :  Pointer to tag structure or NULL on error.
188  *
189  *********************************************************************/
190 static struct client_specific_tag *get_tags_for_client(const char *client_address)
191 {
192    struct requested_tags *requested_tag;
193
194    for (requested_tag = requested_tags; requested_tag != NULL;
195         requested_tag = requested_tag->next)
196    {
197       if (!strcmp(requested_tag->client, client_address))
198       {
199          return requested_tag->tags;
200       }
201    }
202
203    return NULL;
204 }
205
206
207 /*********************************************************************
208  *
209  * Function    :  get_tag_list_for_client
210  *
211  * Description :  Provides a list of tag names the client opted-in.
212  *                Other tag attributes are not part of the list.
213  *
214  * Parameters  :
215  *          1  :  tag_list = The list to fill in.
216  *          2  :  client_address = Address of the client
217  *
218  * Returns     :  Pointer to tag list.
219  *
220  *********************************************************************/
221 void get_tag_list_for_client(struct list *tag_list,
222                              const char *client_address)
223 {
224    struct client_specific_tag *enabled_tags;
225    const time_t now = time(NULL);
226
227    privoxy_mutex_lock(&client_tags_mutex);
228
229    enabled_tags = get_tags_for_client(client_address);
230    while (enabled_tags != NULL)
231    {
232       if (enabled_tags->end_of_life && (enabled_tags->end_of_life < now))
233       {
234          struct client_specific_tag *next_tag = enabled_tags->next;
235          log_error(LOG_LEVEL_INFO,
236             "Tag '%s' for client %s expired %u seconds ago. Deleting it.",
237             enabled_tags->name, client_address,
238             (now - enabled_tags->end_of_life));
239          remove_tag_for_client(client_address, enabled_tags->name);
240          enabled_tags = next_tag;
241          continue;
242       }
243       else
244       {
245          enlist(tag_list, enabled_tags->name);
246       }
247       enabled_tags = enabled_tags->next;
248    }
249
250    privoxy_mutex_unlock(&client_tags_mutex);
251 }
252
253
254 /*********************************************************************
255  *
256  * Function    :  add_tag_for_client
257  *
258  * Description :  Adds the tag for the client.
259  *
260  * Parameters  :
261  *          1  :  client_address = Address of the client
262  *          2  :  tag = The tag to add.
263  *          3  :  time_to_live = 0, or the number of seconds
264  *                               the tag remains activated.
265  *
266  * Returns     :  void
267  *
268  *********************************************************************/
269 static void add_tag_for_client(const char *client_address,
270    const char *tag, const time_t time_to_live)
271 {
272    struct requested_tags *clients_with_tags;
273    struct client_specific_tag *enabled_tags;
274
275    validate_requested_tags();
276
277    if (requested_tags == NULL)
278    {
279       /* XXX: Code duplication. */
280       requested_tags = zalloc_or_die(sizeof(struct requested_tags));
281       requested_tags->client = strdup_or_die(client_address);
282       requested_tags->tags = zalloc_or_die(sizeof(struct client_specific_tag));
283       requested_tags->tags->name = strdup_or_die(tag);
284       requested_tags->tags->end_of_life = time_to_live ?
285          (time(NULL) + time_to_live) : 0;
286
287       validate_requested_tags();
288       return;
289    }
290    else
291    {
292       clients_with_tags = requested_tags;
293       while (clients_with_tags->next != NULL)
294       {
295          if (!strcmp(clients_with_tags->client, client_address))
296          {
297             break;
298          }
299          clients_with_tags = clients_with_tags->next;
300       }
301       if (strcmp(clients_with_tags->client, client_address))
302       {
303          /* Client does not have tags yet, add new structure */
304          clients_with_tags->next = zalloc_or_die(sizeof(struct requested_tags));
305          clients_with_tags->next->prev = clients_with_tags;
306          clients_with_tags = clients_with_tags->next;
307          clients_with_tags->client = strdup_or_die(client_address);
308          clients_with_tags->tags = zalloc_or_die(sizeof(struct client_specific_tag));
309          clients_with_tags->tags->name = strdup_or_die(tag);
310          clients_with_tags->tags->end_of_life = time_to_live ?
311             (time(NULL) + time_to_live) : 0;
312
313          validate_requested_tags();
314
315          return;
316       }
317    }
318
319    enabled_tags = clients_with_tags->tags;
320    while (enabled_tags != NULL)
321    {
322       if (enabled_tags->next == NULL)
323       {
324          enabled_tags->next = zalloc_or_die(sizeof(struct client_specific_tag));
325          enabled_tags->next->name = strdup_or_die(tag);
326          clients_with_tags->tags->end_of_life = time_to_live ?
327             (time(NULL) + time_to_live) : 0;
328          enabled_tags->next->prev = enabled_tags;
329          break;
330       }
331       enabled_tags = enabled_tags->next;
332    }
333
334    validate_requested_tags();
335 }
336
337
338 /*********************************************************************
339  *
340  * Function    :  remove_tag_for_client
341  *
342  * Description :  Removes the tag for the client.
343  *
344  * Parameters  :
345  *          1  :  client_address = Address of the client
346  *          2  :  tag = The tag to remove.
347  *
348  * Returns     :  void
349  *
350  *********************************************************************/
351 static void remove_tag_for_client(const char *client_address, const char *tag)
352 {
353    struct requested_tags *clients_with_tags;
354    struct client_specific_tag *enabled_tags;
355
356    validate_requested_tags();
357
358    clients_with_tags = requested_tags;
359    while (clients_with_tags != NULL && clients_with_tags->client != NULL)
360    {
361       if (!strcmp(clients_with_tags->client, client_address))
362       {
363          break;
364       }
365       clients_with_tags = clients_with_tags->next;
366    }
367
368    assert(clients_with_tags != NULL);
369    if (clients_with_tags == NULL)
370    {
371       log_error(LOG_LEVEL_ERROR,
372          "Tried to remove tag %s for tag-less client %s",
373          tag, client_address);
374    }
375    enabled_tags = clients_with_tags->tags;
376    while (enabled_tags != NULL)
377    {
378       if (!strcmp(enabled_tags->name, tag))
379       {
380          if (enabled_tags->next != NULL)
381          {
382             enabled_tags->next->prev = enabled_tags->prev;
383             if (enabled_tags == clients_with_tags->tags)
384             {
385                /* Tag is first in line */
386                clients_with_tags->tags = enabled_tags->next;
387             }
388          }
389          if (enabled_tags->prev != NULL)
390          {
391             /* Tag has preceding tag */
392             enabled_tags->prev->next = enabled_tags->next;
393          }
394          if (enabled_tags->prev == NULL && enabled_tags->next == NULL)
395          {
396             /* Tag is the only one */
397             if (clients_with_tags->next != NULL)
398             {
399                /* Client has following client */
400                clients_with_tags->next->prev = clients_with_tags->prev;
401             }
402             if (clients_with_tags->prev != NULL)
403             {
404                /* Client has preceding client */
405                clients_with_tags->prev->next = clients_with_tags->next;
406             }
407             freez(clients_with_tags->client);
408             if (clients_with_tags == requested_tags)
409             {
410                /* Removing last tag */
411                freez(requested_tags);
412                clients_with_tags = requested_tags;
413             }
414             else
415             {
416                freez(clients_with_tags);
417             }
418          }
419          freez(enabled_tags->name);
420          freez(enabled_tags);
421          break;
422       }
423
424       enabled_tags = enabled_tags->next;
425    }
426
427    validate_requested_tags();
428
429 }
430
431
432 /*********************************************************************
433  *
434  * Function    :  client_has_requested_tag
435  *
436  * Description :  Checks whether or not the given client requested
437  *                the tag.
438  *
439  * Parameters  :
440  *          1  :  client_address = Address of the client
441  *          2  :  tag = Tag to check.
442  *
443  * Returns     :  TRUE or FALSE.
444  *
445  *********************************************************************/
446 int client_has_requested_tag(const char *client_address, const char *tag)
447 {
448    struct client_specific_tag *enabled_tags;
449
450    enabled_tags = get_tags_for_client(client_address);
451
452    while (enabled_tags != NULL)
453    {
454       if (!strcmp(enabled_tags->name, tag))
455       {
456          return TRUE;
457       }
458       enabled_tags = enabled_tags->next;
459    }
460
461    return FALSE;
462
463 }
464
465 /*********************************************************************
466  *
467  * Function    :  enable_client_specific_tag
468  *
469  * Description :  Enables a client-specific-tag for the client
470  *
471  * Parameters  :
472  *          1  :  csp = Current client state (buffers, headers, etc...)
473  *          2  :  tag_name = The name of the tag to enable
474  *          3  :  time_to_live = If not 0, the number of seconds the
475  *                               tag should stay enabled.
476  *
477  * Returns     :  JB_ERR_OK on success, JB_ERR_MEMORY or JB_ERR_PARSE.
478  *
479  *********************************************************************/
480 jb_err enable_client_specific_tag(struct client_state *csp,
481    const char *tag_name, const time_t time_to_live)
482 {
483    struct client_tag_spec *tag;
484
485    privoxy_mutex_lock(&client_tags_mutex);
486
487    tag = get_client_specific_tag(csp->config->client_tags, tag_name);
488    if (tag == NULL)
489    {
490       privoxy_mutex_unlock(&client_tags_mutex);
491       return JB_ERR_PARSE;
492    }
493
494    if (client_has_requested_tag(csp->ip_addr_str, tag_name))
495    {
496       log_error(LOG_LEVEL_ERROR,
497          "Tag '%s' already enabled for client '%s'", tag->name, csp->ip_addr_str);
498    }
499    else
500    {
501       add_tag_for_client(csp->ip_addr_str, tag_name, time_to_live);
502       log_error(LOG_LEVEL_INFO,
503          "Tag '%s' enabled for client '%s'. TTL: %d.",
504          tag->name, csp->ip_addr_str, time_to_live);
505    }
506
507    privoxy_mutex_unlock(&client_tags_mutex);
508
509    return JB_ERR_OK;
510
511 }
512
513 /*********************************************************************
514  *
515  * Function    :  disable_client_specific_tag
516  *
517  * Description :  Disables a client-specific-tag for the client
518  *
519  * Parameters  :
520  *          1  :  csp = Current client state (buffers, headers, etc...)
521  *          2  :  tag_name = The name of the tag to disable
522  *
523  * Returns     :  JB_ERR_OK on success, JB_ERR_MEMORY or JB_ERR_PARSE.
524  *
525  *********************************************************************/
526 jb_err disable_client_specific_tag(struct client_state *csp, const char *tag_name)
527 {
528    struct client_tag_spec *tag;
529
530    privoxy_mutex_lock(&client_tags_mutex);
531
532    tag = get_client_specific_tag(csp->config->client_tags, tag_name);
533    if (tag == NULL)
534    {
535       privoxy_mutex_unlock(&client_tags_mutex);
536       return JB_ERR_PARSE;
537    }
538
539    if (client_has_requested_tag(csp->ip_addr_str, tag_name))
540    {
541       remove_tag_for_client(csp->ip_addr_str, tag_name);
542       log_error(LOG_LEVEL_INFO,
543          "Tag '%s' disabled for client '%s'", tag->name, csp->ip_addr_str);
544    }
545    else
546    {
547       log_error(LOG_LEVEL_ERROR,
548          "Tag '%s' currently not set for client '%s'",
549          tag->name, csp->ip_addr_str);
550    }
551
552    privoxy_mutex_unlock(&client_tags_mutex);
553    return JB_ERR_OK;
554
555 }
556
557
558 /*********************************************************************
559  *
560  * Function    :  client_tag_match
561  *
562  * Description :  Compare a client tag against a client tag pattern.
563  *
564  * Parameters  :
565  *          1  :  pattern = a TAG pattern
566  *          2  :  tag = Client tag to match
567  *
568  * Returns     :  Nonzero if the tag matches the pattern, else 0.
569  *
570  *********************************************************************/
571 int client_tag_match(const struct pattern_spec *pattern,
572                      const struct list *tags)
573 {
574    struct list_entry *tag;
575
576    if (!(pattern->flags & PATTERN_SPEC_CLIENT_TAG_PATTERN))
577    {
578       /*
579        * It's not a client pattern and thus shouldn't
580        * be matched against client tags.
581        */
582       return 0;
583    }
584
585    assert(tags);
586
587    for (tag = tags->first; tag != NULL; tag = tag->next)
588    {
589       if (0 == regexec(pattern->pattern.tag_regex, tag->str, 0, NULL, 0))
590       {
591          return 1;
592       }
593    }
594
595    return 0;
596
597 }
598 #else
599 #error Compiling client-tags.c without FEATURE_CLIENT_TAGS
600 #endif /* def FEATURE_CLIENT_TAGS */