Add favicon delivery functions.
[privoxy.git] / cgi.c
diff --git a/cgi.c b/cgi.c
index 6d305c6..f2d2429 100644 (file)
--- a/cgi.c
+++ b/cgi.c
@@ -1,4 +1,4 @@
-const char cgi_rcs[] = "$Id: cgi.c,v 1.75 2006/09/07 11:56:39 fabiankeil Exp $";
+const char cgi_rcs[] = "$Id: cgi.c,v 1.88 2007/01/23 13:14:32 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgi.c,v $
@@ -11,8 +11,8 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.75 2006/09/07 11:56:39 fabiankeil Exp $";
  *                Functions declared include:
  * 
  *
- * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
- *                Privoxy team. http://www.privoxy.org/
+ * Copyright   :  Written by and Copyright (C) 2001-2004, 2006-2007
+ *                the SourceForge Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
  *                by and Copyright (C) 1997 Anonymous Coders and 
@@ -38,6 +38,72 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.75 2006/09/07 11:56:39 fabiankeil Exp $";
  *
  * Revisions   :
  *    $Log: cgi.c,v $
+ *    Revision 1.88  2007/01/23 13:14:32  fabiankeil
+ *    - Map variables that aren't guaranteed to be
+ *      pure ASCII html_encoded.
+ *    - Use CGI_PREFIX to generate URL for user manual
+ *      CGI page to make sure CGI_SITE_2_PATH is included.
+ *
+ *    Revision 1.87  2007/01/22 15:34:13  fabiankeil
+ *    - "Protect" against a rather lame JavaScript-based
+ *      Privoxy detection "attack" and check the referrer
+ *      before delivering the CGI style sheet.
+ *    - Move referrer check for unsafe CGI pages into
+ *      referrer_is_safe() and log the result.
+ *    - Map @url@ in cgi-error-disabled page.
+ *      It's required for the "go there anyway" link.
+ *    - Mark *csp as immutable for grep_cgi_referrer().
+ *
+ *    Revision 1.86  2007/01/09 11:54:26  fabiankeil
+ *    Fix strdup() error handling in cgi_error_unknown()
+ *    and cgi_error_no_template(). Reported by Markus Elfring.
+ *
+ *    Revision 1.85  2007/01/05 14:19:02  fabiankeil
+ *    Handle pcrs_execute() errors in template_fill() properly.
+ *
+ *    Revision 1.84  2006/12/28 17:54:22  fabiankeil
+ *    Fixed gcc43 conversion warnings and replaced sprintf
+ *    calls with snprintf to give OpenBSD's gcc one less reason
+ *    to complain.
+ *
+ *    Revision 1.83  2006/12/17 19:35:19  fabiankeil
+ *    Escape ampersand in Privoxy menu.
+ *
+ *    Revision 1.82  2006/12/17 17:53:39  fabiankeil
+ *    Suppress the toggle link if remote toggling is disabled.
+ *
+ *    Revision 1.81  2006/12/09 13:49:16  fabiankeil
+ *    Fix configure option --disable-toggle.
+ *    Thanks to Peter Thoenen for reporting this.
+ *
+ *    Revision 1.80  2006/12/08 14:45:32  fabiankeil
+ *    Don't lose the FORCE_PREFIX in case of
+ *    connection problems. Fixes #612235.
+ *
+ *    Revision 1.79  2006/11/13 19:05:50  fabiankeil
+ *    Make pthread mutex locking more generic. Instead of
+ *    checking for OSX and OpenBSD, check for FEATURE_PTHREAD
+ *    and use mutex locking unless there is an _r function
+ *    available. Better safe than sorry.
+ *
+ *    Fixes "./configure --disable-pthread" and should result
+ *    in less threading-related problems on pthread-using platforms,
+ *    but it still doesn't fix BR#1122404.
+ *
+ *    Revision 1.78  2006/09/21 19:22:07  fabiankeil
+ *    Use CGI_PREFIX to check the referrer.
+ *    The check for "http://config.privoxy.org/" fails
+ *    if the user modified CGI_SITE_2_HOST.
+ *
+ *    Revision 1.77  2006/09/21 15:17:23  fabiankeil
+ *    Adjusted headers for Privoxy's cgi responses:
+ *    Don't set Last-Modified, Expires and Cache-Control
+ *    headers for redirects; always set "Connection: close".
+ *
+ *    Revision 1.76  2006/09/07 14:06:38  fabiankeil
+ *    Only predate the Last-Modified header for cgi responses
+ *    that are delivered with status code 404 or 503.
+ *
  *    Revision 1.75  2006/09/07 11:56:39  fabiankeil
  *    Mark cgi_send_user_manual as harmless,
  *    to fix the access denied problem Hal spotted.
