Turns out trusted referrers above our arbitrary
[privoxy.git] / loaders.c
1 const char loaders_rcs[] = "$Id: loaders.c,v 1.55 2006/09/07 10:25:39 fabiankeil Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/loaders.c,v $
5  *
6  * Purpose     :  Functions to load and unload the various
7  *                configuration files.  Also contains code to manage
8  *                the list of active loaders, and to automatically
9  *                unload files that are no longer in use.
10  *
11  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
12  *                Privoxy team. http://www.privoxy.org/
13  *
14  *                Based on the Internet Junkbuster originally written
15  *                by and Copyright (C) 1997 Anonymous Coders and
16  *                Junkbusters Corporation.  http://www.junkbusters.com
17  *
18  *                This program is free software; you can redistribute it
19  *                and/or modify it under the terms of the GNU General
20  *                Public License as published by the Free Software
21  *                Foundation; either version 2 of the License, or (at
22  *                your option) any later version.
23  *
24  *                This program is distributed in the hope that it will
25  *                be useful, but WITHOUT ANY WARRANTY; without even the
26  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
27  *                PARTICULAR PURPOSE.  See the GNU General Public
28  *                License for more details.
29  *
30  *                The GNU General Public License should be included with
31  *                this file.  If not, you can view it at
32  *                http://www.gnu.org/copyleft/gpl.html
33  *                or write to the Free Software Foundation, Inc., 59
34  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
35  *
36  * Revisions   :
37  *    $Log: loaders.c,v $
38  *    Revision 1.55  2006/09/07 10:25:39  fabiankeil
39  *    Fix typo.
40  *
41  *    Revision 1.54  2006/09/07 10:22:20  fabiankeil
42  *    If too many trusted referrers are used,
43  *    print only one error message instead of logging
44  *    every single trusted referrer above the arbitrary
45  *    limit.
46  *
47  *    Revision 1.53  2006/08/31 16:25:06  fabiankeil
48  *    Work around a buffer overflow that caused Privoxy to
49  *    segfault if too many trusted referrers were used. Good
50  *    enough for now, but should be replaced with a real
51  *    solution after the next release.
52  *
53  *    Revision 1.52  2006/07/18 14:48:46  david__schmidt
54  *    Reorganizing the repository: swapping out what was HEAD (the old 3.1 branch)
55  *    with what was really the latest development (the v_3_0_branch branch)
56  *
57  *    Revision 1.50.2.8  2006/01/30 15:16:25  david__schmidt
58  *    Remove a little residual debugging info
59  *
60  *    Revision 1.50.2.7  2006/01/29 23:10:56  david__schmidt
61  *    Multiple filter file support
62  *
63  *    Revision 1.50.2.6  2003/10/24 10:17:54  oes
64  *    Nit: Allowed tabs as separators in filter headings
65  *
66  *    Revision 1.50.2.5  2003/05/08 15:19:15  oes
67  *    sweep: Made loop structure of sweep step mirror that of mark step
68  *
69  *    Revision 1.50.2.4  2003/05/06 15:57:12  oes
70  *    Bugfix: Update last_active pointer in sweep() before
71  *    leaving an active client. Closes bugs #724395, #727882
72  *
73  *    Revision 1.50.2.3  2002/11/20 17:12:30  oes
74  *    Ooops, forgot one change.
75  *
76  *    Revision 1.50.2.2  2002/11/20 14:38:15  oes
77  *    Fixed delayed/incomplete freeing of client resources and
78  *    simplified loop structure in sweep.
79  *    Thanks to Oliver Stoeneberg for the hint.
80  *
81  *    Revision 1.50.2.1  2002/07/26 15:19:24  oes
82  *    - PCRS jobs now chained in order of appearance. Previous
83  *      reverse chaining was counter-intuitive.
84  *    - Changed loglevel of PCRS job compile errors to
85  *      LOG_LEVEL_ERROR
86  *
87  *    Revision 1.50  2002/04/24 02:12:16  oes
88  *    Jon's multiple AF patch: Sweep now takes care of all AFs
89  *
90  *    Revision 1.49  2002/04/19 16:53:25  jongfoster
91  *    Optimize away a function call by using an equivalent macro
92  *
93  *    Revision 1.48  2002/04/05 00:56:09  gliptak
94  *    Correcting typo to clean up on realloc failure
95  *
96  *    Revision 1.47  2002/03/26 22:29:55  swa
97  *    we have a new homepage!
98  *
99  *    Revision 1.46  2002/03/24 13:25:43  swa
100  *    name change related issues
101  *
102  *    Revision 1.45  2002/03/16 23:54:06  jongfoster
103  *    Adding graceful termination feature, to help look for memory leaks.
104  *    If you enable this (which, by design, has to be done by hand
105  *    editing config.h) and then go to http://i.j.b/die, then the program
106  *    will exit cleanly after the *next* request.  It should free all the
107  *    memory that was used.
108  *
109  *    Revision 1.44  2002/03/16 21:51:00  jongfoster
110  *    Fixing free(NULL).
111  *
112  *    Revision 1.43  2002/03/16 20:28:34  oes
113  *    Added descriptions to the filters so users will know what they select in the cgi editor
114  *
115  *    Revision 1.42  2002/03/13 00:27:05  jongfoster
116  *    Killing warnings
117  *
118  *    Revision 1.41  2002/03/12 01:42:50  oes
119  *    Introduced modular filters
120  *
121  *    Revision 1.40  2002/03/08 17:46:04  jongfoster
122  *    Fixing int/size_t warnings
123  *
124  *    Revision 1.39  2002/03/07 03:46:17  oes
125  *    Fixed compiler warnings
126  *
127  *    Revision 1.38  2002/03/06 22:54:35  jongfoster
128  *    Automated function-comment nitpicking.
129  *
130  *    Revision 1.37  2002/03/03 15:07:49  oes
131  *    Re-enabled automatic config reloading
132  *
133  *    Revision 1.36  2002/01/22 23:46:18  jongfoster
134  *    Moving edit_read_line() and simple_read_line() to loaders.c, and
135  *    extending them to support reading MS-DOS, Mac and UNIX style files
136  *    on all platforms.
137  *
138  *    Modifying read_config_line() (without changing it's prototype) to
139  *    be a trivial wrapper for edit_read_line().  This means that we have
140  *    one function to read a line and handle comments, which is common
141  *    between the initialization code and the edit interface.
142  *
143  *    Revision 1.35  2002/01/17 21:03:08  jongfoster
144  *    Moving all our URL and URL pattern parsing code to urlmatch.c.
145  *
146  *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
147  *
148  *    Revision 1.34  2001/12/30 14:07:32  steudten
149  *    - Add signal handling (unix)
150  *    - Add SIGHUP handler (unix)
151  *    - Add creation of pidfile (unix)
152  *    - Add action 'top' in rc file (RH)
153  *    - Add entry 'SIGNALS' to manpage
154  *    - Add exit message to logfile (unix)
155  *
156  *    Revision 1.33  2001/11/13 00:16:38  jongfoster
157  *    Replacing references to malloc.h with the standard stdlib.h
158  *    (See ANSI or K&R 2nd Ed)
159  *
160  *    Revision 1.32  2001/11/07 00:02:13  steudten
161  *    Add line number in error output for lineparsing for
162  *    actionsfile and configfile.
163  *    Special handling for CLF added.
164  *
165  *    Revision 1.31  2001/10/26 17:39:01  oes
166  *    Removed csp->referrer
167  *    Moved ijb_isspace and ijb_tolower to project.h
168  *
169  *    Revision 1.30  2001/10/25 03:40:48  david__schmidt
170  *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
171  *    threads to call select() simultaneously.  So, it's time to do a real, live,
172  *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
173  *    (native). Both versions will work, but using __OS2__ offers multi-threading.
174  *
175  *    Revision 1.29  2001/10/23 21:38:53  jongfoster
176  *    Adding error-checking to create_url_spec()
177  *
178  *    Revision 1.28  2001/10/07 15:40:39  oes
179  *    Replaced 6 boolean members of csp with one bitmap (csp->flags)
180  *
181  *    Revision 1.27  2001/09/22 16:36:59  jongfoster
182  *    Removing unused parameter fs from read_config_line()
183  *
184  *    Revision 1.26  2001/09/22 14:05:22  jongfoster
185  *    Bugfix: Multiple escaped "#" characters in a configuration
186  *    file are now permitted.
187  *    Also removing 3 unused headers.
188  *
189  *    Revision 1.25  2001/09/13 22:44:03  jongfoster
190  *    Adding {} to an if statement
191  *
192  *    Revision 1.24  2001/07/30 22:08:36  jongfoster
193  *    Tidying up #defines:
194  *    - All feature #defines are now of the form FEATURE_xxx
195  *    - Permanently turned off WIN_GUI_EDIT
196  *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
197  *
198  *    Revision 1.23  2001/07/20 15:51:54  oes
199  *    Fixed indentation of prepocessor commands
200  *
201  *    Revision 1.22  2001/07/20 15:16:17  haroon
202  *    - per Guy's suggestion, added a while loop in sweep() to catch not just
203  *      the last inactive CSP but all other consecutive inactive CSPs after that
204  *      as well
205  *
206  *    Revision 1.21  2001/07/18 17:26:24  oes
207  *    Changed to conform to new pcrs interface
208  *
209  *    Revision 1.20  2001/07/17 13:07:01  oes
210  *    Fixed segv when last line in config files
211  *     lacked a terminating (\r)\n
212  *
213  *    Revision 1.19  2001/07/13 14:01:54  oes
214  *    Removed all #ifdef PCRS
215  *
216  *    Revision 1.18  2001/06/29 21:45:41  oes
217  *    Indentation, CRLF->LF, Tab-> Space
218  *
219  *    Revision 1.17  2001/06/29 13:31:51  oes
220  *    Various adaptions
221  *
222  *    Revision 1.16  2001/06/09 10:55:28  jongfoster
223  *    Changing BUFSIZ ==> BUFFER_SIZE
224  *
225  *    Revision 1.15  2001/06/07 23:14:14  jongfoster
226  *    Removing ACL and forward file loaders - these
227  *    files have been merged into the config file.
228  *    Cosmetic: Moving unloader funcs next to their
229  *    respective loader funcs
230  *
231  *    Revision 1.14  2001/06/01 03:27:04  oes
232  *    Fixed line continuation problem
233  *
234  *    Revision 1.13  2001/05/31 21:28:49  jongfoster
235  *    Removed all permissionsfile code - it's now called the actions
236  *    file, and (almost) all the code is in actions.c
237  *
238  *    Revision 1.12  2001/05/31 17:32:31  oes
239  *
240  *     - Enhanced domain part globbing with infix and prefix asterisk
241  *       matching and optional unanchored operation
242  *
243  *    Revision 1.11  2001/05/29 23:25:24  oes
244  *
245  *     - load_config_line() and load_permissions_file() now use chomp()
246  *
247  *    Revision 1.10  2001/05/29 09:50:24  jongfoster
248  *    Unified blocklist/imagelist/permissionslist.
249  *    File format is still under discussion, but the internal changes
250  *    are (mostly) done.
251  *
252  *    Also modified interceptor behaviour:
253  *    - We now intercept all URLs beginning with one of the following
254  *      prefixes (and *only* these prefixes):
255  *        * http://i.j.b/
256  *        * http://ijbswa.sf.net/config/
257  *        * http://ijbswa.sourceforge.net/config/
258  *    - New interceptors "home page" - go to http://i.j.b/ to see it.
259  *    - Internal changes so that intercepted and fast redirect pages
260  *      are not replaced with an image.
261  *    - Interceptors now have the option to send a binary page direct
262  *      to the client. (i.e. ijb-send-banner uses this)
263  *    - Implemented show-url-info interceptor.  (Which is why I needed
264  *      the above interceptors changes - a typical URL is
265  *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
266  *      The previous mechanism would not have intercepted that, and
267  *      if it had been intercepted then it then it would have replaced
268  *      it with an image.)
269  *
270  *    Revision 1.9  2001/05/26 17:12:07  jongfoster
271  *    Fatal errors loading configuration files now give better error messages.
272  *
273  *    Revision 1.8  2001/05/26 00:55:20  jongfoster
274  *    Removing duplicated code.  load_forwardfile() now uses create_url_spec()
275  *
276  *    Revision 1.7  2001/05/26 00:28:36  jongfoster
277  *    Automatic reloading of config file.
278  *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
279  *    Most of the global variables have been moved to a new
280  *    struct configuration_spec, accessed through csp->config->globalname
281  *    Most of the globals remaining are used by the Win32 GUI.
282  *
283  *    Revision 1.6  2001/05/23 12:27:33  oes
284  *
285  *    Fixed ugly indentation of my last changes
286  *
287  *    Revision 1.5  2001/05/23 10:39:05  oes
288  *    - Added support for escaping the comment character
289  *      in config files by a backslash
290  *    - Added support for line continuation in config
291  *      files
292  *    - Fixed a buffer overflow bug with long config lines
293  *
294  *    Revision 1.4  2001/05/22 18:56:28  oes
295  *    CRLF -> LF
296  *
297  *    Revision 1.3  2001/05/20 01:21:20  jongfoster
298  *    Version 2.9.4 checkin.
299  *    - Merged popupfile and cookiefile, and added control over PCRS
300  *      filtering, in new "permissionsfile".
301  *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
302  *      file error you now get a message box (in the Win32 GUI) rather
303  *      than the program exiting with no explanation.
304  *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
305  *      skipping.
306  *    - Removed tabs from "config"
307  *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
308  *    - Bumped up version number.
309  *
310  *    Revision 1.2  2001/05/17 23:01:01  oes
311  *     - Cleaned CRLF's from the sources and related files
312  *
313  *    Revision 1.1.1.1  2001/05/15 13:58:59  oes
314  *    Initial import of version 2.9.3 source tree
315  *
316  *
317  *********************************************************************/
318 \f
319
320 #include "config.h"
321
322 #include <stdio.h>
323 #include <stdlib.h>
324 #include <sys/types.h>
325 #include <string.h>
326 #include <errno.h>
327 #include <sys/stat.h>
328 #include <ctype.h>
329 #include <assert.h>
330
331 #if !defined(_WIN32) && !defined(__OS2__)
332 #include <unistd.h>
333 #endif
334
335 #include "project.h"
336 #include "list.h"
337 #include "loaders.h"
338 #include "filters.h"
339 #include "parsers.h"
340 #include "jcc.h"
341 #include "miscutil.h"
342 #include "errlog.h"
343 #include "actions.h"
344 #include "urlmatch.h"
345
346 const char loaders_h_rcs[] = LOADERS_H_VERSION;
347
348 /*
349  * Currently active files.
350  * These are also entered in the main linked list of files.
351  */
352
353 #ifdef FEATURE_TRUST
354 static struct file_list *current_trustfile      = NULL;
355 #endif /* def FEATURE_TRUST */
356
357 static int load_one_re_filterfile(struct client_state *csp, int fileid);
358
359 static struct file_list *current_re_filterfile[MAX_AF_FILES]  = {
360    NULL, NULL, NULL, NULL, NULL,
361    NULL, NULL, NULL, NULL, NULL
362 };
363
364
365
366 /*********************************************************************
367  *
368  * Function    :  sweep
369  *
370  * Description :  Basically a mark and sweep garbage collector, it is run
371  *                (by the parent thread) every once in a while to reclaim memory.
372  *
373  * It uses a mark and sweep strategy:
374  *   1) mark all files as inactive
375  *
376  *   2) check with each client:
377  *       if it is active,   mark its files as active
378  *       if it is inactive, free its resources
379  *
380  *   3) free the resources of all of the files that
381  *      are still marked as inactive (and are obsolete).
382  *
383  *   N.B. files that are not obsolete don't have an unloader defined.
384  *
385  * Parameters  :  None
386  *
387  * Returns     :  N/A
388  *
389  *********************************************************************/
390 void sweep(void)
391 {
392    struct file_list *fl, *nfl;
393    struct client_state *csp, *last_active;
394    int i;
395
396    /* clear all of the file's active flags */
397    for ( fl = files->next; NULL != fl; fl = fl->next )
398    {
399       fl->active = 0;
400    }
401
402    last_active = clients;
403    csp = clients->next;
404
405    while (NULL != csp)
406    {
407       if (csp->flags & CSP_FLAG_ACTIVE)
408       {
409          /* Mark this client's files as active */
410
411          /*
412           * Always have a configuration file.
413           * (Also note the slightly non-standard extra
414           * indirection here.)
415           */
416          csp->config->config_file_list->active = 1;
417
418          /* 
419           * Actions files
420           */
421          for (i = 0; i < MAX_AF_FILES; i++)
422          {
423             if (csp->actions_list[i])     
424             {
425                csp->actions_list[i]->active = 1;
426             }
427          }
428
429          /*
430           * Filter files
431           */
432          for (i = 0; i < MAX_AF_FILES; i++)
433          {
434             if (csp->rlist[i])     
435             {
436                csp->rlist[i]->active = 1;
437             }
438          }
439
440          /*
441           * Trust file
442           */
443 #ifdef FEATURE_TRUST
444          if (csp->tlist)
445          {
446             csp->tlist->active = 1;
447          }
448 #endif /* def FEATURE_TRUST */
449          
450          last_active = csp;
451          csp = csp->next;
452
453       }
454       else 
455       /*
456        * This client is not active. Free its resources.
457        */
458       {
459          last_active->next = csp->next;
460
461          freez(csp->ip_addr_str);
462          freez(csp->my_ip_addr_str);
463          freez(csp->my_hostname);
464          freez(csp->x_forwarded);
465          freez(csp->iob->buf);
466
467          free_http_request(csp->http);
468
469          destroy_list(csp->headers);
470          destroy_list(csp->cookie_list);
471
472          free_current_action(csp->action);
473
474 #ifdef FEATURE_STATISTICS
475          urls_read++;
476          if (csp->flags & CSP_FLAG_REJECTED)
477          {
478             urls_rejected++;
479          }
480 #endif /* def FEATURE_STATISTICS */
481
482          freez(csp);
483          
484          csp = last_active->next;
485       }
486    }
487
488    nfl = files;
489    fl = files->next;
490
491    while (fl != NULL)
492    {
493       if ( ( 0 == fl->active ) && ( NULL != fl->unloader ) )
494       {
495          nfl->next = fl->next;
496
497          (fl->unloader)(fl->f);
498
499          freez(fl->filename);
500          freez(fl);
501
502          fl = nfl->next;
503       }
504       else
505       {
506          nfl = fl;
507          fl = fl->next;
508       }
509    }
510
511 }
512
513
514 /*********************************************************************
515  *
516  * Function    :  check_file_changed
517  *
518  * Description :  Helper function to check if a file needs reloading.
519  *                If "current" is still current, return it.  Otherwise
520  *                allocates a new (zeroed) "struct file_list", fills
521  *                in the disk file name and timestamp, and returns it.
522  *
523  * Parameters  :
524  *          1  :  current = The file_list currently being used - will
525  *                          be checked to see if it is out of date.
526  *                          May be NULL (which is treated as out of
527  *                          date).
528  *          2  :  filename = Name of file to check.
529  *          3  :  newfl    = New file list. [Output only]
530  *                           This will be set to NULL, OR a struct
531  *                           file_list newly allocated on the
532  *                           heap, with the filename and lastmodified
533  *                           fields filled, and all others zeroed.
534  *
535  * Returns     :  If file unchanged: 0 (and sets newfl == NULL)
536  *                If file changed: 1 and sets newfl != NULL
537  *                On error: 1 and sets newfl == NULL
538  *
539  *********************************************************************/
540 int check_file_changed(const struct file_list * current,
541                        const char * filename,
542                        struct file_list ** newfl)
543 {
544    struct file_list *fs;
545    struct stat statbuf[1];
546
547    *newfl = NULL;
548
549    if (stat(filename, statbuf) < 0)
550    {
551       /* Error, probably file not found. */
552       return 1;
553    }
554
555    if (current
556        && (current->lastmodified == statbuf->st_mtime)
557        && (0 == strcmp(current->filename, filename)))
558    {
559       return 0;
560    }
561
562    fs = (struct file_list *)zalloc(sizeof(struct file_list));
563    if (fs == NULL)
564    {
565       /* Out of memory error */
566       return 1;
567    }
568
569
570    fs->filename = strdup(filename);
571    fs->lastmodified = statbuf->st_mtime;
572
573    if (fs->filename == NULL)
574    {
575       /* Out of memory error */
576       freez (fs);
577       return 1;
578    }
579    *newfl = fs;
580    return 1;
581 }
582
583
584 /*********************************************************************
585  *
586  * Function    :  simple_read_line
587  *
588  * Description :  Read a single line from a file and return it.
589  *                This is basically a version of fgets() that malloc()s
590  *                it's own line buffer.  Note that the buffer will
591  *                always be a multiple of BUFFER_SIZE bytes long.
592  *                Therefore if you are going to keep the string for
593  *                an extended period of time, you should probably
594  *                strdup() it and free() the original, to save memory.
595  *
596  *
597  * Parameters  :
598  *          1  :  dest = destination for newly malloc'd pointer to
599  *                line data.  Will be set to NULL on error.
600  *          2  :  fp = File to read from
601  *          3  :  newline = Standard for newlines in the file.
602  *                Will be unchanged if it's value on input is not
603  *                NEWLINE_UNKNOWN.
604  *                On output, may be changed from NEWLINE_UNKNOWN to
605  *                actual convention in file.
606  *
607  * Returns     :  JB_ERR_OK     on success
608  *                JB_ERR_MEMORY on out-of-memory
609  *                JB_ERR_FILE   on EOF.
610  *
611  *********************************************************************/
612 jb_err simple_read_line(FILE *fp, char **dest, int *newline)
613 {
614    size_t len = 0;
615    size_t buflen = BUFFER_SIZE;
616    char * buf;
617    char * p;
618    int ch;
619    int realnewline = NEWLINE_UNKNOWN;
620
621    if (NULL == (buf = malloc(buflen)))
622    {
623       return JB_ERR_MEMORY;
624    }
625
626    p = buf;
627
628 /*
629  * Character codes.  If you have a wierd compiler and the following are
630  * incorrect, you also need to fix NEWLINE() in loaders.h
631  */
632 #define CHAR_CR '\r' /* ASCII 13 */
633 #define CHAR_LF '\n' /* ASCII 10 */
634
635    for (;;)
636    {
637       ch = getc(fp);
638       if (ch == EOF)
639       {
640          if (len > 0)
641          {
642             *p = '\0';
643             *dest = buf;
644             return JB_ERR_OK;
645          }
646          else
647          {
648             free(buf);
649             *dest = NULL;
650             return JB_ERR_FILE;
651          }
652       }
653       else if (ch == CHAR_CR)
654       {
655          ch = getc(fp);
656          if (ch == CHAR_LF)
657          {
658             if (*newline == NEWLINE_UNKNOWN)
659             {
660                *newline = NEWLINE_DOS;
661             }
662          }
663          else
664          {
665             if (ch != EOF)
666             {
667                ungetc(ch, fp);
668             }
669             if (*newline == NEWLINE_UNKNOWN)
670             {
671                *newline = NEWLINE_MAC;
672             }
673          }
674          *p = '\0';
675          *dest = buf;
676          if (*newline == NEWLINE_UNKNOWN)
677          {
678             *newline = realnewline;
679          }
680          return JB_ERR_OK;
681       }
682       else if (ch == CHAR_LF)
683       {
684          *p = '\0';
685          *dest = buf;
686          if (*newline == NEWLINE_UNKNOWN)
687          {
688             *newline = NEWLINE_UNIX;
689          }
690          return JB_ERR_OK;
691       }
692       else if (ch == 0)
693       {
694          *p = '\0';
695          *dest = buf;
696          return JB_ERR_OK;
697       }
698
699       *p++ = ch;
700
701       if (++len >= buflen)
702       {
703          buflen += BUFFER_SIZE;
704          if (NULL == (p = realloc(buf, buflen)))
705          {
706             free(buf);
707             return JB_ERR_MEMORY;
708          }
709          buf = p;
710          p = buf + len;
711       }
712    }
713 }
714
715
716 /*********************************************************************
717  *
718  * Function    :  edit_read_line
719  *
720  * Description :  Read a single non-empty line from a file and return
721  *                it.  Trims comments, leading and trailing whitespace
722  *                and respects escaping of newline and comment char.
723  *                Provides the line in 2 alternative forms: raw and
724  *                preprocessed.
725  *                - raw is the raw data read from the file.  If the
726  *                  line is not modified, then this should be written
727  *                  to the new file.
728  *                - prefix is any comments and blank lines that were
729  *                  read from the file.  If the line is modified, then
730  *                  this should be written out to the file followed
731  *                  by the modified data.  (If this string is non-empty
732  *                  then it will have a newline at the end).
733  *                - data is the actual data that will be parsed
734  *                  further by appropriate routines.
735  *                On EOF, the 3 strings will all be set to NULL and
736  *                0 will be returned.
737  *
738  * Parameters  :
739  *          1  :  fp = File to read from
740  *          2  :  raw_out = destination for newly malloc'd pointer to
741  *                raw line data.  May be NULL if you don't want it.
742  *          3  :  prefix_out = destination for newly malloc'd pointer to
743  *                comments.  May be NULL if you don't want it.
744  *          4  :  data_out = destination for newly malloc'd pointer to
745  *                line data with comments and leading/trailing spaces
746  *                removed, and line continuation performed.  May be
747  *                NULL if you don't want it.
748  *          5  :  newline = Standard for newlines in the file.
749  *                On input, set to value to use or NEWLINE_UNKNOWN.
750  *                On output, may be changed from NEWLINE_UNKNOWN to
751  *                actual convention in file.  May be NULL if you
752  *                don't want it.
753  *          6  :  line_number = Line number in file.  In "lines" as
754  *                reported by a text editor, not lines containing data.
755  *
756  * Returns     :  JB_ERR_OK     on success
757  *                JB_ERR_MEMORY on out-of-memory
758  *                JB_ERR_FILE   on EOF.
759  *
760  *********************************************************************/
761 jb_err edit_read_line(FILE *fp,
762                       char **raw_out,
763                       char **prefix_out,
764                       char **data_out,
765                       int *newline,
766                       unsigned long *line_number)
767 {
768    char *p;          /* Temporary pointer   */
769    char *linebuf;    /* Line read from file */
770    char *linestart;  /* Start of linebuf, usually first non-whitespace char */
771    int contflag = 0; /* Nonzero for line continuation - i.e. line ends '\' */
772    int is_empty = 1; /* Flag if not got any data yet */
773    char *raw    = NULL; /* String to be stored in raw_out    */
774    char *prefix = NULL; /* String to be stored in prefix_out */
775    char *data   = NULL; /* String to be stored in data_out   */
776    int scrapnewline;    /* Used for (*newline) if newline==NULL */
777    jb_err rval = JB_ERR_OK;
778
779    assert(fp);
780    assert(raw_out || data_out);
781    assert(newline == NULL
782        || *newline == NEWLINE_UNKNOWN
783        || *newline == NEWLINE_UNIX
784        || *newline == NEWLINE_DOS
785        || *newline == NEWLINE_MAC);
786
787    if (newline == NULL)
788    {
789       scrapnewline = NEWLINE_UNKNOWN;
790       newline = &scrapnewline;
791    }
792
793    /* Set output parameters to NULL */
794    if (raw_out)
795    {
796       *raw_out    = NULL;
797    }
798    if (prefix_out)
799    {
800       *prefix_out = NULL;
801    }
802    if (data_out)
803    {
804       *data_out   = NULL;
805    }
806
807    /* Set string variables to new, empty strings. */
808
809    if (raw_out)
810    {
811       if ((raw = malloc(1)) == NULL)
812       {
813          return JB_ERR_MEMORY;
814       }
815       *raw = '\0';
816    }
817    if (prefix_out)
818    {
819       if ((prefix = malloc(1)) == NULL)
820       {
821          freez(raw);
822          return JB_ERR_MEMORY;
823       }
824       *prefix = '\0';
825    }
826    if (data_out)
827    {
828       if ((data = malloc(1)) == NULL)
829       {
830          freez(raw);
831          freez(prefix);
832          return JB_ERR_MEMORY;
833       }
834       *data = '\0';
835    }
836
837    /* Main loop.  Loop while we need more data & it's not EOF. */
838
839    while ( (contflag || is_empty)
840         && (JB_ERR_OK == (rval = simple_read_line(fp, &linebuf, newline))))
841    {
842       if (line_number)
843       {
844          (*line_number)++;
845       }
846       if (raw)
847       {
848          string_append(&raw,linebuf);
849          if (string_append(&raw,NEWLINE(*newline)))
850          {
851             freez(prefix);
852             freez(data);
853             free(linebuf);
854             return JB_ERR_MEMORY;
855          }
856       }
857
858       /* Line continuation? Trim escape and set flag. */
859       p = linebuf + strlen(linebuf) - 1;
860       contflag = ((*linebuf != '\0') && (*p == '\\'));
861       if (contflag)
862       {
863          *p = '\0';
864       }
865
866       /* Trim leading spaces if we're at the start of the line */
867       linestart = linebuf;
868       if (*data == '\0')
869       {
870          /* Trim leading spaces */
871          while (*linestart && isspace((int)(unsigned char)*linestart))
872          {
873             linestart++;
874          }
875       }
876
877       /* Handle comment characters. */
878       p = linestart;
879       while ((p = strchr(p, '#')) != NULL)
880       {
881          /* Found a comment char.. */
882          if ((p != linebuf) && (*(p-1) == '\\'))
883          {
884             /* ..and it's escaped, left-shift the line over the escape. */
885             char *q = p - 1;
886             while ((*q = *(q + 1)) != '\0')
887             {
888                q++;
889             }
890             /* Now scan from just after the "#". */
891          }
892          else
893          {
894             /* Real comment.  Save it... */
895             if (p == linestart)
896             {
897                /* Special case:  Line only contains a comment, so all the
898                 * previous whitespace is considered part of the comment.
899                 * Undo the whitespace skipping, if any.
900                 */
901                linestart = linebuf;
902                p = linestart;
903             }
904             if (prefix)
905             {
906                string_append(&prefix,p);
907                if (string_append(&prefix, NEWLINE(*newline)))
908                {
909                   freez(raw);
910                   freez(data);
911                   free(linebuf);
912                   return JB_ERR_MEMORY;
913                }
914             }
915
916             /* ... and chop off the rest of the line */
917             *p = '\0';
918          }
919       } /* END while (there's a # character) */
920
921       /* Write to the buffer */
922       if (*linestart)
923       {
924          is_empty = 0;
925          if (data)
926          {
927             if (string_append(&data, linestart))
928             {
929                freez(raw);
930                freez(prefix);
931                free(linebuf);
932                return JB_ERR_MEMORY;
933             }
934          }
935       }
936
937       free(linebuf);
938    } /* END while(we need more data) */
939
940    /* Handle simple_read_line() errors - ignore EOF */
941    if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
942    {
943       freez(raw);
944       freez(prefix);
945       freez(data);
946       return rval;
947    }
948
949    if (raw ? (*raw == '\0') : is_empty)
950    {
951       /* EOF and no data there.  (Definition of "data" depends on whether
952        * the caller cares about "raw" or just "data").
953        */
954
955       freez(raw);
956       freez(prefix);
957       freez(data);
958
959       return JB_ERR_FILE;
960    }
961    else
962    {
963       /* Got at least some data */
964
965       /* Remove trailing whitespace */
966       chomp(data);
967
968       if (raw_out)
969       {
970          *raw_out    = raw;
971       }
972       else
973       {
974          freez(raw);
975       }
976       if (prefix_out)
977       {
978          *prefix_out = prefix;
979       }
980       else
981       {
982          freez(prefix);
983       }
984       if (data_out)
985       {
986          *data_out   = data;
987       }
988       else
989       {
990          freez(data);
991       }
992       return JB_ERR_OK;
993    }
994 }
995
996
997 /*********************************************************************
998  *
999  * Function    :  read_config_line
1000  *
1001  * Description :  Read a single non-empty line from a file and return
1002  *                it.  Trims comments, leading and trailing whitespace
1003  *                and respects escaping of newline and comment char.
1004  *
1005  * Parameters  :
1006  *          1  :  buf = Buffer to use.
1007  *          2  :  buflen = Size of buffer in bytes.
1008  *          3  :  fp = File to read from
1009  *          4  :  linenum = linenumber in file
1010  *
1011  * Returns     :  NULL on EOF or error
1012  *                Otherwise, returns buf.
1013  *
1014  *********************************************************************/
1015 char *read_config_line(char *buf, size_t buflen, FILE *fp, unsigned long *linenum)
1016 {
1017    jb_err err;
1018    char *buf2 = NULL;
1019    err = edit_read_line(fp, NULL, NULL, &buf2, NULL, linenum);
1020    if (err)
1021    {
1022       if (err == JB_ERR_MEMORY)
1023       {
1024          log_error(LOG_LEVEL_FATAL, "Out of memory loading a config file");
1025       }
1026       return NULL;
1027    }
1028    else
1029    {
1030       assert(buf2);
1031       assert(strlen(buf2) + 1U < buflen);
1032       strncpy(buf, buf2, buflen - 1);
1033       free(buf2);
1034       buf[buflen - 1] = '\0';
1035       return buf;
1036    }
1037 }
1038
1039
1040 #ifdef FEATURE_TRUST
1041 /*********************************************************************
1042  *
1043  * Function    :  unload_trustfile
1044  *
1045  * Description :  Unloads a trustfile.
1046  *
1047  * Parameters  :
1048  *          1  :  f = the data structure associated with the trustfile.
1049  *
1050  * Returns     :  N/A
1051  *
1052  *********************************************************************/
1053 static void unload_trustfile(void *f)
1054 {
1055    struct block_spec *cur = (struct block_spec *)f;
1056    struct block_spec *next;
1057
1058    while (cur != NULL)
1059    {
1060       next = cur->next;
1061
1062       free_url_spec(cur->url);
1063       free(cur);
1064
1065       cur = next;
1066    }
1067
1068 }
1069
1070
1071 #ifdef FEATURE_GRACEFUL_TERMINATION
1072 /*********************************************************************
1073  *
1074  * Function    :  unload_current_trust_file
1075  *
1076  * Description :  Unloads current trust file - reset to state at
1077  *                beginning of program.
1078  *
1079  * Parameters  :  None
1080  *
1081  * Returns     :  N/A
1082  *
1083  *********************************************************************/
1084 void unload_current_trust_file(void)
1085 {
1086    if (current_trustfile)
1087    {
1088       current_trustfile->unloader = unload_trustfile;
1089       current_trustfile = NULL;
1090    }
1091 }
1092 #endif /* FEATURE_GRACEFUL_TERMINATION */
1093
1094
1095 /*********************************************************************
1096  *
1097  * Function    :  load_trustfile
1098  *
1099  * Description :  Read and parse a trustfile and add to files list.
1100  *
1101  * Parameters  :
1102  *          1  :  csp = Current client state (buffers, headers, etc...)
1103  *
1104  * Returns     :  0 => Ok, everything else is an error.
1105  *
1106  *********************************************************************/
1107 int load_trustfile(struct client_state *csp)
1108 {
1109    FILE *fp;
1110
1111    struct block_spec *b, *bl;
1112    struct url_spec **tl;
1113
1114    char  buf[BUFFER_SIZE], *p, *q;
1115    int reject, trusted;
1116    struct file_list *fs;
1117    unsigned long linenum = 0;
1118    int trusted_referrers = 0;
1119
1120    if (!check_file_changed(current_trustfile, csp->config->trustfile, &fs))
1121    {
1122       /* No need to load */
1123       if (csp)
1124       {
1125          csp->tlist = current_trustfile;
1126       }
1127       return(0);
1128    }
1129    if (!fs)
1130    {
1131       goto load_trustfile_error;
1132    }
1133
1134    fs->f = bl = (struct block_spec *)zalloc(sizeof(*bl));
1135    if (bl == NULL)
1136    {
1137       goto load_trustfile_error;
1138    }
1139
1140    if ((fp = fopen(csp->config->trustfile, "r")) == NULL)
1141    {
1142       goto load_trustfile_error;
1143    }
1144
1145    tl = csp->config->trust_list;
1146
1147    while (read_config_line(buf, sizeof(buf), fp, &linenum) != NULL)
1148    {
1149       trusted = 0;
1150       reject  = 1;
1151
1152       if (*buf == '+')
1153       {
1154          trusted = 1;
1155          *buf = '~';
1156       }
1157
1158       if (*buf == '~')
1159       {
1160          reject = 0;
1161          p = buf;
1162          q = p+1;
1163          while ((*p++ = *q++) != '\0')
1164          {
1165             /* nop */
1166          }
1167       }
1168
1169       /* skip blank lines */
1170       if (*buf == '\0')
1171       {
1172          continue;
1173       }
1174
1175       /* allocate a new node */
1176       if ((b = zalloc(sizeof(*b))) == NULL)
1177       {
1178          fclose(fp);
1179          goto load_trustfile_error;
1180       }
1181
1182       /* add it to the list */
1183       b->next  = bl->next;
1184       bl->next = b;
1185
1186       b->reject = reject;
1187
1188       /* Save the URL pattern */
1189       if (create_url_spec(b->url, buf))
1190       {
1191          fclose(fp);
1192          goto load_trustfile_error;
1193       }
1194
1195       /*
1196        * save a pointer to URL's spec in the list of trusted URL's, too
1197        */
1198       if (trusted)
1199       {
1200          if(++trusted_referrers < MAX_TRUSTED_REFERRERS)
1201          {
1202             *tl++ = b->url;
1203          }
1204       }
1205    }
1206
1207    if(trusted_referrers >= MAX_TRUSTED_REFERRERS) 
1208    {
1209       /*
1210        * FIXME: ... after Privoxy 3.0.4 is out.
1211        */
1212        log_error(LOG_LEVEL_ERROR, "Too many trusted referrers. Current limit is %d, you are using %d.\n"
1213           "  Additional trusted referrers are treated like ordinary trusted URLs.\n"
1214           "  (You can increase this limit by changing MAX_TRUSTED_REFERRERS in project.h and recompiling).",
1215           MAX_TRUSTED_REFERRERS, trusted_referrers);
1216    }
1217
1218    *tl = NULL;
1219
1220    fclose(fp);
1221
1222    /* the old one is now obsolete */
1223    if (current_trustfile)
1224    {
1225       current_trustfile->unloader = unload_trustfile;
1226    }
1227
1228    fs->next    = files->next;
1229    files->next = fs;
1230    current_trustfile = fs;
1231
1232    if (csp)
1233    {
1234       csp->tlist = fs;
1235    }
1236
1237    return(0);
1238
1239 load_trustfile_error:
1240    log_error(LOG_LEVEL_FATAL, "can't load trustfile '%s': %E",
1241              csp->config->trustfile);
1242    return(-1);
1243
1244 }
1245 #endif /* def FEATURE_TRUST */
1246
1247
1248 /*********************************************************************
1249  *
1250  * Function    :  unload_re_filterfile
1251  *
1252  * Description :  Unload the re_filter list by freeing all chained
1253  *                re_filterfile specs and their data.
1254  *
1255  * Parameters  :
1256  *          1  :  f = the data structure associated with the filterfile.
1257  *
1258  * Returns     :  N/A
1259  *
1260  *********************************************************************/
1261 static void unload_re_filterfile(void *f)
1262 {
1263    struct re_filterfile_spec *a, *b = (struct re_filterfile_spec *)f;
1264
1265    while (b != NULL)
1266    {
1267       a = b->next;
1268
1269       destroy_list(b->patterns);
1270       pcrs_free_joblist(b->joblist);
1271       freez(b->name);
1272       freez(b->description);
1273       freez(b);
1274
1275       b = a;
1276    }
1277
1278    return;
1279 }
1280
1281
1282 #ifdef FEATURE_GRACEFUL_TERMINATION
1283 /*********************************************************************
1284  *
1285  * Function    :  unload_current_re_filterfile
1286  *
1287  * Description :  Unloads current re_filter file - reset to state at
1288  *                beginning of program.
1289  *
1290  * Parameters  :  None
1291  *
1292  * Returns     :  N/A
1293  *
1294  *********************************************************************/
1295 void unload_current_re_filterfile(void)
1296 {
1297    int i;
1298
1299    for (i = 0; i < MAX_AF_FILES; i++)
1300    {
1301       if (current_re_filterfile[i])
1302       {
1303          current_re_filterfile[i]->unloader = unload_re_filterfile;
1304          current_re_filterfile[i] = NULL;
1305       }
1306    }
1307 }
1308 #endif
1309
1310
1311 /*********************************************************************
1312  *
1313  * Function    :  load_re_filterfile
1314  *
1315  * Description :  Load the re_filterfile. 
1316  *                Generate a chained list of re_filterfile_spec's from
1317  *                the "FILTER: " blocks, compiling all their substitutions
1318  *                into chained lists of pcrs_job structs.
1319  *
1320  * Parameters  :
1321  *          1  :  csp = Current client state (buffers, headers, etc...)
1322  *
1323  * Returns     :  0 => Ok, everything else is an error.
1324  *
1325  *********************************************************************/
1326 int load_re_filterfile(struct client_state *csp)
1327 {
1328    int i;
1329    int result;
1330
1331    for (i = 0; i < MAX_AF_FILES; i++)
1332    {
1333       if (csp->config->re_filterfile[i])
1334       {
1335          result = load_one_re_filterfile(csp, i);
1336          if (result)
1337          {
1338             return result;
1339          }
1340       }
1341       else if (current_re_filterfile[i])
1342       {
1343          current_re_filterfile[i]->unloader = unload_re_filterfile;
1344          current_re_filterfile[i] = NULL;
1345       }
1346    }
1347
1348    return 0;
1349 }
1350
1351 /*********************************************************************
1352  *
1353  * Function    :  load_one_re_filterfile
1354  *
1355  * Description :  Load a re_filterfile. 
1356  *                Generate a chained list of re_filterfile_spec's from
1357  *                the "FILTER: " blocks, compiling all their substitutions
1358  *                into chained lists of pcrs_job structs.
1359  *
1360  * Parameters  :
1361  *          1  :  csp = Current client state (buffers, headers, etc...)
1362  *
1363  * Returns     :  0 => Ok, everything else is an error.
1364  *
1365  *********************************************************************/
1366 int load_one_re_filterfile(struct client_state *csp, int fileid)
1367 {
1368    FILE *fp;
1369
1370    struct re_filterfile_spec *new_bl, *bl = NULL;
1371    struct file_list *fs;
1372
1373    char  buf[BUFFER_SIZE];
1374    int error;
1375    unsigned long linenum = 0;
1376    pcrs_job *dummy, *lastjob = NULL;
1377
1378    /*
1379     * No need to reload if unchanged
1380     */
1381    if (!check_file_changed(current_re_filterfile[fileid], csp->config->re_filterfile[fileid], &fs))
1382    {
1383       if (csp)
1384       {
1385          csp->rlist[fileid] = current_re_filterfile[fileid];
1386       }
1387       return(0);
1388    }
1389    if (!fs)
1390    {
1391       goto load_re_filterfile_error;
1392    }
1393
1394    /* 
1395     * Open the file or fail
1396     */
1397    if ((fp = fopen(csp->config->re_filterfile[fileid], "r")) == NULL)
1398    {
1399       goto load_re_filterfile_error;
1400    }
1401
1402    /* 
1403     * Read line by line
1404     */
1405    while (read_config_line(buf, sizeof(buf), fp, &linenum) != NULL)
1406    {
1407       /*
1408        * If this is the head of a new filter block, make it a
1409        * re_filterfile spec of its own and chain it to the list:
1410        */
1411       if (strncmp(buf, "FILTER:", 7) == 0)
1412       {
1413          new_bl = (struct re_filterfile_spec  *)zalloc(sizeof(*bl));
1414          if (new_bl == NULL)
1415          {
1416             goto load_re_filterfile_error;
1417          }
1418
1419          new_bl->name = chomp(buf + 7);
1420
1421          if (NULL != (new_bl->description = strpbrk(new_bl->name, " \t")))
1422          {
1423             *new_bl->description++ = '\0';
1424             new_bl->description = strdup(chomp(new_bl->description));
1425          }
1426          else
1427          {
1428             new_bl->description = strdup("No description available for this filter");
1429          }
1430
1431          new_bl->name = strdup(chomp(new_bl->name));
1432          
1433          /*
1434           * If this is the first filter block, chain it
1435           * to the file_list rather than its (nonexistant)
1436           * predecessor
1437           */
1438          if (fs->f == NULL)
1439          {
1440             fs->f = new_bl;
1441          }
1442          else
1443          {
1444             bl->next = new_bl;
1445          }
1446          bl = new_bl;
1447
1448          log_error(LOG_LEVEL_RE_FILTER, "Reading in filter \"%s\" (\"%s\")", bl->name, bl->description);
1449
1450          continue;
1451       }
1452
1453       /* 
1454        * Else, save the expression, make it a pcrs_job
1455        * and chain it into the current filter's joblist 
1456        */
1457       if (bl != NULL)
1458       {
1459          enlist(bl->patterns, buf);
1460
1461          if ((dummy = pcrs_compile_command(buf, &error)) == NULL)
1462          {
1463             log_error(LOG_LEVEL_ERROR,
1464                       "Adding re_filter job %s to filter %s failed with error %d.", buf, bl->name, error);
1465             continue;
1466          }
1467          else
1468          {
1469             if (bl->joblist == NULL)
1470             {
1471                bl->joblist = dummy;
1472             }
1473             else
1474             {
1475                lastjob->next = dummy;
1476             }
1477             lastjob = dummy;
1478             log_error(LOG_LEVEL_RE_FILTER, "Adding re_filter job %s to filter %s succeeded.", buf, bl->name);
1479          }
1480       }
1481       else
1482       {
1483          log_error(LOG_LEVEL_ERROR, "Ignoring job %s outside filter block in %s, line %d", buf, csp->config->re_filterfile, linenum);
1484       }
1485    }
1486
1487    fclose(fp);
1488
1489    /* 
1490     * Schedule the now-obsolete old data for unloading
1491     */
1492    if ( NULL != current_re_filterfile[fileid] )
1493    {
1494       current_re_filterfile[fileid]->unloader = unload_re_filterfile;
1495    }
1496
1497    /*
1498     * Chain this file into the global list of loaded files
1499     */
1500    fs->next    = files->next;
1501    files->next = fs;
1502    current_re_filterfile[fileid] = fs;
1503
1504    if (csp)
1505    {
1506       csp->rlist[fileid] = fs;
1507    }
1508
1509    return( 0 );
1510
1511 load_re_filterfile_error:
1512    log_error(LOG_LEVEL_FATAL, "can't load re_filterfile '%s': %E",
1513              csp->config->re_filterfile[fileid]);
1514    return(-1);
1515
1516 }
1517
1518
1519 /*********************************************************************
1520  *
1521  * Function    :  add_loader
1522  *
1523  * Description :  Called from `load_config'.  Called once for each input
1524  *                file found in config.
1525  *
1526  * Parameters  :
1527  *          1  :  loader = pointer to a function that can parse and load
1528  *                the appropriate config file.
1529  *          2  :  config = The configuration_spec to add the loader to.
1530  *
1531  * Returns     :  N/A
1532  *
1533  *********************************************************************/
1534 void add_loader(int (*loader)(struct client_state *),
1535                 struct configuration_spec * config)
1536 {
1537    int i;
1538
1539    for (i=0; i < NLOADERS; i++)
1540    {
1541       if (config->loaders[i] == NULL)
1542       {
1543          config->loaders[i] = loader;
1544          break;
1545       }
1546    }
1547
1548 }
1549
1550
1551 /*********************************************************************
1552  *
1553  * Function    :  run_loader
1554  *
1555  * Description :  Called from `load_config' and `listen_loop'.  This
1556  *                function keeps the "csp" current with any file mods
1557  *                since the last loop.  If a file is unchanged, the
1558  *                loader functions do NOT reload the file.
1559  *
1560  * Parameters  :
1561  *          1  :  csp = Current client state (buffers, headers, etc...)
1562  *                      Must be non-null.  Reads: "csp->config"
1563  *                      Writes: various data members.
1564  *
1565  * Returns     :  0 => Ok, everything else is an error.
1566  *
1567  *********************************************************************/
1568 int run_loader(struct client_state *csp)
1569 {
1570    int ret = 0;
1571    int i;
1572
1573    for (i=0; i < NLOADERS; i++)
1574    {
1575       if (csp->config->loaders[i] == NULL)
1576       {
1577          break;
1578       }
1579       ret |= (csp->config->loaders[i])(csp);
1580    }
1581    return(ret);
1582
1583 }
1584
1585
1586 /*
1587   Local Variables:
1588   tab-width: 3
1589   end:
1590 */