Line data Source code
1 : /*********************************************************************
2 : *
3 : * File : $Source: /cvsroot/ijbswa/current/loaders.c,v $
4 : *
5 : * Purpose : Functions to load and unload the various
6 : * configuration files. Also contains code to manage
7 : * the list of active loaders, and to automatically
8 : * unload files that are no longer in use.
9 : *
10 : * Copyright : Written by and Copyright (C) 2001-2014 the
11 : * Privoxy team. https://www.privoxy.org/
12 : *
13 : * Based on the Internet Junkbuster originally written
14 : * by and Copyright (C) 1997 Anonymous Coders and
15 : * Junkbusters Corporation. http://www.junkbusters.com
16 : *
17 : * This program is free software; you can redistribute it
18 : * and/or modify it under the terms of the GNU General
19 : * Public License as published by the Free Software
20 : * Foundation; either version 2 of the License, or (at
21 : * your option) any later version.
22 : *
23 : * This program is distributed in the hope that it will
24 : * be useful, but WITHOUT ANY WARRANTY; without even the
25 : * implied warranty of MERCHANTABILITY or FITNESS FOR A
26 : * PARTICULAR PURPOSE. See the GNU General Public
27 : * License for more details.
28 : *
29 : * The GNU General Public License should be included with
30 : * this file. If not, you can view it at
31 : * http://www.gnu.org/copyleft/gpl.html
32 : * or write to the Free Software Foundation, Inc., 59
33 : * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
34 : *
35 : *********************************************************************/
36 :
37 :
38 : #include "config.h"
39 :
40 : #include <stdio.h>
41 : #include <stdlib.h>
42 : #include <sys/types.h>
43 : #include <string.h>
44 : #include <errno.h>
45 : #include <sys/stat.h>
46 : #include <ctype.h>
47 : #include <assert.h>
48 :
49 : #if !defined(_WIN32)
50 : #include <unistd.h>
51 : #endif
52 :
53 : #include "project.h"
54 : #include "list.h"
55 : #include "loaders.h"
56 : #include "filters.h"
57 : #include "parsers.h"
58 : #include "jcc.h"
59 : #include "miscutil.h"
60 : #include "errlog.h"
61 : #include "actions.h"
62 : #include "urlmatch.h"
63 : #include "encode.h"
64 :
65 : /*
66 : * Currently active files.
67 : * These are also entered in the main linked list of files.
68 : */
69 :
70 : #ifdef FEATURE_TRUST
71 : static struct file_list *current_trustfile = NULL;
72 : #endif /* def FEATURE_TRUST */
73 :
74 : #ifndef FUZZ
75 : static int load_one_re_filterfile(struct client_state *csp, int fileid);
76 : #endif
77 :
78 : static struct file_list *current_re_filterfile[MAX_AF_FILES] = {
79 : NULL, NULL, NULL, NULL, NULL,
80 : NULL, NULL, NULL, NULL, NULL
81 : };
82 :
83 : /*********************************************************************
84 : *
85 : * Function : free_csp_resources
86 : *
87 : * Description : Frees memory referenced by the csp that isn't
88 : * shared with other csps.
89 : *
90 : * Parameters :
91 : * 1 : csp = Current client state (buffers, headers, etc...)
92 : *
93 : * Returns : N/A
94 : *
95 : *********************************************************************/
96 0 : void free_csp_resources(struct client_state *csp)
97 : {
98 0 : freez(csp->ip_addr_str);
99 : #ifdef FEATURE_CLIENT_TAGS
100 0 : freez(csp->client_address);
101 : #endif
102 0 : freez(csp->listen_addr_str);
103 0 : freez(csp->client_iob->buf);
104 0 : freez(csp->iob->buf);
105 0 : freez(csp->error_message);
106 :
107 0 : if (csp->action->flags & ACTION_FORWARD_OVERRIDE &&
108 0 : NULL != csp->fwd)
109 : {
110 0 : unload_forward_spec(csp->fwd);
111 : }
112 0 : free_http_request(csp->http);
113 :
114 0 : destroy_list(csp->headers);
115 : #ifdef FEATURE_HTTPS_INSPECTION
116 0 : destroy_list(csp->https_headers);
117 : #endif
118 0 : destroy_list(csp->tags);
119 : #ifdef FEATURE_CLIENT_TAGS
120 0 : destroy_list(csp->client_tags);
121 : #endif
122 :
123 0 : free_current_action(csp->action);
124 0 : }
125 :
126 : /*********************************************************************
127 : *
128 : * Function : sweep
129 : *
130 : * Description : Basically a mark and sweep garbage collector, it is run
131 : * (by the parent thread) every once in a while to reclaim memory.
132 : *
133 : * It uses a mark and sweep strategy:
134 : * 1) mark all files as inactive
135 : *
136 : * 2) check with each client:
137 : * if it is active, mark its files as active
138 : * if it is inactive, free its resources
139 : *
140 : * 3) free the resources of all of the files that
141 : * are still marked as inactive (and are obsolete).
142 : *
143 : * N.B. files that are not obsolete don't have an unloader defined.
144 : *
145 : * Parameters : None
146 : *
147 : * Returns : The number of threads that are still active.
148 : *
149 : *********************************************************************/
150 0 : unsigned int sweep(void)
151 : {
152 : struct file_list *fl, *nfl;
153 : struct client_state *csp;
154 : struct client_states *last_active, *client_list;
155 : int i;
156 0 : unsigned int active_threads = 0;
157 :
158 : /* clear all of the file's active flags */
159 0 : for (fl = files->next; NULL != fl; fl = fl->next)
160 : {
161 0 : fl->active = 0;
162 : }
163 :
164 0 : last_active = clients;
165 0 : client_list = clients->next;
166 :
167 0 : while (NULL != client_list)
168 : {
169 0 : csp = &client_list->csp;
170 0 : if (csp->flags & CSP_FLAG_ACTIVE)
171 : {
172 : /* Mark this client's files as active */
173 :
174 : /*
175 : * Always have a configuration file.
176 : * (Also note the slightly non-standard extra
177 : * indirection here.)
178 : */
179 0 : csp->config->config_file_list->active = 1;
180 :
181 : /*
182 : * Actions files
183 : */
184 0 : for (i = 0; i < MAX_AF_FILES; i++)
185 : {
186 0 : if (csp->actions_list[i])
187 : {
188 0 : csp->actions_list[i]->active = 1;
189 : }
190 : }
191 :
192 : /*
193 : * Filter files
194 : */
195 0 : for (i = 0; i < MAX_AF_FILES; i++)
196 : {
197 0 : if (csp->rlist[i])
198 : {
199 0 : csp->rlist[i]->active = 1;
200 : }
201 : }
202 :
203 : /*
204 : * Trust file
205 : */
206 : #ifdef FEATURE_TRUST
207 0 : if (csp->tlist)
208 : {
209 0 : csp->tlist->active = 1;
210 : }
211 : #endif /* def FEATURE_TRUST */
212 :
213 0 : active_threads++;
214 :
215 0 : last_active = client_list;
216 0 : client_list = client_list->next;
217 : }
218 : else
219 : /*
220 : * This client is not active. Free its resources.
221 : */
222 : {
223 0 : last_active->next = client_list->next;
224 :
225 : #ifdef FEATURE_STATISTICS
226 0 : urls_read++;
227 0 : if (csp->flags & CSP_FLAG_REJECTED)
228 : {
229 0 : urls_rejected++;
230 : }
231 : #endif /* def FEATURE_STATISTICS */
232 :
233 0 : freez(client_list);
234 :
235 0 : client_list = last_active->next;
236 : }
237 : }
238 :
239 0 : nfl = files;
240 0 : fl = files->next;
241 :
242 0 : while (fl != NULL)
243 : {
244 0 : if ((0 == fl->active) && (NULL != fl->unloader))
245 : {
246 0 : nfl->next = fl->next;
247 :
248 0 : (fl->unloader)(fl->f);
249 :
250 0 : freez(fl->filename);
251 0 : freez(fl);
252 :
253 0 : fl = nfl->next;
254 : }
255 : else
256 : {
257 0 : nfl = fl;
258 0 : fl = fl->next;
259 : }
260 : }
261 :
262 0 : return active_threads;
263 :
264 : }
265 :
266 :
267 : /*********************************************************************
268 : *
269 : * Function : check_file_changed
270 : *
271 : * Description : Helper function to check if a file needs reloading.
272 : * If "current" is still current, return it. Otherwise
273 : * allocates a new (zeroed) "struct file_list", fills
274 : * in the disk file name and timestamp, and returns it.
275 : *
276 : * Parameters :
277 : * 1 : current = The file_list currently being used - will
278 : * be checked to see if it is out of date.
279 : * May be NULL (which is treated as out of
280 : * date).
281 : * 2 : filename = Name of file to check.
282 : * 3 : newfl = New file list. [Output only]
283 : * This will be set to NULL, OR a struct
284 : * file_list newly allocated on the
285 : * heap, with the filename and lastmodified
286 : * fields filled, and all others zeroed.
287 : *
288 : * Returns : If file unchanged: 0 (and sets newfl == NULL)
289 : * If file changed: 1 and sets newfl != NULL
290 : * On error: 1 and sets newfl == NULL
291 : *
292 : *********************************************************************/
293 27882 : int check_file_changed(const struct file_list * current,
294 : const char * filename,
295 : struct file_list ** newfl)
296 : {
297 : struct file_list *fs;
298 : struct stat statbuf[1];
299 :
300 27882 : *newfl = NULL;
301 :
302 27882 : if (stat(filename, statbuf) < 0)
303 : {
304 : /* Error, probably file not found. */
305 0 : return 1;
306 : }
307 :
308 27882 : if (current
309 12392 : && (current->lastmodified == statbuf->st_mtime)
310 12392 : && (0 == strcmp(current->filename, filename)))
311 : {
312 12392 : return 0;
313 : }
314 :
315 15490 : fs = zalloc_or_die(sizeof(struct file_list));
316 15490 : fs->filename = strdup_or_die(filename);
317 15490 : fs->lastmodified = statbuf->st_mtime;
318 :
319 15490 : *newfl = fs;
320 15490 : return 1;
321 : }
322 :
323 :
324 : /*********************************************************************
325 : *
326 : * Function : simple_read_line
327 : *
328 : * Description : Read a single line from a file and return it.
329 : * This is basically a version of fgets() that malloc()s
330 : * it's own line buffer. Note that the buffer will
331 : * always be a multiple of BUFFER_SIZE bytes long.
332 : * Therefore if you are going to keep the string for
333 : * an extended period of time, you should probably
334 : * strdup() it and free() the original, to save memory.
335 : *
336 : *
337 : * Parameters :
338 : * 1 : dest = destination for newly malloc'd pointer to
339 : * line data. Will be set to NULL on error.
340 : * 2 : fp = File to read from
341 : * 3 : newline = Standard for newlines in the file.
342 : * Will be unchanged if it's value on input is not
343 : * NEWLINE_UNKNOWN.
344 : * On output, may be changed from NEWLINE_UNKNOWN to
345 : * actual convention in file.
346 : *
347 : * Returns : JB_ERR_OK on success
348 : * JB_ERR_MEMORY on out-of-memory
349 : * JB_ERR_FILE on EOF.
350 : *
351 : *********************************************************************/
352 12154590 : jb_err simple_read_line(FILE *fp, char **dest, int *newline)
353 : {
354 12154590 : size_t len = 0;
355 12154590 : size_t buflen = BUFFER_SIZE;
356 : char * buf;
357 : char * p;
358 : int ch;
359 12154590 : int realnewline = NEWLINE_UNKNOWN;
360 :
361 12154590 : if (NULL == (buf = malloc(buflen)))
362 : {
363 0 : return JB_ERR_MEMORY;
364 : }
365 :
366 12154590 : p = buf;
367 :
368 : /*
369 : * Character codes. If you have a weird compiler and the following are
370 : * incorrect, you also need to fix NEWLINE() in loaders.h
371 : */
372 : #define CHAR_CR '\r' /* ASCII 13 */
373 : #define CHAR_LF '\n' /* ASCII 10 */
374 :
375 : for (;;)
376 : {
377 411929560 : ch = getc(fp);
378 :
379 411929560 : if (ch == EOF)
380 : {
381 19376 : if (len > 0)
382 : {
383 0 : *p = '\0';
384 0 : *dest = buf;
385 0 : return JB_ERR_OK;
386 : }
387 : else
388 : {
389 19376 : free(buf);
390 19376 : *dest = NULL;
391 19376 : return JB_ERR_FILE;
392 : }
393 : }
394 411910184 : else if (ch == CHAR_CR)
395 : {
396 0 : ch = getc(fp);
397 0 : if (ch == CHAR_LF)
398 : {
399 0 : if (*newline == NEWLINE_UNKNOWN)
400 : {
401 0 : *newline = NEWLINE_DOS;
402 : }
403 : }
404 : else
405 : {
406 0 : if (ch != EOF)
407 : {
408 0 : ungetc(ch, fp);
409 : }
410 0 : if (*newline == NEWLINE_UNKNOWN)
411 : {
412 0 : *newline = NEWLINE_MAC;
413 : }
414 : }
415 0 : *p = '\0';
416 0 : *dest = buf;
417 0 : if (*newline == NEWLINE_UNKNOWN)
418 : {
419 0 : *newline = realnewline;
420 : }
421 0 : return JB_ERR_OK;
422 : }
423 411910184 : else if (ch == CHAR_LF)
424 : {
425 12135214 : *p = '\0';
426 12135214 : *dest = buf;
427 12135214 : if (*newline == NEWLINE_UNKNOWN)
428 : {
429 490272 : *newline = NEWLINE_UNIX;
430 : }
431 12135214 : return JB_ERR_OK;
432 : }
433 399774970 : else if (ch == 0)
434 : {
435 : /* XXX: Why do we allow this anyway? */
436 0 : *p = '\0';
437 0 : *dest = buf;
438 0 : return JB_ERR_OK;
439 : }
440 :
441 399774970 : *p++ = (char)ch;
442 :
443 399774970 : if (++len >= buflen)
444 : {
445 0 : buflen += BUFFER_SIZE;
446 0 : if (NULL == (p = realloc(buf, buflen)))
447 : {
448 0 : free(buf);
449 0 : return JB_ERR_MEMORY;
450 : }
451 0 : buf = p;
452 0 : p = buf + len;
453 : }
454 : }
455 : }
456 :
457 :
458 : /*********************************************************************
459 : *
460 : * Function : edit_read_line
461 : *
462 : * Description : Read a single non-empty line from a file and return
463 : * it. Trims comments, leading and trailing whitespace
464 : * and respects escaping of newline and comment char.
465 : * Provides the line in 2 alternative forms: raw and
466 : * preprocessed.
467 : * - raw is the raw data read from the file. If the
468 : * line is not modified, then this should be written
469 : * to the new file.
470 : * - prefix is any comments and blank lines that were
471 : * read from the file. If the line is modified, then
472 : * this should be written out to the file followed
473 : * by the modified data. (If this string is non-empty
474 : * then it will have a newline at the end).
475 : * - data is the actual data that will be parsed
476 : * further by appropriate routines.
477 : * On EOF, the 3 strings will all be set to NULL and
478 : * 0 will be returned.
479 : *
480 : * Parameters :
481 : * 1 : fp = File to read from
482 : * 2 : raw_out = destination for newly malloc'd pointer to
483 : * raw line data. May be NULL if you don't want it.
484 : * 3 : prefix_out = destination for newly malloc'd pointer to
485 : * comments. May be NULL if you don't want it.
486 : * 4 : data_out = destination for newly malloc'd pointer to
487 : * line data with comments and leading/trailing spaces
488 : * removed, and line continuation performed. May be
489 : * NULL if you don't want it.
490 : * 5 : newline = Standard for newlines in the file.
491 : * On input, set to value to use or NEWLINE_UNKNOWN.
492 : * On output, may be changed from NEWLINE_UNKNOWN to
493 : * actual convention in file. May be NULL if you
494 : * don't want it.
495 : * 6 : line_number = Line number in file. In "lines" as
496 : * reported by a text editor, not lines containing data.
497 : *
498 : * Returns : JB_ERR_OK on success
499 : * JB_ERR_MEMORY on out-of-memory
500 : * JB_ERR_FILE on EOF.
501 : *
502 : *********************************************************************/
503 590784 : jb_err edit_read_line(FILE *fp,
504 : char **raw_out,
505 : char **prefix_out,
506 : char **data_out,
507 : int *newline,
508 : unsigned long *line_number)
509 : {
510 : char *p; /* Temporary pointer */
511 : char *linebuf; /* Line read from file */
512 : char *linestart; /* Start of linebuf, usually first non-whitespace char */
513 590784 : int contflag = 0; /* Nonzero for line continuation - i.e. line ends '\' */
514 590784 : int is_empty = 1; /* Flag if not got any data yet */
515 590784 : char *raw = NULL; /* String to be stored in raw_out */
516 590784 : char *prefix = NULL; /* String to be stored in prefix_out */
517 590784 : char *data = NULL; /* String to be stored in data_out */
518 : int scrapnewline; /* Used for (*newline) if newline==NULL */
519 590784 : jb_err rval = JB_ERR_OK;
520 :
521 590784 : assert(fp);
522 590784 : assert(raw_out || data_out);
523 590784 : assert(newline == NULL
524 : || *newline == NEWLINE_UNKNOWN
525 : || *newline == NEWLINE_UNIX
526 : || *newline == NEWLINE_DOS
527 : || *newline == NEWLINE_MAC);
528 :
529 590784 : if (newline == NULL)
530 : {
531 495680 : scrapnewline = NEWLINE_UNKNOWN;
532 495680 : newline = &scrapnewline;
533 : }
534 :
535 : /* Set output parameters to NULL */
536 590784 : if (raw_out)
537 : {
538 95104 : *raw_out = NULL;
539 : }
540 590784 : if (prefix_out)
541 : {
542 95104 : *prefix_out = NULL;
543 : }
544 590784 : if (data_out)
545 : {
546 590784 : *data_out = NULL;
547 : }
548 :
549 : /* Set string variables to new, empty strings. */
550 :
551 590784 : if (raw_out)
552 : {
553 95104 : raw = strdup_or_die("");
554 : }
555 590784 : if (prefix_out)
556 : {
557 95104 : prefix = strdup_or_die("");
558 : }
559 590784 : if (data_out)
560 : {
561 590784 : data = strdup_or_die("");
562 : }
563 :
564 : /* Main loop. Loop while we need more data & it's not EOF. */
565 :
566 12725998 : while ((contflag || is_empty)
567 12154590 : && (JB_ERR_OK == (rval = simple_read_line(fp, &linebuf, newline))))
568 : {
569 12135214 : if (line_number)
570 : {
571 11258132 : (*line_number)++;
572 : }
573 12135214 : if (raw)
574 : {
575 877082 : string_append(&raw,linebuf);
576 877082 : if (string_append(&raw,NEWLINE(*newline)))
577 : {
578 0 : freez(prefix);
579 0 : freez(data);
580 0 : free(linebuf);
581 0 : return JB_ERR_MEMORY;
582 : }
583 : }
584 :
585 : /* Line continuation? Trim escape and set flag. */
586 12135214 : p = linebuf + strlen(linebuf) - 1;
587 12135214 : contflag = ((*linebuf != '\0') && (*p == '\\'));
588 12135214 : if (contflag)
589 : {
590 340004 : *p = '\0';
591 : }
592 :
593 : /* Trim leading spaces if we're at the start of the line */
594 12135214 : linestart = linebuf;
595 12135214 : assert(NULL != data);
596 12135214 : if (*data == '\0')
597 : {
598 : /* Trim leading spaces */
599 12374874 : while (*linestart && isspace((int)(unsigned char)*linestart))
600 : {
601 406176 : linestart++;
602 : }
603 : }
604 :
605 : /* Handle comment characters. */
606 12135214 : p = linestart;
607 23367832 : while ((p = strchr(p, '#')) != NULL)
608 : {
609 : /* Found a comment char.. */
610 11232618 : if ((p != linebuf) && (*(p-1) == '\\'))
611 0 : {
612 : /* ..and it's escaped, left-shift the line over the escape. */
613 0 : char *q = p - 1;
614 0 : while ((*q = *(q + 1)) != '\0')
615 : {
616 0 : q++;
617 : }
618 : /* Now scan from just after the "#". */
619 : }
620 : else
621 : {
622 : /* Real comment. Save it... */
623 11232618 : if (p == linestart)
624 : {
625 : /* Special case: Line only contains a comment, so all the
626 : * previous whitespace is considered part of the comment.
627 : * Undo the whitespace skipping, if any.
628 : */
629 11168490 : linestart = linebuf;
630 11168490 : p = linestart;
631 : }
632 11232618 : if (prefix)
633 : {
634 683928 : string_append(&prefix,p);
635 683928 : if (string_append(&prefix, NEWLINE(*newline)))
636 : {
637 0 : freez(raw);
638 0 : freez(data);
639 0 : free(linebuf);
640 0 : return JB_ERR_MEMORY;
641 : }
642 : }
643 :
644 : /* ... and chop off the rest of the line */
645 11232618 : *p = '\0';
646 : }
647 : } /* END while (there's a # character) */
648 :
649 : /* Write to the buffer */
650 12135214 : if (*linestart)
651 : {
652 737924 : is_empty = 0;
653 737924 : if (string_append(&data, linestart))
654 : {
655 0 : freez(raw);
656 0 : freez(prefix);
657 0 : free(linebuf);
658 0 : return JB_ERR_MEMORY;
659 : }
660 : }
661 :
662 12135214 : free(linebuf);
663 : } /* END while(we need more data) */
664 :
665 : /* Handle simple_read_line() errors - ignore EOF */
666 590784 : if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
667 : {
668 0 : freez(raw);
669 0 : freez(prefix);
670 0 : freez(data);
671 0 : return rval;
672 : }
673 :
674 590784 : if (raw ? (*raw == '\0') : is_empty)
675 : {
676 : /* EOF and no data there. (Definition of "data" depends on whether
677 : * the caller cares about "raw" or just "data").
678 : */
679 :
680 19376 : freez(raw);
681 19376 : freez(prefix);
682 19376 : freez(data);
683 :
684 19376 : return JB_ERR_FILE;
685 : }
686 : else
687 : {
688 : /* Got at least some data */
689 :
690 : /* Remove trailing whitespace */
691 571408 : chomp(data);
692 :
693 571408 : if (raw_out)
694 : {
695 91218 : *raw_out = raw;
696 : }
697 : else
698 : {
699 480190 : freez(raw);
700 : }
701 571408 : if (prefix_out)
702 : {
703 91218 : *prefix_out = prefix;
704 : }
705 : else
706 : {
707 480190 : freez(prefix);
708 : }
709 571408 : if (data_out)
710 : {
711 571408 : *data_out = data;
712 : }
713 : else
714 : {
715 0 : freez(data);
716 : }
717 571408 : return JB_ERR_OK;
718 : }
719 : }
720 :
721 :
722 : /*********************************************************************
723 : *
724 : * Function : read_config_line
725 : *
726 : * Description : Read a single non-empty line from a file and return
727 : * it. Trims comments, leading and trailing whitespace
728 : * and respects escaping of newline and comment char.
729 : *
730 : * Parameters :
731 : * 1 : fp = File to read from
732 : * 2 : linenum = linenumber in file
733 : * 3 : buf = Pointer to a pointer to set to the data buffer.
734 : *
735 : * Returns : NULL on EOF or error
736 : * Otherwise, returns buf.
737 : *
738 : *********************************************************************/
739 495680 : char *read_config_line(FILE *fp, unsigned long *linenum, char **buf)
740 : {
741 : jb_err err;
742 495680 : err = edit_read_line(fp, NULL, NULL, buf, NULL, linenum);
743 495680 : if (err)
744 : {
745 15490 : if (err == JB_ERR_MEMORY)
746 : {
747 0 : log_error(LOG_LEVEL_FATAL, "Out of memory loading a config file");
748 : }
749 15490 : *buf = NULL;
750 : }
751 495680 : return *buf;
752 : }
753 :
754 :
755 : #ifdef FEATURE_TRUST
756 : /*********************************************************************
757 : *
758 : * Function : unload_trustfile
759 : *
760 : * Description : Unloads a trustfile.
761 : *
762 : * Parameters :
763 : * 1 : f = the data structure associated with the trustfile.
764 : *
765 : * Returns : N/A
766 : *
767 : *********************************************************************/
768 0 : static void unload_trustfile(void *f)
769 : {
770 0 : struct block_spec *cur = (struct block_spec *)f;
771 : struct block_spec *next;
772 :
773 0 : while (cur != NULL)
774 : {
775 0 : next = cur->next;
776 :
777 0 : free_pattern_spec(cur->url);
778 0 : free(cur);
779 :
780 0 : cur = next;
781 : }
782 :
783 0 : }
784 :
785 :
786 : #ifdef FEATURE_GRACEFUL_TERMINATION
787 : /*********************************************************************
788 : *
789 : * Function : unload_current_trust_file
790 : *
791 : * Description : Unloads current trust file - reset to state at
792 : * beginning of program.
793 : *
794 : * Parameters : None
795 : *
796 : * Returns : N/A
797 : *
798 : *********************************************************************/
799 : void unload_current_trust_file(void)
800 : {
801 : if (current_trustfile)
802 : {
803 : current_trustfile->unloader = unload_trustfile;
804 : current_trustfile = NULL;
805 : }
806 : }
807 : #endif /* FEATURE_GRACEFUL_TERMINATION */
808 :
809 :
810 : /*********************************************************************
811 : *
812 : * Function : load_trustfile
813 : *
814 : * Description : Read and parse a trustfile and add to files list.
815 : *
816 : * Parameters :
817 : * 1 : csp = Current client state (buffers, headers, etc...)
818 : *
819 : * Returns : 0 => Ok, everything else is an error.
820 : *
821 : *********************************************************************/
822 0 : int load_trustfile(struct client_state *csp)
823 : {
824 : FILE *fp;
825 :
826 : struct block_spec *b, *bl;
827 : struct pattern_spec **tl;
828 :
829 0 : char *buf = NULL;
830 : int reject, trusted;
831 : struct file_list *fs;
832 0 : unsigned long linenum = 0;
833 0 : int trusted_referrers = 0;
834 :
835 0 : if (!check_file_changed(current_trustfile, csp->config->trustfile, &fs))
836 : {
837 : /* No need to load */
838 0 : csp->tlist = current_trustfile;
839 0 : return(0);
840 : }
841 0 : if (!fs)
842 : {
843 0 : goto load_trustfile_error;
844 : }
845 :
846 0 : fs->f = bl = zalloc_or_die(sizeof(*bl));
847 :
848 0 : if ((fp = fopen(csp->config->trustfile, "r")) == NULL)
849 : {
850 0 : goto load_trustfile_error;
851 : }
852 0 : log_error(LOG_LEVEL_INFO, "Loading trust file: %s", csp->config->trustfile);
853 :
854 0 : tl = csp->config->trust_list;
855 :
856 0 : while (read_config_line(fp, &linenum, &buf) != NULL)
857 : {
858 0 : trusted = 0;
859 0 : reject = 1;
860 :
861 0 : if (*buf == '+')
862 : {
863 0 : trusted = 1;
864 0 : *buf = '~';
865 : }
866 :
867 0 : if (*buf == '~')
868 : {
869 : char *p;
870 : char *q;
871 :
872 0 : reject = 0;
873 0 : p = buf;
874 0 : q = p+1;
875 0 : while ((*p++ = *q++) != '\0')
876 : {
877 : /* nop */
878 : }
879 : }
880 :
881 : /* skip blank lines */
882 0 : if (*buf == '\0')
883 : {
884 0 : freez(buf);
885 0 : continue;
886 : }
887 :
888 : /* allocate a new node */
889 0 : b = zalloc_or_die(sizeof(*b));
890 :
891 : /* add it to the list */
892 0 : b->next = bl->next;
893 0 : bl->next = b;
894 :
895 0 : b->reject = reject;
896 :
897 : /* Save the URL pattern */
898 0 : if (create_pattern_spec(b->url, buf))
899 : {
900 0 : fclose(fp);
901 0 : goto load_trustfile_error;
902 : }
903 :
904 : /*
905 : * save a pointer to URL's spec in the list of trusted URL's, too
906 : */
907 0 : if (trusted)
908 : {
909 0 : if (++trusted_referrers < MAX_TRUSTED_REFERRERS)
910 : {
911 0 : *tl++ = b->url;
912 : }
913 : }
914 0 : freez(buf);
915 : }
916 :
917 0 : if (trusted_referrers >= MAX_TRUSTED_REFERRERS)
918 : {
919 : /*
920 : * FIXME: ... after Privoxy 3.0.4 is out.
921 : */
922 0 : log_error(LOG_LEVEL_ERROR, "Too many trusted referrers. Current limit is %d, you are using %d.\n"
923 : " Additional trusted referrers are treated like ordinary trusted URLs.\n"
924 : " (You can increase this limit by changing MAX_TRUSTED_REFERRERS in project.h and recompiling).",
925 : MAX_TRUSTED_REFERRERS, trusted_referrers);
926 : }
927 :
928 0 : *tl = NULL;
929 :
930 0 : fclose(fp);
931 :
932 : /* the old one is now obsolete */
933 0 : if (current_trustfile)
934 : {
935 0 : current_trustfile->unloader = unload_trustfile;
936 : }
937 :
938 0 : fs->next = files->next;
939 0 : files->next = fs;
940 0 : current_trustfile = fs;
941 0 : csp->tlist = fs;
942 :
943 0 : return(0);
944 :
945 0 : load_trustfile_error:
946 0 : log_error(LOG_LEVEL_FATAL, "can't load trustfile '%s': %E",
947 0 : csp->config->trustfile);
948 0 : freez(buf);
949 0 : return(-1);
950 :
951 : }
952 : #endif /* def FEATURE_TRUST */
953 :
954 :
955 : /*********************************************************************
956 : *
957 : * Function : unload_re_filterfile
958 : *
959 : * Description : Unload the re_filter list by freeing all chained
960 : * re_filterfile specs and their data.
961 : *
962 : * Parameters :
963 : * 1 : f = the data structure associated with the filterfile.
964 : *
965 : * Returns : N/A
966 : *
967 : *********************************************************************/
968 0 : static void unload_re_filterfile(void *f)
969 : {
970 0 : struct re_filterfile_spec *a, *b = (struct re_filterfile_spec *)f;
971 :
972 0 : while (b != NULL)
973 : {
974 0 : a = b->next;
975 :
976 0 : destroy_list(b->patterns);
977 0 : pcrs_free_joblist(b->joblist);
978 0 : freez(b->name);
979 0 : freez(b->description);
980 0 : freez(b);
981 :
982 0 : b = a;
983 : }
984 :
985 0 : return;
986 : }
987 :
988 : /*********************************************************************
989 : *
990 : * Function : unload_forward_spec
991 : *
992 : * Description : Unload the forward spec settings by freeing all
993 : * memory referenced by members and the memory for
994 : * the spec itself.
995 : *
996 : * Parameters :
997 : * 1 : fwd = the forward spec.
998 : *
999 : * Returns : N/A
1000 : *
1001 : *********************************************************************/
1002 0 : void unload_forward_spec(struct forward_spec *fwd)
1003 : {
1004 0 : free_pattern_spec(fwd->url);
1005 0 : freez(fwd->gateway_host);
1006 0 : freez(fwd->forward_host);
1007 0 : freez(fwd->auth_username);
1008 0 : freez(fwd->auth_password);
1009 0 : free(fwd);
1010 :
1011 0 : return;
1012 : }
1013 :
1014 :
1015 : #ifdef FEATURE_GRACEFUL_TERMINATION
1016 : /*********************************************************************
1017 : *
1018 : * Function : unload_current_re_filterfile
1019 : *
1020 : * Description : Unloads current re_filter file - reset to state at
1021 : * beginning of program.
1022 : *
1023 : * Parameters : None
1024 : *
1025 : * Returns : N/A
1026 : *
1027 : *********************************************************************/
1028 : void unload_current_re_filterfile(void)
1029 : {
1030 : int i;
1031 :
1032 : for (i = 0; i < MAX_AF_FILES; i++)
1033 : {
1034 : if (current_re_filterfile[i])
1035 : {
1036 : current_re_filterfile[i]->unloader = unload_re_filterfile;
1037 : current_re_filterfile[i] = NULL;
1038 : }
1039 : }
1040 : }
1041 : #endif
1042 :
1043 :
1044 : /*********************************************************************
1045 : *
1046 : * Function : load_re_filterfiles
1047 : *
1048 : * Description : Loads all the filterfiles.
1049 : * Generate a chained list of re_filterfile_spec's from
1050 : * the "FILTER: " blocks, compiling all their substitutions
1051 : * into chained lists of pcrs_job structs.
1052 : *
1053 : * Parameters :
1054 : * 1 : csp = Current client state (buffers, headers, etc...)
1055 : *
1056 : * Returns : 0 => Ok, everything else is an error.
1057 : *
1058 : *********************************************************************/
1059 6196 : int load_re_filterfiles(struct client_state *csp)
1060 : {
1061 : int i;
1062 : int result;
1063 :
1064 625796 : for (i = 0; i < MAX_AF_FILES; i++)
1065 : {
1066 619600 : if (csp->config->re_filterfile[i])
1067 : {
1068 12392 : result = load_one_re_filterfile(csp, i);
1069 12392 : if (result)
1070 : {
1071 0 : return result;
1072 : }
1073 : }
1074 607208 : else if (current_re_filterfile[i])
1075 : {
1076 0 : current_re_filterfile[i]->unloader = unload_re_filterfile;
1077 0 : current_re_filterfile[i] = NULL;
1078 : }
1079 : }
1080 :
1081 6196 : return 0;
1082 : }
1083 :
1084 :
1085 : /*********************************************************************
1086 : *
1087 : * Function : load_one_re_filterfile
1088 : *
1089 : * Description : Load a re_filterfile.
1090 : * Generate a chained list of re_filterfile_spec's from
1091 : * the "FILTER: " blocks, compiling all their substitutions
1092 : * into chained lists of pcrs_job structs.
1093 : *
1094 : * Parameters :
1095 : * 1 : csp = Current client state (buffers, headers, etc...)
1096 : *
1097 : * Returns : 0 => Ok, everything else is an error.
1098 : *
1099 : *********************************************************************/
1100 12392 : int load_one_re_filterfile(struct client_state *csp, int fileid)
1101 : {
1102 : FILE *fp;
1103 :
1104 12392 : struct re_filterfile_spec *new_bl, *bl = NULL;
1105 : struct file_list *fs;
1106 :
1107 12392 : char *buf = NULL;
1108 12392 : unsigned long linenum = 0;
1109 12392 : pcrs_job *dummy, *lastjob = NULL;
1110 :
1111 : /*
1112 : * No need to reload if unchanged
1113 : */
1114 12392 : if (!check_file_changed(current_re_filterfile[fileid], csp->config->re_filterfile[fileid], &fs))
1115 : {
1116 6196 : csp->rlist[fileid] = current_re_filterfile[fileid];
1117 6196 : return(0);
1118 : }
1119 6196 : if (!fs)
1120 : {
1121 0 : goto load_re_filterfile_error;
1122 : }
1123 :
1124 : /*
1125 : * Open the file or fail
1126 : */
1127 6196 : if ((fp = fopen(csp->config->re_filterfile[fileid], "r")) == NULL)
1128 : {
1129 0 : goto load_re_filterfile_error;
1130 : }
1131 :
1132 6196 : log_error(LOG_LEVEL_INFO, "Loading filter file: %s", csp->config->re_filterfile[fileid]);
1133 :
1134 : /*
1135 : * Read line by line
1136 : */
1137 154900 : while (read_config_line(fp, &linenum, &buf) != NULL)
1138 : {
1139 148704 : enum filter_type new_filter = FT_INVALID_FILTER;
1140 :
1141 148704 : if (strncmp(buf, "FILTER:", 7) == 0)
1142 : {
1143 12392 : new_filter = FT_CONTENT_FILTER;
1144 : }
1145 136312 : else if (strncmp(buf, "SERVER-HEADER-FILTER:", 21) == 0)
1146 : {
1147 3098 : new_filter = FT_SERVER_HEADER_FILTER;
1148 : }
1149 133214 : else if (strncmp(buf, "CLIENT-HEADER-FILTER:", 21) == 0)
1150 : {
1151 12392 : new_filter = FT_CLIENT_HEADER_FILTER;
1152 : }
1153 120822 : else if (strncmp(buf, "CLIENT-HEADER-TAGGER:", 21) == 0)
1154 : {
1155 3098 : new_filter = FT_CLIENT_HEADER_TAGGER;
1156 : }
1157 117724 : else if (strncmp(buf, "SERVER-HEADER-TAGGER:", 21) == 0)
1158 : {
1159 3098 : new_filter = FT_SERVER_HEADER_TAGGER;
1160 : }
1161 : #ifdef FEATURE_EXTERNAL_FILTERS
1162 114626 : else if (strncmp(buf, "EXTERNAL-FILTER:", 16) == 0)
1163 : {
1164 0 : new_filter = FT_EXTERNAL_CONTENT_FILTER;
1165 : }
1166 : #endif
1167 114626 : else if (strncmp(buf, "CLIENT-BODY-FILTER:", 19) == 0)
1168 : {
1169 3098 : new_filter = FT_CLIENT_BODY_FILTER;
1170 : }
1171 :
1172 : /*
1173 : * If this is the head of a new filter block, make it a
1174 : * re_filterfile spec of its own and chain it to the list:
1175 : */
1176 148704 : if (new_filter != FT_INVALID_FILTER)
1177 : {
1178 37176 : new_bl = zalloc_or_die(sizeof(*bl));
1179 37176 : if (new_filter == FT_CONTENT_FILTER)
1180 : {
1181 12392 : new_bl->name = chomp(buf + 7);
1182 : }
1183 : #ifdef FEATURE_EXTERNAL_FILTERS
1184 24784 : else if (new_filter == FT_EXTERNAL_CONTENT_FILTER)
1185 : {
1186 0 : new_bl->name = chomp(buf + 16);
1187 : }
1188 : #endif
1189 24784 : else if (new_filter == FT_CLIENT_BODY_FILTER)
1190 : {
1191 3098 : new_bl->name = chomp(buf + 19);
1192 : }
1193 : else
1194 : {
1195 21686 : new_bl->name = chomp(buf + 21);
1196 : }
1197 37176 : new_bl->type = new_filter;
1198 :
1199 : /*
1200 : * If a filter description is available,
1201 : * encode it to HTML and save it.
1202 : */
1203 37176 : if (NULL != (new_bl->description = strpbrk(new_bl->name, " \t")))
1204 : {
1205 37176 : *new_bl->description++ = '\0';
1206 37176 : new_bl->description = html_encode(chomp(new_bl->description));
1207 37176 : if (NULL == new_bl->description)
1208 : {
1209 0 : new_bl->description = strdup_or_die("Out of memory while "
1210 : "encoding filter description to HTML");
1211 : }
1212 : }
1213 : else
1214 : {
1215 0 : new_bl->description = strdup_or_die("No description available");
1216 : }
1217 :
1218 37176 : new_bl->name = strdup_or_die(chomp(new_bl->name));
1219 :
1220 : /*
1221 : * If this is the first filter block, chain it
1222 : * to the file_list rather than its (nonexistent)
1223 : * predecessor
1224 : */
1225 37176 : if (fs->f == NULL)
1226 : {
1227 3098 : fs->f = new_bl;
1228 : }
1229 : else
1230 : {
1231 34078 : assert(NULL != bl);
1232 34078 : bl->next = new_bl;
1233 : }
1234 37176 : bl = new_bl;
1235 :
1236 37176 : log_error(LOG_LEVEL_RE_FILTER, "Reading in filter \"%s\" (\"%s\")", bl->name, bl->description);
1237 : #ifdef FEATURE_EXTENDED_STATISTICS
1238 37176 : register_filter_for_statistics(bl->name);
1239 : #endif
1240 37176 : freez(buf);
1241 37176 : continue;
1242 : }
1243 :
1244 : #ifdef FEATURE_EXTERNAL_FILTERS
1245 111528 : if ((bl != NULL) && (bl->type == FT_EXTERNAL_CONTENT_FILTER))
1246 : {
1247 : jb_err jb_error;
1248 : /* Save the code as "pattern", but do not compile anything. */
1249 0 : if (bl->patterns->first != NULL)
1250 : {
1251 0 : log_error(LOG_LEVEL_FATAL, "External filter '%s' contains several jobs. "
1252 : "Did you forget to escape a line break?",
1253 : bl->name);
1254 : }
1255 0 : jb_error = enlist(bl->patterns, buf);
1256 0 : if (JB_ERR_MEMORY == jb_error)
1257 : {
1258 0 : log_error(LOG_LEVEL_FATAL,
1259 : "Out of memory while enlisting external filter code \'%s\' for filter %s.",
1260 : buf, bl->name);
1261 : }
1262 0 : freez(buf);
1263 0 : continue;
1264 : }
1265 : #endif
1266 111528 : if (bl != NULL)
1267 : {
1268 : int pcrs_error;
1269 : jb_err jb_error;
1270 : /*
1271 : * Save the expression, make it a pcrs_job
1272 : * and chain it into the current filter's joblist
1273 : */
1274 111528 : jb_error = enlist(bl->patterns, buf);
1275 111528 : if (JB_ERR_MEMORY == jb_error)
1276 : {
1277 0 : log_error(LOG_LEVEL_FATAL,
1278 : "Out of memory while enlisting re_filter job \'%s\' for filter %s.", buf, bl->name);
1279 : }
1280 111528 : assert(JB_ERR_OK == jb_error);
1281 :
1282 111528 : if (pcrs_job_is_dynamic(buf))
1283 : {
1284 : /*
1285 : * Dynamic pattern that might contain variables
1286 : * and has to be recompiled for every request
1287 : */
1288 0 : if (bl->joblist != NULL)
1289 : {
1290 0 : pcrs_free_joblist(bl->joblist);
1291 0 : bl->joblist = NULL;
1292 : }
1293 0 : bl->dynamic = 1;
1294 0 : log_error(LOG_LEVEL_RE_FILTER,
1295 : "Adding dynamic re_filter job \'%s\' to filter %s succeeded.", buf, bl->name);
1296 0 : freez(buf);
1297 0 : continue;
1298 : }
1299 111528 : else if (bl->dynamic)
1300 : {
1301 : /*
1302 : * A previous job was dynamic and as we
1303 : * recompile the whole filter anyway, it
1304 : * makes no sense to compile this job now.
1305 : */
1306 0 : log_error(LOG_LEVEL_RE_FILTER,
1307 : "Adding static re_filter job \'%s\' to dynamic filter %s succeeded.", buf, bl->name);
1308 0 : freez(buf);
1309 0 : continue;
1310 : }
1311 :
1312 111528 : if ((dummy = pcrs_compile_command(buf, &pcrs_error)) == NULL)
1313 : {
1314 0 : log_error(LOG_LEVEL_ERROR,
1315 : "Adding re_filter job \'%s\' to filter %s failed: %s",
1316 : buf, bl->name, pcrs_strerror(pcrs_error));
1317 0 : freez(buf);
1318 0 : continue;
1319 : }
1320 : else
1321 : {
1322 111528 : if (bl->joblist == NULL)
1323 : {
1324 37176 : bl->joblist = dummy;
1325 : }
1326 74352 : else if (NULL != lastjob)
1327 : {
1328 74352 : lastjob->next = dummy;
1329 : }
1330 111528 : lastjob = dummy;
1331 111528 : log_error(LOG_LEVEL_RE_FILTER, "Adding re_filter job \'%s\' to filter %s succeeded.", buf, bl->name);
1332 : }
1333 : }
1334 : else
1335 : {
1336 0 : log_error(LOG_LEVEL_ERROR,
1337 : "Ignoring job %s outside filter block in %s, line %lu",
1338 0 : buf, csp->config->re_filterfile[fileid], linenum);
1339 : }
1340 111528 : freez(buf);
1341 : }
1342 :
1343 6196 : fclose(fp);
1344 :
1345 : /*
1346 : * Schedule the now-obsolete old data for unloading
1347 : */
1348 6196 : if (NULL != current_re_filterfile[fileid])
1349 : {
1350 0 : current_re_filterfile[fileid]->unloader = unload_re_filterfile;
1351 : }
1352 :
1353 : /*
1354 : * Chain this file into the global list of loaded files
1355 : */
1356 6196 : fs->next = files->next;
1357 6196 : files->next = fs;
1358 6196 : current_re_filterfile[fileid] = fs;
1359 6196 : csp->rlist[fileid] = fs;
1360 :
1361 6196 : return(0);
1362 :
1363 0 : load_re_filterfile_error:
1364 0 : log_error(LOG_LEVEL_FATAL, "can't load re_filterfile '%s': %E",
1365 0 : csp->config->re_filterfile[fileid]);
1366 0 : return(-1);
1367 :
1368 : }
1369 :
1370 :
1371 : /*********************************************************************
1372 : *
1373 : * Function : add_loader
1374 : *
1375 : * Description : Called from `load_config'. Called once for each input
1376 : * file found in config.
1377 : *
1378 : * Parameters :
1379 : * 1 : loader = pointer to a function that can parse and load
1380 : * the appropriate config file.
1381 : * 2 : config = The configuration_spec to add the loader to.
1382 : *
1383 : * Returns : N/A
1384 : *
1385 : *********************************************************************/
1386 6196 : void add_loader(int (*loader)(struct client_state *),
1387 : struct configuration_spec * config)
1388 : {
1389 : int i;
1390 :
1391 9294 : for (i = 0; i < NLOADERS; i++)
1392 : {
1393 9294 : if (config->loaders[i] == NULL)
1394 : {
1395 6196 : config->loaders[i] = loader;
1396 6196 : break;
1397 : }
1398 : }
1399 :
1400 6196 : }
1401 :
1402 :
1403 : /*********************************************************************
1404 : *
1405 : * Function : run_loader
1406 : *
1407 : * Description : Called from `load_config' and `listen_loop'. This
1408 : * function keeps the "csp" current with any file mods
1409 : * since the last loop. If a file is unchanged, the
1410 : * loader functions do NOT reload the file.
1411 : *
1412 : * Parameters :
1413 : * 1 : csp = Current client state (buffers, headers, etc...)
1414 : * Must be non-null. Reads: "csp->config"
1415 : * Writes: various data members.
1416 : *
1417 : * Returns : 0 => Ok, everything else is an error.
1418 : *
1419 : *********************************************************************/
1420 6196 : int run_loader(struct client_state *csp)
1421 : {
1422 6196 : int ret = 0;
1423 : int i;
1424 :
1425 18588 : for (i = 0; i < NLOADERS; i++)
1426 : {
1427 18588 : if (csp->config->loaders[i] == NULL)
1428 : {
1429 6196 : break;
1430 : }
1431 12392 : ret |= (csp->config->loaders[i])(csp);
1432 : }
1433 6196 : return(ret);
1434 :
1435 : }
1436 :
1437 : /*********************************************************************
1438 : *
1439 : * Function : file_has_been_modified
1440 : *
1441 : * Description : Helper function to check if a file has been changed
1442 : *
1443 : * Parameters :
1444 : * 1 : filename = The name of the file to check
1445 : * 2 : last_known_modification = The time of the last known
1446 : * modification
1447 : *
1448 : * Returns : TRUE if the file has been changed,
1449 : * FALSE otherwise.
1450 : *
1451 : *********************************************************************/
1452 158180 : static int file_has_been_modified(const char *filename, time_t last_know_modification)
1453 : {
1454 : struct stat statbuf[1];
1455 :
1456 158180 : if (stat(filename, statbuf) < 0)
1457 : {
1458 : /* Error, probably file not found which counts as change. */
1459 0 : return 1;
1460 : }
1461 :
1462 158180 : return (last_know_modification != statbuf->st_mtime);
1463 : }
1464 :
1465 :
1466 : /*********************************************************************
1467 : *
1468 : * Function : any_loaded_file_changed
1469 : *
1470 : * Description : Helper function to check if any loaded file has been
1471 : * changed since the time it has been loaded.
1472 : *
1473 : * XXX: Should we cache the return value for x seconds?
1474 : *
1475 : * Parameters :
1476 : * 1 : files_to_check = List of files to check
1477 : *
1478 : * Returns : TRUE if any file has been changed,
1479 : * FALSE otherwise.
1480 : *
1481 : *********************************************************************/
1482 31636 : int any_loaded_file_changed(const struct client_state *csp)
1483 : {
1484 31636 : const struct file_list *file_to_check = csp->config->config_file_list;
1485 : int i;
1486 :
1487 31636 : if (file_has_been_modified(file_to_check->filename, file_to_check->lastmodified))
1488 : {
1489 0 : return TRUE;
1490 : }
1491 :
1492 3195236 : for (i = 0; i < MAX_AF_FILES; i++)
1493 : {
1494 3163600 : if (csp->actions_list[i])
1495 : {
1496 63272 : file_to_check = csp->actions_list[i];
1497 63272 : if (file_has_been_modified(file_to_check->filename, file_to_check->lastmodified))
1498 : {
1499 0 : return TRUE;
1500 : }
1501 : }
1502 : }
1503 :
1504 3195236 : for (i = 0; i < MAX_AF_FILES; i++)
1505 : {
1506 3163600 : if (csp->rlist[i])
1507 : {
1508 63272 : file_to_check = csp->rlist[i];
1509 63272 : if (file_has_been_modified(file_to_check->filename, file_to_check->lastmodified))
1510 : {
1511 0 : return TRUE;
1512 : }
1513 : }
1514 : }
1515 :
1516 : #ifdef FEATURE_TRUST
1517 31636 : if (csp->tlist)
1518 : {
1519 0 : if (file_has_been_modified(csp->tlist->filename, csp->tlist->lastmodified))
1520 : {
1521 0 : return TRUE;
1522 : }
1523 : }
1524 : #endif /* def FEATURE_TRUST */
1525 :
1526 31636 : return FALSE;
1527 : }
1528 :
1529 :
1530 : /*
1531 : Local Variables:
1532 : tab-width: 3
1533 : end:
1534 : */
|