@@ -504,7 +570,6 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.75 2006/09/07 11:56:39 fabiankeil Exp $";
 #include "loadcfg.h"
 /* loadcfg.h is for global_toggle_state only */
 #ifdef FEATURE_PTHREAD
-#include <pthread.h>
 #include "jcc.h"
 /* jcc.h is for mutex semaphore globals only */
 #endif /* def FEATURE_PTHREAD */
@@ -530,7 +595,7 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
    { "show-status", 
          cgi_show_status,  
 #ifdef FEATURE_CGI_EDIT_ACTIONS
-        "View & change the current configuration",
+        "View &amp; change the current configuration",
 #else
         "View the current configuration",
 #endif
@@ -548,10 +613,12 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
          "Look up which actions apply to a URL and why",
          TRUE },
 #ifdef FEATURE_CGI_EDIT_ACTIONS
+#ifdef FEATURE_TOGGLE
    { "toggle",
          cgi_toggle, 
          "Toggle Privoxy on or off",
          FALSE },
+#endif /* def FEATURE_TOGGLE */
    { "edit-actions", /* Edit the actions list */
          cgi_edit_actions, 
          NULL, FALSE },
@@ -619,6 +686,12 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
          cgi_edit_actions_section_swap, 
          NULL, FALSE /* Swap two sections in the actionsfile */ },
 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
+   { "error-favicon.ico", 
+         cgi_send_error_favicon,  
+         NULL, TRUE /* Sends the favicon image for error pages. */ },
+   { "favicon.ico", 
+         cgi_send_default_favicon,  
+         NULL, TRUE /* Sends the default favicon image. */ },
    { "robots.txt", 
          cgi_robots_txt,  
          NULL, TRUE /* Sends a robots.txt file to tell robots to go away. */ }, 
@@ -627,7 +700,7 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
          NULL, TRUE /* Send a built-in image */ },
    { "send-stylesheet",
          cgi_send_stylesheet, 
-         NULL, TRUE /* Send templates/cgi-style.css */ },
+         NULL, FALSE /* Send templates/cgi-style.css */ },
    { "t",
          cgi_transparent_image, 
          NULL, TRUE /* Send a transparent image (short name) */ },
@@ -794,7 +867,7 @@ struct http_response *dispatch_cgi(struct client_state *csp)
  * Returns     :  pointer to value (no copy!), or NULL if none found.
  *
  *********************************************************************/
-char *grep_cgi_referrer(struct client_state *csp)
+char *grep_cgi_referrer(const struct client_state *csp)
 {
    struct list_entry *p;
 
@@ -811,6 +884,54 @@ char *grep_cgi_referrer(struct client_state *csp)
 }
 
 
