First version of CGI-based edit interface. This is very much a
[privoxy.git] / cgi.c
1 const char cgi_rcs[] = "$Id: cgi.c,v 1.25 2001/09/16 15:02:35 jongfoster Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/cgi.c,v $
5  *
6  * Purpose     :  Declares functions to intercept request, generate
7  *                html or gif answers, and to compose HTTP resonses.
8  *                
9  *                Functions declared include:
10  * 
11  *
12  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
13  *                IJBSWA team.  http://ijbswa.sourceforge.net
14  *
15  *                Based on the Internet Junkbuster originally written
16  *                by and Copyright (C) 1997 Anonymous Coders and 
17  *                Junkbusters Corporation.  http://www.junkbusters.com
18  *
19  *                This program is free software; you can redistribute it 
20  *                and/or modify it under the terms of the GNU General
21  *                Public License as published by the Free Software
22  *                Foundation; either version 2 of the License, or (at
23  *                your option) any later version.
24  *
25  *                This program is distributed in the hope that it will
26  *                be useful, but WITHOUT ANY WARRANTY; without even the
27  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
28  *                PARTICULAR PURPOSE.  See the GNU General Public
29  *                License for more details.
30  *
31  *                The GNU General Public License should be included with
32  *                this file.  If not, you can view it at
33  *                http://www.gnu.org/copyleft/gpl.html
34  *                or write to the Free Software Foundation, Inc., 59
35  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
36  *
37  * Revisions   :
38  *    $Log: cgi.c,v $
39  *    Revision 1.25  2001/09/16 15:02:35  jongfoster
40  *    Adding i.j.b/robots.txt.
41  *    Inlining add_stats() since it's only ever called from one place.
42  *
43  *    Revision 1.24  2001/09/16 11:38:01  jongfoster
44  *    Splitting fill_template() into 2 functions:
45  *    template_load() loads the file
46  *    template_fill() performs the PCRS regexps.
47  *    This is because the CGI edit interface has a "table row"
48  *    template which is used many times in the page - this
49  *    change means it's only loaded from disk once.
50  *
51  *    Revision 1.23  2001/09/16 11:16:05  jongfoster
52  *    Better error handling in dispatch_cgi() and parse_cgi_parameters()
53  *
54  *    Revision 1.22  2001/09/16 11:00:10  jongfoster
55  *    New function alloc_http_response, for symmetry with free_http_response
56  *
57  *    Revision 1.21  2001/09/13 23:53:03  jongfoster
58  *    Support for both static and dynamically generated CGI pages.
59  *    Correctly setting Last-Modified: and Expires: HTTP headers.
60  *
61  *    Revision 1.20  2001/09/13 23:40:36  jongfoster
62  *    (Cosmetic only) Indentation correction
63  *
64  *    Revision 1.19  2001/09/13 23:31:25  jongfoster
65  *    Moving image data to cgi.c rather than cgi.h.
66  *
67  *    Revision 1.18  2001/08/05 16:06:20  jongfoster
68  *    Modifiying "struct map" so that there are now separate header and
69  *    "map_entry" structures.  This means that functions which modify a
70  *    map no longer need to return a pointer to the modified map.
71  *    Also, it no longer reverses the order of the entries (which may be
72  *    important with some advanced template substitutions).
73  *
74  *    Revision 1.17  2001/08/05 15:57:38  oes
75  *    Adapted finish_http_response to new list_to_text
76  *
77  *    Revision 1.16  2001/08/01 21:33:18  jongfoster
78  *    Changes to fill_template() that reduce memory usage without having
79  *    an impact on performance.  I also renamed some variables so as not
80  *    to clash with the C++ keywords "new" and "template".
81  *
82  *    Revision 1.15  2001/08/01 21:19:22  jongfoster
83  *    Moving file version information to a separate CGI page.
84  *
85  *    Revision 1.14  2001/08/01 00:19:03  jongfoster
86  *    New function: map_conditional() for an if-then-else syntax.
87  *    Changing to use new version of show_defines()
88  *
89  *    Revision 1.13  2001/07/30 22:08:36  jongfoster
90  *    Tidying up #defines:
91  *    - All feature #defines are now of the form FEATURE_xxx
92  *    - Permanently turned off WIN_GUI_EDIT
93  *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
94  *
95  *    Revision 1.12  2001/07/29 18:47:05  jongfoster
96  *    Adding missing #include "loadcfg.h"
97  *
98  *    Revision 1.11  2001/07/18 17:24:37  oes
99  *    Changed to conform to new pcrs interface
100  *
101  *    Revision 1.10  2001/07/13 13:53:13  oes
102  *    Removed all #ifdef PCRS and related code
103  *
104  *    Revision 1.9  2001/06/29 21:45:41  oes
105  *    Indentation, CRLF->LF, Tab-> Space
106  *
107  *    Revision 1.8  2001/06/29 13:21:46  oes
108  *    - Cosmetics: renamed and reordered functions, variables,
109  *      texts, improved comments  etc
110  *
111  *    - Removed ij_untrusted_url() The relevant
112  *      info is now part of the "untrusted" page,
113  *      which is generated by filters.c:trust_url()
114  *
115  *    - Generators of content now call finish_http_response()
116  *      themselves, making jcc.c:chat() a little less
117  *      cluttered
118  *
119  *    - Removed obsolete "Pragma: no-cache" from our headers
120  *
121  *    - http_responses now know their head length
122  *
123  *    - fill_template now uses the new interface to pcrs, so that
124  *       - long jobs (like whole files) no longer have to be assembled
125  *         in a fixed size buffer
126  *       - the new T (trivial) option is used, and the replacement may
127  *         contain Perl syntax backrefs without confusing pcrs
128  *
129  *    - Introduced default_exports() which generates a set of exports
130  *      common to all CGIs and other content generators
131  *
132  *    - Introduced convenience function map_block_killer()
133  *
134  *    - Introduced convenience function make_menu()
135  *
136  *    - Introduced CGI-like function error_response() which generates
137  *      the "No such domain" and "Connect failed" messages using the
138  *      CGI platform
139  *
140  *    - cgi_show_url_info:
141  *      - adapted to new CGI features
142  *      - form and answers now generated from same template
143  *      - http:// prefix in URL now OK
144  *
145  *    - cgi_show_status:
146  *      - adapted to new CGI features
147  *      - no longer uses csp->init_proxy_args
148  *
149  *    - cgi_default:
150  *      - moved menu generation to make_menu()
151  *
152  *    - add_stats now writes single export map entries instead
153  *      of a fixed string
154  *
155  *    - Moved redirect_url() to filters.c
156  *
157  *    - Fixed mem leak in free_http_response(), map_block_killer(),
158  *
159  *    - Removed logentry from cancelled commit
160  *
161  *    Revision 1.7  2001/06/09 10:51:58  jongfoster
162  *    Changing "show URL info" handler to new style.
163  *    Changing BUFSIZ ==> BUFFER_SIZE
164  *
165  *    Revision 1.6  2001/06/07 23:05:19  jongfoster
166  *    Removing code related to old forward and ACL files.
167  *
168  *    Revision 1.5  2001/06/05 19:59:16  jongfoster
169  *    Fixing multiline character string (a GCC-only "feature"), and snprintf (it's _snprintf under VC++).
170  *
171  *    Revision 1.4  2001/06/04 10:41:52  swa
172  *    show version string of cgi.h and cgi.c
173  *
174  *    Revision 1.3  2001/06/03 19:12:16  oes
175  *    introduced new cgi handling
176  *
177  *    No revisions before 1.3
178  *
179  **********************************************************************/
180 \f
181
182 #include "config.h"
183
184 #include <stdio.h>
185 #include <sys/types.h>
186 #include <stdlib.h>
187 #include <ctype.h>
188 #include <string.h>
189 #include <assert.h>
190
191 #ifdef _WIN32
192 #define snprintf _snprintf
193 #endif /* def _WIN32 */
194
195 #include "project.h"
196 #include "cgi.h"
197 #include "list.h"
198 #include "encode.h"
199 #include "ssplit.h"
200 #include "jcc.h"
201 #include "filters.h"
202 #include "actions.h"
203 #include "errlog.h"
204 #include "miscutil.h"
205 #include "showargs.h"
206 #include "loadcfg.h"
207 #ifdef FEATURE_CGI_EDIT_ACTIONS
208 #include "cgiedit.h"
209 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
210
211 const char cgi_h_rcs[] = CGI_H_VERSION;
212
213 const struct cgi_dispatcher cgi_dispatcher[] = {
214    { "robots.txt", 
215          10, cgi_robots_txt,  
216          "HIDE Sends a robots.txt file to tell robots to go away." }, 
217    { "show-status", 
218          11, cgi_show_status,  
219          "Show information about the current configuration" }, 
220    { "show-url-info",
221          13, cgi_show_url_info, 
222          "Show which actions apply to a URL and why"  },
223    { "show-version", 
224          12, cgi_show_version,  
225          "Show the source code version numbers" }, 
226    { "send-banner",
227          11, cgi_send_banner, 
228          "HIDE Send the transparent or \"Junkbuster\" gif" },
229 #ifdef FEATURE_CGI_EDIT_ACTIONS
230    { "edit-actions-list",
231          17, cgi_edit_actions_list, 
232          "Edit the actions list" },
233    { "edit-actions-submit",
234          19, cgi_edit_actions_submit, 
235          "HIDE Change the actions for (a) specified URL(s)" },
236    { "edit-actions",
237          12, cgi_edit_actions, 
238          "HIDE Edit the actions for (a) specified URL(s)" },
239 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
240    { "",
241          0, cgi_default,
242          "Junkbuster main page" },
243    { NULL, 0, NULL, NULL }
244 };
245
246
247 /*
248  * Some images
249  *
250  * Hint: You can encode your own GIFs like that:
251  * perl -e 'while (read STDIN, $c, 1) { printf("\\%.3o,", unpack("C", $c)); }'
252  */
253
254 const char image_junkbuster_gif_data[] =
255    "GIF89aD\000\013\000\360\000\000\000\000\000\377\377\377!"
256    "\371\004\001\000\000\001\000,\000\000\000\000D\000\013\000"
257    "\000\002a\214\217\251\313\355\277\000\200G&K\025\316hC\037"
258    "\200\234\230Y\2309\235S\230\266\206\372J\253<\3131\253\271"
259    "\270\215\342\254\013\203\371\202\264\334P\207\332\020o\266"
260    "N\215I\332=\211\312\3513\266:\026AK)\364\370\365aobr\305"
261    "\372\003S\275\274k2\354\254z\347?\335\274x\306^9\374\276"
262    "\037Q\000\000;";
263
264 const int image_junkbuster_gif_length = sizeof(image_junkbuster_gif_data) - 1;
265
266
267 const char image_blank_gif_data[] =
268    "GIF89a\001\000\001\000\200\000\000\377\377\377\000\000"
269    "\000!\371\004\001\000\000\000\000,\000\000\000\000\001"
270    "\000\001\000\000\002\002D\001\000;";
271
272 const int image_blank_gif_length = sizeof(image_blank_gif_data) - 1;
273
274
275 /*********************************************************************
276  * 
277  * Function    :  dispatch_cgi
278  *
279  * Description :  Checks if a request URL has either the magical hostname
280  *                i.j.b or matches HOME_PAGE_URL/config/. If so, it parses
281  *                the (rest of the) path as a cgi name plus query string,
282  *                prepares a map that maps CGI parameter names to their values,
283  *                initializes the http_response struct, and calls the 
284  *                relevant CGI handler function.
285  *
286  * Parameters  :
287  *          1  :  csp = Current client state (buffers, headers, etc...)
288  *
289  * Returns     :  http_response if match, NULL if nonmatch or handler fail
290  *
291  *********************************************************************/
292 struct http_response *dispatch_cgi(struct client_state *csp)
293 {
294    char *argstring = NULL;
295    const struct cgi_dispatcher *d;
296    struct map *param_list;
297    struct http_response *rsp;
298
299    /*
300     * Should we intercept ?
301     */
302
303    /* Either the host matches CGI_PREFIX_HOST ..*/
304    if (0 == strcmpic(csp->http->host, CGI_PREFIX_HOST))
305    {
306       /* ..then the path will all be for us */
307       argstring = csp->http->path;
308    }
309    /* Or it's the host part HOME_PAGE_URL, and the path /config ? */
310    else if (   (0 == strcmpic(csp->http->host, HOME_PAGE_URL + 7 ))
311             && (0 == strncmpic(csp->http->path,"/config", 7))
312             && ((csp->http->path[7] == '/') || (csp->http->path[7] == '\0')))
313    {
314       /* then it's everything following "/config" */
315       argstring = csp->http->path + 7;
316    }
317    else
318    {
319       return NULL;
320    }
321
322    /* 
323     * This is a CGI call.
324     */
325
326    /* Get mem for response or fail*/
327    if (NULL == (rsp = alloc_http_response()))
328    {
329       return NULL;
330    }
331
332
333    /* Remove leading slash */
334    if (*argstring == '/')
335    {
336       argstring++;
337    }
338
339    log_error(LOG_LEVEL_GPC, "%s%s cgi call", csp->http->hostport, csp->http->path);
340    log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 3", 
341                             csp->ip_addr_str, csp->http->cmd); 
342
343    /* Find and start the right CGI function*/
344    for (d = cgi_dispatcher; d->handler; d++)
345    {
346       if (strncmp(argstring, d->name, d->name_length) == 0)
347       {
348          if (NULL == (param_list = 
349              parse_cgi_parameters(argstring + d->name_length)))
350          {
351             free_map(param_list);
352             free_http_response(rsp);
353             return(NULL);
354          }
355          if ((d->handler)(csp, rsp, param_list))
356          {
357             free_map(param_list);
358             free_http_response(rsp);
359             return(NULL);
360          }
361
362          free_map(param_list);
363          return(finish_http_response(rsp));
364       }
365    }
366
367    /* Can't get here, since cgi_default will match all requests */
368    free_http_response(rsp);
369    return(NULL);
370 }
371
372
373 /*********************************************************************
374  *
375  * Function    :  parse_cgi_parameters
376  *
377  * Description :  Parse a URL-encoded argument string into name/value
378  *                pairs and store them in a struct map list.
379  *
380  * Parameters  :
381  *          1  :  string = string to be parsed 
382  *
383  * Returns     :  pointer to param list, or NULL if out of memory.
384  *
385  *********************************************************************/
386 struct map *parse_cgi_parameters(char *argstring)
387 {
388    char *tmp, *p;
389    char *vector[BUFFER_SIZE];
390    int pairs, i;
391    struct map *cgi_params;
392
393    if (NULL == (cgi_params = new_map()))
394    {
395       return NULL;
396    }
397
398    if(*argstring == '?')
399    {
400       argstring++;
401    }
402    if (NULL == (tmp = strdup(argstring)))
403    {
404       free_map(cgi_params);
405       return NULL;
406    }
407
408    pairs = ssplit(tmp, "&", vector, SZ(vector), 1, 1);
409
410    for (i = 0; i < pairs; i++)
411    {
412       if ((NULL != (p = strchr(vector[i], '='))) && (*(p+1) != '\0'))
413       {
414          *p = '\0';
415          map(cgi_params, url_decode(vector[i]), 0, url_decode(++p), 0);
416       }
417    }
418
419    free(tmp);
420    return(cgi_params);
421
422 }
423
424
425 /*********************************************************************
426  *
427  * Function    :  cgi_default
428  *
429  * Description :  CGI function that is called if no action was given.
430  *                Lists menu of available unhidden CGIs.
431  *               
432  * Parameters  :
433  *           1 :  csp = Current client state (buffers, headers, etc...)
434  *           2 :  rsp = http_response data structure for output
435  *           3 :  parameters = map of cgi parameters
436  *
437  * Returns     :  0
438  *
439  *********************************************************************/
440 int cgi_default(struct client_state *csp, struct http_response *rsp,
441                 struct map *parameters)
442 {
443    char *p;
444    char *tmp = NULL;
445    struct map * exports = default_exports(csp, "");
446
447    /* If there were other parameters, export a dump as "cgi-parameters" */
448    if(parameters)
449    {
450       p = dump_map(parameters);
451       tmp = strsav(tmp, "<p>What made you think this cgi takes parameters?\n"
452                         "Anyway, here they are, in case you're interested:</p>\n");
453       tmp = strsav(tmp, p);
454       map(exports, "cgi-parameters", 1, tmp, 0);
455       free(p);
456    }
457    else
458    {
459       map(exports, "cgi-parameters", 1, "", 1);
460    }
461
462    rsp->body = template_load(csp, "default");
463    template_fill(&rsp->body, exports);
464    free_map(exports);
465    return(0);
466
467 }
468
469
470 /*********************************************************************
471  *
472  * Function    :  cgi_send_banner
473  *
474  * Description :  CGI function that returns a banner. 
475  *
476  * Parameters  :
477  *           1 :  csp = Current client state (buffers, headers, etc...)
478  *           2 :  rsp = http_response data structure for output
479  *           3 :  parameters = map of cgi parameters
480  *
481  * CGI Parameters :
482  *           type : Selects the type of banner between "trans" and "jb".
483  *                  Defaults to "jb" if absent or != "trans".
484  *
485  * Returns     :  0
486  *
487  *********************************************************************/
488 int cgi_send_banner(struct client_state *csp, struct http_response *rsp,
489                     struct map *parameters)
490 {
491    if(strcmp(lookup(parameters, "type"), "trans"))
492    {
493       rsp->body = bindup(image_junkbuster_gif_data, image_junkbuster_gif_length);
494       rsp->content_length = image_junkbuster_gif_length;
495    }
496    else
497    {
498       rsp->body = bindup(image_blank_gif_data, image_blank_gif_length);
499       rsp->content_length = image_blank_gif_length;
500    }   
501
502    enlist(rsp->headers, "Content-Type: image/gif");
503    rsp->is_static = 1;
504
505    return(0);
506
507 }
508
509
510 /*********************************************************************
511  *
512  * Function    :  cgi_show_version
513  *
514  * Description :  CGI function that returns a a web page describing the
515  *                file versions of IJB.
516  *
517  * Parameters  :
518  *           1 :  csp = Current client state (buffers, headers, etc...)
519  *           2 :  rsp = http_response data structure for output
520  *           3 :  parameters = map of cgi parameters
521  *
522  * CGI Parameters :
523  *           type : Selects the type of banner between "trans" and "jb".
524  *                  Defaults to "jb" if absent or != "trans".
525  *
526  * Returns     :  0
527  *
528  *********************************************************************/
529 int cgi_show_version(struct client_state *csp, struct http_response *rsp,
530                      struct map *parameters)
531 {
532    struct map * exports = default_exports(csp, "show-version");
533
534    map(exports, "sourceversions", 1, show_rcs(), 0);  
535
536    rsp->body = template_load(csp, "show-version");
537    template_fill(&rsp->body, exports);
538    free_map(exports);
539    return(0);
540
541 }
542
543  
544 /*********************************************************************
545  *
546  * Function    :  cgi_show_status
547  *
548  * Description :  CGI function that returns a a web page describing the
549  *                current status of IJB.
550  *
551  * Parameters  :
552  *           1 :  csp = Current client state (buffers, headers, etc...)
553  *           2 :  rsp = http_response data structure for output
554  *           3 :  parameters = map of cgi parameters
555  *
556  * CGI Parameters :
557  *           type : Selects the type of banner between "trans" and "jb".
558  *                  Defaults to "jb" if absent or != "trans".
559  *
560  * Returns     :  0
561  *
562  *********************************************************************/
563 int cgi_show_status(struct client_state *csp, struct http_response *rsp,
564                     struct map *parameters)
565 {
566    char *s = NULL;
567    int i;
568
569    FILE * fp;
570    char buf[BUFFER_SIZE];
571    char * p;
572    const char * filename = NULL;
573    char * file_description = NULL;
574 #ifdef FEATURE_STATISTICS
575    float perc_rej;   /* Percentage of http requests rejected */
576    int local_urls_read;
577    int local_urls_rejected;
578 #endif /* ndef FEATURE_STATISTICS */
579
580    struct map * exports = default_exports(csp, "show-status");
581
582    switch (*(lookup(parameters, "file")))
583    {
584    case 'p':
585       if (csp->actions_list)
586       {
587          filename = csp->actions_list->filename;
588          file_description = "Actions List";
589       }
590       break;
591
592    case 'r':
593       if (csp->rlist)
594       {
595          filename = csp->rlist->filename;
596          file_description = "Regex Filter List";
597       }
598       break;
599
600 #ifdef FEATURE_TRUST
601    case 't':
602       if (csp->tlist)
603       {
604          filename = csp->tlist->filename;
605          file_description = "Trust List";
606       }
607       break;
608 #endif /* def FEATURE_TRUST */
609    }
610
611    if (NULL != filename)
612    {
613       map(exports, "file-description", 1, file_description, 1);
614       map(exports, "filepath", 1, html_encode(filename), 0);
615
616       if ((fp = fopen(filename, "r")) == NULL)
617       {
618          map(exports, "content", 1, "<h1>ERROR OPENING FILE!</h1>", 1);
619       }
620       else
621       {
622          while (fgets(buf, sizeof(buf), fp))
623          {
624             p = html_encode(buf);
625             if (p)
626             {
627                s = strsav(s, p);
628                freez(p);
629                s = strsav(s, "<br>");
630             }
631          }
632          fclose(fp);
633          map(exports, "contents", 1, s, 0);
634       }
635       rsp->body = template_load(csp, "show-status-file");
636       template_fill(&rsp->body, exports);
637       free_map(exports);
638       return(0);
639
640    }
641
642    map(exports, "redirect-url", 1, REDIRECT_URL, 1);
643    
644    s = NULL;
645    for (i=0; i < Argc; i++)
646    {
647       s = strsav(s, Argv[i]);
648       s = strsav(s, " ");
649    }
650    map(exports, "invocation", 1, s, 0);
651
652    map(exports, "options", 1, csp->config->proxy_args, 1);
653    show_defines(exports);
654
655 #ifdef FEATURE_STATISTICS
656    local_urls_read     = urls_read;
657    local_urls_rejected = urls_rejected;
658
659    /*
660     * Need to alter the stats not to include the fetch of this
661     * page.
662     *
663     * Can't do following thread safely! doh!
664     *
665     * urls_read--;
666     * urls_rejected--; * This will be incremented subsequently *
667     */
668
669    if (local_urls_read == 0)
670    {
671       map_block_killer(exports, "have-stats");
672    }
673    else
674    {
675       map_block_killer(exports, "have-no-stats");
676
677       perc_rej = (float)local_urls_rejected * 100.0F /
678             (float)local_urls_read;
679
680       sprintf(buf, "%d", local_urls_read);
681       map(exports, "requests-received", 1, buf, 1);
682
683       sprintf(buf, "%d", local_urls_rejected);
684       map(exports, "requests-blocked", 1, buf, 1);
685
686       sprintf(buf, "%6.2f", perc_rej);
687       map(exports, "percent-blocked", 1, buf, 1);
688    }
689
690 #else /* ndef FEATURE_STATISTICS */
691    map_block_killer(exports, "statistics");
692 #endif /* ndef FEATURE_STATISTICS */
693
694    if (csp->actions_list)
695    {
696       map(exports, "actions-filename", 1,  csp->actions_list->filename, 1);
697    }
698    else
699    {
700       map(exports, "actions-filename", 1, "None specified", 1);
701    }
702
703    if (csp->rlist)
704    {
705       map(exports, "re-filter-filename", 1,  csp->rlist->filename, 1);
706    }
707    else
708    {
709       map(exports, "re-filter-filename", 1, "None specified", 1);
710    }
711
712 #ifdef FEATURE_TRUST
713    if (csp->tlist)
714    {
715       map(exports, "trust-filename", 1,  csp->tlist->filename, 1);
716    }
717    else
718    {
719        map(exports, "trust-filename", 1, "None specified", 1);
720    }
721 #else
722    map_block_killer(exports, "trust-support");
723 #endif /* ndef FEATURE_TRUST */
724
725    rsp->body = template_load(csp, "show-status");
726    template_fill(&rsp->body, exports);
727    free_map(exports);
728    return(0);
729
730 }
731
732  
733 /*********************************************************************
734  *
735  * Function    :  cgi_show_url_info
736  *
737  * Description :  CGI function that determines and shows which actions
738  *                junkbuster will perform for a given url, and which
739  *                matches starting from the defaults have lead to that.
740  *
741  * Parameters  :
742  *           1 :  csp = Current client state (buffers, headers, etc...)
743  *           2 :  rsp = http_response data structure for output
744  *           3 :  parameters = map of cgi parameters
745  *
746  * CGI Parameters :
747  *            url : The url whose actions are to be determined.
748  *                  If url is unset, the url-given conditional will be
749  *                  set, so that all but the form can be suppressed in
750  *                  the template.
751  *
752  * Returns     :  0
753  *
754  *********************************************************************/
755 int cgi_show_url_info(struct client_state *csp, struct http_response *rsp,
756                       struct map *parameters)
757 {
758    char *url_param;
759    char *host = NULL;
760    struct map * exports = default_exports(csp, "show-url-info");
761
762    if (NULL == (url_param = strdup(lookup(parameters, "url"))) || *url_param == '\0')
763    {
764       map_block_killer(exports, "url-given");
765       map(exports, "url", 1, "", 1);
766    }
767    else
768    {
769       char *matches = NULL;
770       char *path;
771       char *s;
772       int port = 80;
773       int hits = 0;
774       struct file_list *fl;
775       struct url_actions *b;
776       struct url_spec url[1];
777       struct current_action_spec action[1];
778       
779       host = url_param;
780       host += (strncmp(url_param, "http://", 7)) ? 0 : 7;
781
782       map(exports, "url", 1, host, 1);
783       map(exports, "url-html", 1, html_encode(host), 0);
784
785       init_current_action(action);
786
787       s = current_action_to_text(action);
788       map(exports, "default", 1, s , 0);
789
790       if (((fl = csp->actions_list) == NULL) || ((b = fl->f) == NULL))
791       {
792          map(exports, "matches", 1, "none" , 1);
793          map(exports, "final", 1, lookup(exports, "default"), 1);
794
795          freez(url_param);
796          free_current_action(action);
797
798          rsp->body = template_load(csp, "show-url-info");
799          template_fill(&rsp->body, exports);
800          free_map(exports);
801
802          return 0;
803       }
804
805       s = strchr(host, '/');
806       if (s != NULL)
807       {
808          path = strdup(s);
809          *s = '\0';
810       }
811       else
812       {
813          path = strdup("");
814       }
815       s = strchr(host, ':');
816       if (s != NULL)
817       {
818          *s++ = '\0';
819          port = atoi(s);
820          s = NULL;
821       }
822
823       *url = dsplit(host);
824
825       /* if splitting the domain fails, punt */
826       if (url->dbuf == NULL)
827       {
828          map(exports, "matches", 1, "none" , 1);
829          map(exports, "final", 1, lookup(exports, "default"), 1);
830
831          freez(url_param);
832          freez(path);
833          free_current_action(action);
834
835          rsp->body = template_load(csp, "show-url-info");
836          template_fill(&rsp->body, exports);
837          free_map(exports);
838
839          return 0;
840       }
841
842       for (b = b->next; NULL != b; b = b->next)
843       {
844          if ((b->url->port == 0) || (b->url->port == port))
845          {
846             if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
847             {
848                if ((b->url->path == NULL) ||
849 #ifdef REGEX
850                   (regexec(b->url->preg, path, 0, NULL, 0) == 0)
851 #else
852                   (strncmp(b->url->path, path, b->url->pathlen) == 0)
853 #endif
854                )
855                {
856                   s = actions_to_text(b->action);
857                   matches = strsav(matches, "<b>{");
858                   matches = strsav(matches, s);
859                   matches = strsav(matches, " }</b><br>\n<code>");
860                   matches = strsav(matches, b->url->spec);
861                   matches = strsav(matches, "</code><br>\n<br>\n");
862                   freez(s);
863
864                   merge_current_action(action, b->action);
865                   hits++;
866                }
867             }
868          }
869       }
870
871       if (hits)
872       {
873          map(exports, "matches", 1, matches , 0);
874       }
875       else
876       {
877          map(exports, "matches", 1, "none", 1);
878       }
879       matches = NULL;
880
881       freez(url->dbuf);
882       freez(url->dvec);
883
884       freez(url_param);
885       freez(path);
886
887       s = current_action_to_text(action);
888       map(exports, "final", 1, s, 0);
889       s = NULL;
890
891       free_current_action(action);
892    }
893
894    rsp->body = template_load(csp, "show-url-info");
895    template_fill(&rsp->body, exports);
896    free_map(exports);
897    return 0;
898
899 }
900
901
902 /*********************************************************************
903  *
904  * Function    :  error_response
905  *
906  * Description :  returns an http_response that explains the reason
907  *                why a request failed.
908  *
909  * Parameters  :
910  *          1  :  csp = Current client state (buffers, headers, etc...)
911  *          2  :  templatename = Which template should be used for the answer
912  *          3  :  errno = system error number
913  *
914  * Returns     :  NULL if no memory, else http_response
915  *
916  *********************************************************************/
917 struct http_response *error_response(struct client_state *csp, const char *templatename, int err)
918 {
919    struct http_response *rsp;
920    struct map * exports = default_exports(csp, NULL);
921
922    if (NULL == (rsp = alloc_http_response()))
923    {
924       return NULL;
925    }
926
927    map(exports, "host-html", 1, html_encode(csp->http->host), 0);
928    map(exports, "hostport", 1, csp->http->hostport, 1);
929    map(exports, "hostport-html", 1, html_encode(csp->http->hostport), 0);
930    map(exports, "path", 1, csp->http->path, 1);
931    map(exports, "path-html", 1, html_encode(csp->http->path), 0);
932    map(exports, "error", 1, safe_strerror(err), 0);
933    map(exports, "host-ip", 1, csp->http->host_ip_addr_str, 1);
934
935    rsp->body = template_load(csp, templatename);
936    template_fill(&rsp->body, exports);
937    free_map(exports);
938
939    if (!strcmp(templatename, "no-such-domain"))
940    {
941       rsp->status = strdup("404 No such domain"); 
942    }
943    else if (!strcmp(templatename, "connect-failed"))
944    {
945       rsp->status = strdup("503 Connect failed");
946    }
947
948    return(finish_http_response(rsp));
949 }
950
951
952 /*********************************************************************
953  *
954  * Function    :  get_http_time
955  *
956  * Description :  Get the time in a format suitable for use in a
957  *                HTTP header - e.g.:
958  *                "Sun, 06 Nov 1994 08:49:37 GMT"
959  *
960  * Parameters  :  
961  *          1  :  time_offset = Time returned will be current time
962  *                              plus this number of seconds.
963  *          2  :  buf = Destination for result.  Must be long enough
964  *                      to hold 29 characters plus a trailing zero.
965  *
966  * Returns     :  N/A
967  *
968  *********************************************************************/
969 static void get_http_time(int time_offset, char * buf)
970 {
971    static const char day_names[7][4] =
972       { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
973    static const char month_names[12][4] =
974       { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
975         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
976
977    struct tm *t;
978    time_t current_time;
979
980    assert(buf);
981
982    time(&current_time); /* get current time */
983
984 /* FIXME: is this needed?  time() can't fail on Win32.  What about Linux?
985    if(current_time <= 0)
986    {
987       return NULL;
988    }
989 */
990
991    current_time += time_offset;
992
993    /* get and save the gmt */
994    t = gmtime(&current_time);
995
996    /* Format: "Sun, 06 Nov 1994 08:49:37 GMT" */
997    snprintf(buf, 30,
998       "%s, %02d %s %4d %02d:%02d:%02d GMT",
999       day_names[t->tm_wday],
1000       t->tm_mday,
1001       month_names[t->tm_mon],
1002       t->tm_year + 1900,
1003       t->tm_hour,
1004       t->tm_min,
1005       t->tm_sec
1006       );
1007    buf[32] = '\0';
1008 }
1009
1010
1011 /*********************************************************************
1012  *
1013  * Function    :  finish_http_response
1014  *
1015  * Description :  Fill in the missing headers in an http response,
1016  *                and flatten the headers to an http head.
1017  *
1018  * Parameters  :
1019  *          1  :  rsp = pointer to http_response to be processed
1020  *
1021  * Returns     :  http_response, or NULL on failiure
1022  *
1023  *********************************************************************/
1024 struct http_response *finish_http_response(struct http_response *rsp)
1025 {
1026    char buf[BUFFER_SIZE];
1027
1028    /* 
1029     * Fill in the HTTP Status
1030     */
1031    sprintf(buf, "HTTP/1.0 %s", rsp->status ? rsp->status : "200 OK");
1032    enlist_first(rsp->headers, buf);
1033
1034    /* 
1035     * Set the Content-Length
1036     */
1037    if (rsp->content_length == 0)
1038    {
1039       rsp->content_length = rsp->body ? strlen(rsp->body) : 0;
1040    }
1041    sprintf(buf, "Content-Length: %d", rsp->content_length);
1042    enlist(rsp->headers, buf);
1043
1044    /* 
1045     * Fill in the default headers:
1046     *
1047     * Content-Type: default to text/html if not already specified.
1048     * Date: set to current date/time.
1049     * Last-Modified: set to date/time the page was last changed.
1050     * Expires: set to date/time page next needs reloading.
1051     * Cache-Control: set to "no-cache" if applicable.
1052     * 
1053     * See http://www.w3.org/Protocols/rfc2068/rfc2068
1054     */
1055    enlist_unique(rsp->headers, "Content-Type: text/html", 13);
1056
1057    if (rsp->is_static)
1058    {
1059       /*
1060        * Set Expires to about 10 min into the future so it'll get reloaded
1061        * occasionally, e.g. if IJB gets upgraded.
1062        */
1063
1064       get_http_time(0, buf);
1065       enlist_unique_header(rsp->headers, "Date", buf);
1066
1067       /* Some date in the past. */
1068       enlist_unique_header(rsp->headers, "Last-Modified", "Sat, 17 Jun 2000 12:00:00 GMT");
1069
1070       get_http_time(10 * 60, buf); /* 10 * 60sec = 10 minutes */
1071       enlist_unique_header(rsp->headers, "Expires", buf);
1072    }
1073    else
1074    {
1075       /*
1076        * Compliant browsers should not cache this due to the "Cache-Control"
1077        * setting.  However, to be certain, we also set both "Last-Modified"
1078        * and "Expires" to the current time.
1079        */
1080       enlist_unique_header(rsp->headers, "Cache-Control", "no-cache");
1081       get_http_time(0, buf);
1082       enlist_unique_header(rsp->headers, "Date", buf);
1083       enlist_unique_header(rsp->headers, "Last-Modified", buf);
1084       enlist_unique_header(rsp->headers, "Expires", buf);
1085    }
1086
1087
1088    /* 
1089     * Write the head
1090     */
1091    if (NULL == (rsp->head = list_to_text(rsp->headers)))
1092    {
1093       free_http_response(rsp);
1094       return(NULL);
1095    }
1096    rsp->head_length = strlen(rsp->head);
1097
1098    return(rsp);
1099
1100 }
1101   
1102
1103 /*********************************************************************
1104  *
1105  * Function    :  alloc_http_response
1106  *
1107  * Description :  Allocates a new http_response structure.
1108  *
1109  * Parameters  :  N/A
1110  *
1111  * Returns     :  pointer to a new http_response, or NULL.
1112  *
1113  *********************************************************************/
1114 struct http_response * alloc_http_response(void)
1115 {
1116    return (struct http_response *) zalloc(sizeof(struct http_response));
1117 }
1118
1119
1120 /*********************************************************************
1121  *
1122  * Function    :  free_http_response
1123  *
1124  * Description :  Free the memory occupied by an http_response
1125  *                and its depandant structures.
1126  *
1127  * Parameters  :
1128  *          1  :  rsp = pointer to http_response to be freed
1129  *
1130  * Returns     :  N/A
1131  *
1132  *********************************************************************/
1133 void free_http_response(struct http_response *rsp)
1134 {
1135    if (rsp)
1136    {
1137       freez(rsp->status);
1138       freez(rsp->head);
1139       freez(rsp->body);
1140       destroy_list(rsp->headers);
1141       free(rsp);
1142    }
1143
1144 }
1145
1146
1147 /*********************************************************************
1148  *
1149  * Function    :  fill_template
1150  *
1151  * Description :  CGI support function that loads a given HTML
1152  *                template from the confdir, and fills it in
1153  *                by replacing @name@ with value using pcrs,
1154  *                for each item in the output map.
1155  *
1156  * Parameters  :
1157  *           1 :  csp = Current client state (buffers, headers, etc...)
1158  *           3 :  template = name of the HTML template to be used
1159  *
1160  * Returns     :  char * with loaded template, or NULL if failure
1161  *
1162  *********************************************************************/
1163 char *template_load(struct client_state *csp, const char *templatename)
1164 {
1165    char buf[BUFFER_SIZE];
1166    char *file_buffer = NULL;
1167    FILE *fp;
1168
1169    /*
1170     * Open template file or fail
1171     */
1172    snprintf(buf, BUFFER_SIZE, "%s/templates/%s", csp->config->confdir, templatename);
1173
1174    if(NULL == (fp = fopen(buf, "r")))
1175    {
1176       log_error(LOG_LEVEL_ERROR, "error loading template %s: %E", buf);
1177       return NULL;
1178    }
1179    
1180
1181    /* 
1182     * Read the file, ignoring comments
1183     */
1184    while (fgets(buf, BUFFER_SIZE, fp))
1185    {
1186       /* skip lines starting with '#' */
1187       if(*buf == '#')
1188       {
1189          continue;
1190       }
1191    
1192       file_buffer = strsav(file_buffer, buf);
1193    }
1194    fclose(fp);
1195
1196    return(file_buffer);
1197 }
1198
1199
1200 /*********************************************************************
1201  *
1202  * Function    :  fill_template
1203  *
1204  * Description :  CGI support function that loads a given HTML
1205  *                template from the confdir, and fills it in
1206  *                by replacing @name@ with value using pcrs,
1207  *                for each item in the output map.
1208  *
1209  * Parameters  :
1210  *           1 :  template_ptr = IN: Template to be filled out.
1211  *                                   Will be free()d.
1212  *                               OUT: Filled out template.
1213  *                                    Caller must free().
1214  *           2 :  exports = map with fill in symbol -> name pairs
1215  *
1216  * Returns     :  N/A
1217  *
1218  *********************************************************************/
1219 void template_fill(char ** template_ptr, struct map *exports)
1220 {
1221    struct map_entry *m;
1222    pcrs_job *job;
1223    char buf[BUFFER_SIZE];
1224    char *tmp_out_buffer;
1225    char *file_buffer;
1226    int size;
1227    int error;
1228    const char * flags;
1229
1230    assert(template_ptr);
1231    assert(*template_ptr);
1232    assert(exports);
1233
1234    file_buffer = *template_ptr;
1235    size = strlen(file_buffer) + 1;
1236
1237    /* 
1238     * Assemble pcrs joblist from exports map
1239     */
1240    for (m = exports->first; m != NULL; m = m->next)
1241    {
1242       if (*m->name == '$')
1243       {
1244          /*
1245           * First character of name is '$', so remove this flag
1246           * character and allow backreferences ($1 etc) in the
1247           * "replace with" text.
1248           */
1249          snprintf(buf, BUFFER_SIZE, "%s", m->name + 1);
1250          flags = "sigU";
1251       }
1252       else
1253       {
1254          /*
1255           * Treat the "replace with" text as a literal string - 
1256           * no quoting needed, no backreferences allowed.
1257           * ("Trivial" ['T'] flag).
1258           */
1259          flags = "sigTU";
1260
1261          /* Enclose name in @@ */
1262          snprintf(buf, BUFFER_SIZE, "@%s@", m->name);
1263       }
1264
1265
1266       log_error(LOG_LEVEL_CGI, "Substituting: s/%s/%s/%s", buf, m->value, flags);
1267
1268       /* Make and run job. */
1269       job = pcrs_compile(buf, m->value, flags,  &error);
1270       if (job == NULL) 
1271       {
1272          log_error(LOG_LEVEL_ERROR, "Error compiling template fill job %s: %d", m->name, error);
1273       }
1274       else
1275       {
1276          pcrs_execute(job, file_buffer, size, &tmp_out_buffer, &size);
1277          if (file_buffer != tmp_out_buffer)
1278          {
1279             free(file_buffer);
1280             file_buffer = tmp_out_buffer;
1281          }
1282          pcrs_free_job(job);
1283       }
1284    }
1285
1286    /*
1287     * Return
1288     */
1289    *template_ptr = file_buffer;
1290 }
1291
1292
1293 /*********************************************************************
1294  *
1295  * Function    :  default_exports
1296  *
1297  * Description :  returns a struct map list that contains exports
1298  *                which are common to all CGI functions.
1299  *
1300  * Parameters  :
1301  *          1  :  exports = Structure to write output to.  This
1302  *                structure should be newly allocated and will be
1303  *                zeroed.
1304  *          1  :  csp = Current client state (buffers, headers, etc...)
1305  *          2  :  caller = name of CGI who calls us and which should
1306  *                         be excluded from the generated menu.
1307  * Returns     :  NULL if no memory, else map
1308  *
1309  *********************************************************************/
1310 struct map * default_exports(const struct client_state *csp, const char *caller)
1311 {
1312    char buf[20];
1313    struct map * exports = new_map();
1314
1315    map(exports, "version", 1, VERSION, 1);
1316    map(exports, "my-ip-address", 1, csp->my_ip_addr_str ? csp->my_ip_addr_str : "unknown", 1);
1317    map(exports, "my-hostname", 1, csp->my_hostname ? csp->my_hostname : "unknown", 1);
1318    map(exports, "admin-address", 1, csp->config->admin_address ? csp->config->admin_address : "fill@me.in.please", 1);
1319    map(exports, "homepage", 1, HOME_PAGE_URL, 1);
1320    map(exports, "default-cgi", 1, HOME_PAGE_URL "/config", 1);
1321    map(exports, "menu", 1, make_menu(caller), 0);
1322    map(exports, "code-status", 1, CODE_STATUS, 1);
1323
1324    snprintf(buf, 20, "%d", csp->config->hport);
1325    map(exports, "my-port", 1, buf, 1);
1326
1327    if(!strcmp(CODE_STATUS, "stable"))
1328    {
1329       map_block_killer(exports, "unstable");
1330    }
1331
1332    if(csp->config->proxy_info_url != NULL)
1333    {
1334       map(exports, "proxy-info-url", 1, csp->config->proxy_info_url, 1);
1335    }
1336    else
1337    {
1338       map_block_killer(exports, "have-proxy-info");
1339    }   
1340
1341    return (exports);
1342 }
1343
1344
1345 /*********************************************************************
1346  *
1347  * Function    :  map_block_killer
1348  *
1349  * Description :  Convenience function.
1350  *                Adds a "killer" for the conditional HTML-template
1351  *                block <name>, i.e. a substitution of the regex
1352  *                "if-<name>-start.*if-<name>-end" to the given
1353  *                export list.
1354  *
1355  * Parameters  :  
1356  *          1  :  exports = map to extend
1357  *          2  :  name = name of conditional block
1358  *
1359  * Returns     :  extended map
1360  *
1361  *********************************************************************/
1362 void map_block_killer(struct map *exports, const char *name)
1363 {
1364    char buf[1000]; /* Will do, since the names are hardwired */
1365
1366    snprintf(buf, 1000, "if-%s-start.*if-%s-end", name, name);
1367    map(exports, buf, 1, "", 1);
1368 }
1369
1370
1371 /*********************************************************************
1372  *
1373  * Function    :  map_conditional
1374  *
1375  * Description :  Convenience function.
1376  *                Adds an "if-then-else" for the conditional HTML-template
1377  *                block <name>, i.e. a substitution of the form:
1378  *                @if-<name>-then@
1379  *                   True text
1380  *                @else-not-<name>@
1381  *                   False text
1382  *                @endif-<name>@
1383  *
1384  *                The control structure and one of the alternatives
1385  *                will be hidden.
1386  *
1387  * Parameters  :  
1388  *          1  :  exports = map to extend
1389  *          2  :  name = name of conditional block
1390  *          3  :  choose_first = nonzero for first, zero for second.
1391  *
1392  * Returns     :  extended map
1393  *
1394  *********************************************************************/
1395 void map_conditional(struct map *exports, const char *name, int choose_first)
1396 {
1397    char buf[1000]; /* Will do, since the names are hardwired */
1398
1399    snprintf(buf, 1000, (choose_first
1400       ? "else-not-%s@.*@endif-%s"
1401       : "if-%s-then@.*@else-not-%s"),
1402       name, name);
1403    map(exports, buf, 1, "", 1);
1404
1405    snprintf(buf, 1000, (choose_first ? "if-%s-then" : "endif-%s"), name);
1406    map(exports, buf, 1, "", 1);
1407 }
1408
1409
1410 /*********************************************************************
1411  *
1412  * Function    :  make_menu
1413  *
1414  * Description :  Returns an HTML-formatted menu of the available 
1415  *                unhidden CGIs, excluding the one given in <self>.
1416  *
1417  * Parameters  :  self = name of CGI to leave out, can be NULL
1418  *
1419  * Returns     :  menu string
1420  *
1421  *********************************************************************/
1422 char *make_menu(const char *self)
1423 {
1424    const struct cgi_dispatcher *d;
1425    char buf[BUFFER_SIZE];
1426    char *result = NULL;
1427
1428    if (self == NULL)
1429    {
1430       self = "NO-SUCH-CGI!";
1431    }
1432
1433    /* List available unhidden CGI's and export as "other-cgis" */
1434    for (d = cgi_dispatcher; d->handler; d++)
1435    {
1436       if (strncmp(d->description, "HIDE", 4) && strcmp(d->name, self))
1437       {
1438          snprintf(buf, BUFFER_SIZE, "<li><a href=%s/config/%s>%s</a></li>\n",
1439                HOME_PAGE_URL, d->name, d->description);
1440          result = strsav(result, buf);
1441       }
1442    }
1443    return(result);
1444
1445 }
1446
1447
1448 /*********************************************************************
1449  *
1450  * Function    :  dump_map
1451  *
1452  * Description :  HTML-dump a map for debugging
1453  *
1454  * Parameters  :
1455  *          1  :  the_map = map to dump
1456  *
1457  * Returns     :  string with HTML
1458  *
1459  *********************************************************************/
1460 char *dump_map(const struct map *the_map)
1461 {
1462    struct map_entry *cur_entry = the_map->first;
1463    char *ret = NULL;
1464
1465    ret = strsav(ret, "<table>\n");
1466
1467    while (cur_entry)
1468    {
1469       ret = strsav(ret, "<tr><td><b>");
1470       ret = strsav(ret, cur_entry->name);
1471       ret = strsav(ret, "</b></td><td>");
1472       ret = strsav(ret, cur_entry->value);
1473       ret = strsav(ret, "</td></tr>\n");
1474       cur_entry = cur_entry->next;
1475    }
1476
1477    ret = strsav(ret, "</table>\n");
1478    return(ret);
1479
1480 }
1481
1482
1483 /*********************************************************************
1484  *
1485  * Function    :  cgi_robots_txt
1486  *
1487  * Description :  CGI function to return "/robots.txt".
1488  *
1489  * Parameters  :
1490  *           1 :  csp = Current client state (buffers, headers, etc...)
1491  *           2 :  rsp = http_response data structure for output
1492  *           3 :  parameters = map of cgi parameters
1493  *
1494  * CGI Parameters : None
1495  *
1496  * Returns     :  0
1497  *
1498  *********************************************************************/
1499 int cgi_robots_txt(struct client_state *csp, struct http_response *rsp,
1500                    struct map *parameters)
1501 {
1502    char buf[100];
1503
1504    rsp->body = strdup(
1505       "# This is the Internet Junkbuster control interface.\n"
1506       "# It isn't very useful to index it, and you're likely to break stuff.\n"
1507       "# So go away!\n"
1508       "\n"
1509       "User-agent: *\n"
1510       "Disallow: /\n"
1511       "\n");
1512
1513    enlist_unique(rsp->headers, "Content-Type: text/plain", 13);
1514
1515    rsp->is_static = 1;
1516
1517    get_http_time(7 * 24 * 60 * 60, buf); /* 7 days into future */
1518    enlist_unique_header(rsp->headers, "Expires", buf);
1519
1520    return 0;
1521 }
1522
1523
1524 /*
1525   Local Variables:
1526   tab-width: 3
1527   end:
1528 */