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