+/*********************************************************************
+ * 
+ * Function    :  referrer_is_safe
+ *
+ * Description :  Decides whether we trust the Referer for
+ *                CGI pages which are only meant to be reachable
+ *                through Privoxy's web interface directly.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  TRUE  if the referrer is safe, or
+ *                FALSE if the referrer is unsafe or not set.
+ *
+ *********************************************************************/
+int referrer_is_safe (const struct client_state *csp)
+{
+   char *referrer;
+   const char alternative_prefix[] = "http://" CGI_SITE_1_HOST "/";
+
+   referrer = grep_cgi_referrer(csp);
+
+   if (NULL == referrer)
+   {
+      /* No referrer, no access  */
+      log_error(LOG_LEVEL_ERROR, "Denying access to %s. No referrer found.",
+         csp->http->url);
+   }
+   else if ((0 == strncmp(referrer, CGI_PREFIX, sizeof(CGI_PREFIX)-1)
+         || (0 == strncmp(referrer, alternative_prefix, strlen(alternative_prefix)))))
+   {
+      /* Trustworthy referrer */
+      log_error(LOG_LEVEL_CGI, "Granting access to %s, referrer %s is trustworthy.",
+         csp->http->url, referrer);
+
+      return TRUE;
+   }
+   else
+   {
+      /* Untrustworthy referrer */
+      log_error(LOG_LEVEL_ERROR, "Denying access to %s, referrer %s isn't trustworthy.",
+         csp->http->url, referrer);
+   }
+
+   return FALSE;
+
+}
+
 /*********************************************************************
  * 
  * Function    :  dispatch_known_cgi
@@ -839,7 +960,6 @@ static struct http_response *dispatch_known_cgi(struct client_state * csp,
    struct http_response *rsp;
    char *query_args_start;
    char *path_copy;
-   char *referrer;
    jb_err err;
 
    if (NULL == (path_copy = strdup(path)))
@@ -902,10 +1022,7 @@ static struct http_response *dispatch_known_cgi(struct client_state * csp,
           * If the called CGI is either harmless, or referred
           * from a trusted source, start it.
           */
-         if (d->harmless
-             || ((NULL != (referrer = grep_cgi_referrer(csp)))
-                 && (0 == strncmp(referrer, "http://config.privoxy.org/", 26)))
-             )
+         if (d->harmless || referrer_is_safe(csp))
          {
             err = (d->handler)(csp, rsp, param_list);
          }
@@ -1036,7 +1153,7 @@ char get_char_param(const struct map *parameters,
    ch = *(lookup(parameters, param_name));
    if ((ch >= 'a') && (ch <= 'z'))
    {
-      ch = ch - 'a' + 'A';
+      ch = (char)(ch - 'a' + 'A');
    }
 
    return ch;
@@ -1168,7 +1285,7 @@ jb_err get_number_param(struct client_state *csp,
          return JB_ERR_CGI_PARAMS;
       }
 
-      ch -= '0';
+      ch = (char)(ch - '0');
 
       /* Note:
        *
@@ -1182,7 +1299,7 @@ jb_err get_number_param(struct client_state *csp,
          return JB_ERR_CGI_PARAMS;
       }
 
-      value = value * 10 + ch;
+      value = value * 10 + (unsigned)ch;
    }
 
    /* Success */
@@ -1215,7 +1332,9 @@ struct http_response *error_response(struct client_state *csp,
 {
    jb_err err;
    struct http_response *rsp;
-   struct map * exports = default_exports(csp, NULL);
+   struct map *exports = default_exports(csp, NULL);
+   char *path = NULL;
+
    if (exports == NULL)
    {
       return cgi_error_memory();
@@ -1227,9 +1346,19 @@ struct http_response *error_response(struct client_state *csp,
       return cgi_error_memory();
    }
 
-   err = map(exports, "host", 1, html_encode(csp->http->host), 0);
+   if (csp->flags & CSP_FLAG_FORCED)
+   {
+      path = strdup(FORCE_PREFIX);
+   }
+   else
+   {
+      path = strdup("");
+   }
+   err = string_append(&path, csp->http->path);
+
+   if (!err) err = map(exports, "host", 1, html_encode(csp->http->host), 0);
    if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0);
-   if (!err) err = map(exports, "path", 1, html_encode(csp->http->path), 0);
+   if (!err) err = map(exports, "path", 1, html_encode_and_free_original(path), 0);
    if (!err) err = map(exports, "error", 1, html_encode_and_free_original(safe_strerror(sys_err)), 0);
    if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); 
    if (!err)
@@ -1289,7 +1418,9 @@ struct http_response *error_response(struct client_state *csp,
  * Description :  CGI function that is called to generate an error
  *                response if the actions editor or toggle CGI are
  *                accessed despite having being disabled at compile-
- *                or run-time.
+ *                or run-time, or if the user followed an untrusted link
+ *                to access a unsafe CGI feature that is only reachable
+ *                through Privoxy directly.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -1309,10 +1440,15 @@ jb_err cgi_error_disabled(struct client_state *csp,
    assert(csp);
    assert(rsp);
 
-   if (NULL == (exports = default_exports(csp, NULL)))
+   if (NULL == (exports = default_exports(csp, "cgi-error-disabled")))
    {
       return JB_ERR_MEMORY;
    }
+   if (map(exports, "url", 1, html_encode(csp->http->url), 0))
+   {
+      /* Not important enough to do anything */
+      log_error(LOG_LEVEL_ERROR, "Failed to fill in url.");
+   }
 
    return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
 }
@@ -1445,7 +1581,7 @@ jb_err cgi_error_no_template(struct client_state *csp,
    strcat(rsp->body, body_suffix);
 
    rsp->status = strdup(status);
-   if (rsp->body == NULL)
+   if (rsp->status == NULL)
    {
       return JB_ERR_MEMORY;
    }
@@ -1510,7 +1646,7 @@ jb_err cgi_error_unknown(struct client_state *csp,
    rsp->head_length = 0;
    rsp->is_static = 0;
 
-   sprintf(errnumbuf, "%d", error_to_report);
+   snprintf(errnumbuf, sizeof(errnumbuf), "%d", error_to_report);
 
    rsp->body = malloc(strlen(body_prefix) + strlen(errnumbuf) + strlen(body_suffix) + 1);
    if (rsp->body == NULL)
@@ -1522,7 +1658,7 @@ jb_err cgi_error_unknown(struct client_state *csp,
    strcat(rsp->body, body_suffix);
 
    rsp->status = strdup(status);
-   if (rsp->body == NULL)
+   if (rsp->status == NULL)
    {
       return JB_ERR_MEMORY;
    }
@@ -1679,7 +1815,7 @@ void get_http_time(int time_offset, char *buf)
 
    struct tm *t;
    time_t current_time;
-#if defined(HAVE_GMTIME_R) && !defined(OSX_DARWIN)
+#if defined(HAVE_GMTIME_R)
    /*
     * Declare dummy up here (instead of inside get/set gmt block) so it
     * doesn't go out of scope before it's potentially used in snprintf later.
@@ -1697,12 +1833,12 @@ void get_http_time(int time_offset, char *buf)
 
    /* get and save the gmt */
    {
-#ifdef OSX_DARWIN
+#if HAVE_GMTIME_R
+      t = gmtime_r(&current_time, &dummy);
+#elif FEATURE_PTHREAD
       pthread_mutex_lock(&gmtime_mutex);
       t = gmtime(&current_time);
       pthread_mutex_unlock(&gmtime_mutex);
-#elif HAVE_GMTIME_R
-      t = gmtime_r(&current_time, &dummy);
 #else
       t = gmtime(&current_time);
 #endif
@@ -1753,7 +1889,7 @@ struct http_response *finish_http_response(struct http_response *rsp)
    /* 
     * Fill in the HTTP Status
     */
-   sprintf(buf, "HTTP/1.0 %s", rsp->status ? rsp->status : "200 OK");
+   snprintf(buf, sizeof(buf), "HTTP/1.0 %s", rsp->status ? rsp->status : "200 OK");
    err = enlist_first(rsp->headers, buf);
 
    /* 
@@ -1765,14 +1901,23 @@ struct http_response *finish_http_response(struct http_response *rsp)
    }
    if (!err)
    {
-      sprintf(buf, "Content-Length: %d", (int)rsp->content_length);
+      snprintf(buf, sizeof(buf), "Content-Length: %d", (int)rsp->content_length);
       err = enlist(rsp->headers, buf);
    }
 
-   /* 
-    * Fill in the default headers:
+   if (strncmpic(rsp->status, "302", 3))
+   {
+     /*
+      * If it's not a redirect without any content,
+      * set the Content-Type to text/html if it's
+      * not already specified.
+      */
+     if (!err) err = enlist_unique(rsp->headers, "Content-Type: text/html", 13);
+   }
+
+   /*
+    * Fill in the rest of the default headers:
     *
-    * Content-Type: default to text/html if not already specified.
     * Date: set to current date/time.
     * Last-Modified: set to date/time the page was last changed.
     * Expires: set to date/time page next needs reloading.
@@ -1780,8 +1925,6 @@ struct http_response *finish_http_response(struct http_response *rsp)
     * 
     * See http://www.w3.org/Protocols/rfc2068/rfc2068
     */
-   if (!err) err = enlist_unique(rsp->headers, "Content-Type: text/html", 13);
-
    if (rsp->is_static)
    {
       /*
@@ -1804,6 +1947,11 @@ struct http_response *finish_http_response(struct http_response *rsp)
          err = enlist_unique_header(rsp->headers, "Expires", buf);
       }
    }
+   else if (!strncmpic(rsp->status, "302", 3))
+   {
+      get_http_time(0, buf);
+      if (!err) err = enlist_unique_header(rsp->headers, "Date", buf);
+   }
    else
    {
       /*
@@ -1829,7 +1977,7 @@ struct http_response *finish_http_response(struct http_response *rsp)
 
       get_http_time(0, buf);
       if (!err) err = enlist_unique_header(rsp->headers, "Date", buf);
-      if(!strncmpic(rsp->status, "404", 3) || !strncmpic(rsp->status, "503", 3))
+      if (!strncmpic(rsp->status, "404", 3) || !strncmpic(rsp->status, "503", 3))
       {
          if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", "Wed, 08 Jun 1955 12:00:00 GMT");
       }
@@ -1841,6 +1989,13 @@ struct http_response *finish_http_response(struct http_response *rsp)
       if (!err) err = enlist_unique_header(rsp->headers, "Pragma", "no-cache");
    }
 
+   /*
+    * Quoting RFC 2616:
+    *
+    * HTTP/1.1 applications that do not support persistent connections MUST
+    * include the "close" connection option in every message.
+    */
+   if (!err) err = enlist_unique_header(rsp->headers, "Connection", "close");
 
    /* 
     * Write the head
@@ -2060,7 +2215,7 @@ jb_err template_load(struct client_state *csp, char **template_ptr,
  *                                    Caller must free().
  *          2  :  exports = map with fill in symbol -> name pairs
  *
- * Returns     :  JB_ERR_OK on success
+ * Returns     :  JB_ERR_OK on success (and for uncritical errors)
  *                JB_ERR_MEMORY on out-of-memory error
  *
  *********************************************************************/
@@ -2131,15 +2286,35 @@ jb_err template_fill(char **template_ptr, const struct map *exports)
       }
       else
       {
-         pcrs_execute(job, file_buffer, size, &tmp_out_buffer, &size);
-         free(file_buffer);
+         error = pcrs_execute(job, file_buffer, size, &tmp_out_buffer, &size);
+
          pcrs_free_job(job);
          if (NULL == tmp_out_buffer)
          {
             *template_ptr = NULL;
             return JB_ERR_MEMORY;
          }
-         file_buffer = tmp_out_buffer;
+
+         if (error < 0)
+         {
+            /* 
+             * Substitution failed, keep the original buffer,
+             * log the problem and ignore it.
+             * 
+             * The user might see some unresolved @CGI_VARIABLES@,
+             * but returning a special CGI error page seems unreasonable
+             * and could mask more important error messages.
+             */
+            free(tmp_out_buffer);
+            log_error(LOG_LEVEL_ERROR, "Failed to execute s/%s/%s/%s. %s",
+               buf, m->value, flags, pcrs_strerror(error));
+         }
+         else
+         {
+            /* Substitution succeeded, use modified buffer. */
+            free(file_buffer);
+            file_buffer = tmp_out_buffer;
+         }
       }
    }
 
@@ -2235,16 +2410,18 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
    if (!err) err = map(exports, "my-hostname",   1, html_encode(csp->my_hostname ? csp->my_hostname : "unknown"), 0);
    if (!err) err = map(exports, "homepage",      1, html_encode(HOME_PAGE_URL), 0);
    if (!err) err = map(exports, "default-cgi",   1, html_encode(CGI_PREFIX), 0);
-   if (!err) err = map(exports, "menu",          1, make_menu(caller), 0);
+   if (!err) err = map(exports, "menu",          1, make_menu(caller, csp->config->feature_flags), 0);
    if (!err) err = map(exports, "code-status",   1, CODE_STATUS, 1);
    if (!strncmpic(csp->config->usermanual, "file://", 7) ||
        !strncmpic(csp->config->usermanual, "http", 4))
    {
-      if (!err) err = map(exports, "user-manual", 1, csp->config->usermanual ,1);
+      /* Manual is located somewhere else, just link to it. */
+      if (!err) err = map(exports, "user-manual", 1, html_encode(csp->config->usermanual), 0);
    }
    else
    {
-      if (!err) err = map(exports, "user-manual", 1, "http://"CGI_SITE_2_HOST"/user-manual/" ,1);
+      /* Manual is delivered by Privoxy. */
+      if (!err) err = map(exports, "user-manual", 1, html_encode(CGI_PREFIX"user-manual/"), 0);
    }
    if (!err) err = map(exports, "actions-help-prefix", 1, ACTIONS_HELP_PREFIX ,1);
 #ifdef FEATURE_TOGGLE
@@ -2421,14 +2598,18 @@ jb_err map_conditional(struct map *exports, const char *name, int choose_first)
  *
  * Description :  Returns an HTML-formatted menu of the available 
  *                unhidden CGIs, excluding the one given in <self>
+ *                and the toggle CGI if toggling is disabled.
  *
- * Parameters  :  self = name of CGI to leave out, can be NULL for
+ * Parameters  :
+ *          1  :  self = name of CGI to leave out, can be NULL for
  *                complete listing.
+ *          2  :  feature_flags = feature bitmap from csp->config
+ *                
  *
  * Returns     :  menu string, or NULL on out-of-memory error.
  *
  *********************************************************************/
-char *make_menu(const char *self)
+char *make_menu(const char *self, const unsigned feature_flags)
 {
    const struct cgi_dispatcher *d;
    char *result = strdup("");
@@ -2441,13 +2622,36 @@ char *make_menu(const char *self)
    /* List available unhidden CGI's and export as "other-cgis" */
    for (d = cgi_dispatchers; d->name; d++)
    {
+
+#ifdef FEATURE_TOGGLE
+      if (!(feature_flags & RUNTIME_FEATURE_CGI_TOGGLE) && !strcmp(d->name, "toggle"))
+      {
+         /*
+          * Suppress the toggle link if remote toggling is disabled.
+          */
+         continue;
+      }
+#endif /* def FEATURE_TOGGLE */
+
       if (d->description && strcmp(d->name, self))
       {
-         string_append(&result, "<li><a href=\"" CGI_PREFIX);
+         char *html_encoded_prefix;
+
+         string_append(&result, "<li><a href=\"");
+         html_encoded_prefix = html_encode(CGI_PREFIX);
+         if (html_encoded_prefix == NULL)
+         {
+            return NULL;  
+         }
+         else
+         {
+            string_append(&result, html_encoded_prefix);
+            free(html_encoded_prefix);
+         }
          string_append(&result, d->name);
          string_append(&result, "\">");
          string_append(&result, d->description);
-         string_append(&result, "</a></li>");
+         string_append(&result, "</a></li>\n");
       }
    }