Some updates regarding header filtering,
[privoxy.git] / cgi.c
diff --git a/cgi.c b/cgi.c
index f65f9a2..fac49bd 100644 (file)
--- a/cgi.c
+++ b/cgi.c
@@ -1,4 +1,4 @@
-const char cgi_rcs[] = "$Id: cgi.c,v 1.81 2006/12/09 13:49:16 fabiankeil Exp $";
+const char cgi_rcs[] = "$Id: cgi.c,v 1.95 2007/02/10 17:01:37 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgi.c,v $
@@ -11,8 +11,8 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.81 2006/12/09 13:49:16 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,68 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.81 2006/12/09 13:49:16 fabiankeil Exp $";
  *
  * Revisions   :
  *    $Log: cgi.c,v $
+ *    Revision 1.95  2007/02/10 17:01:37  fabiankeil
+ *    Don't overlook map result for the forwarding-type.
+ *
+ *    Revision 1.94  2007/02/08 19:44:49  fabiankeil
+ *    Use a transparent background for the PNG replacement pattern.
+ *
+ *    Revision 1.93  2007/02/07 10:45:22  fabiankeil
+ *    - Save the reason for generating http_responses.
+ *    - Fix --disable-toggle (again).
+ *    - Use TBL birthday hack for 403 responses as well.
+ *    - Uglify the @menu@ again to fix JavaScript
+ *      errors on the "blocked" template.
+ *    - Escape an ampersand in cgi_error_unknown().
+ *
+ *    Revision 1.92  2007/01/28 13:41:17  fabiankeil
+ *    - Add HEAD support to finish_http_response.
+ *    - Add error favicon to internal HTML error messages.
+ *
+ *    Revision 1.91  2007/01/27 13:09:16  fabiankeil
+ *    Add new config option "templdir" to
+ *    change the templates directory.
+ *
+ *    Revision 1.90  2007/01/25 13:47:26  fabiankeil
+ *    Added "forwarding-failed" template support for error_response().
+ *
+ *    Revision 1.89  2007/01/23 15:51:16  fabiankeil
+ *    Add favicon delivery functions.
+ *
+ *    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.
@@ -528,6 +590,7 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.81 2006/12/09 13:49:16 fabiankeil Exp $";
 #include "encode.h"
 #include "ssplit.h"
 #include "errlog.h"
+#include "filters.h"
 #include "miscutil.h"
 #include "cgisimple.h"
 #ifdef FEATURE_CGI_EDIT_ACTIONS
@@ -561,7 +624,7 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
    { "show-status", 
          cgi_show_status,  
 #ifdef FEATURE_CGI_EDIT_ACTIONS
-        "View & change the current configuration",
+        "View & change the current configuration",
 #else
         "View the current configuration",
 #endif
@@ -652,6 +715,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. */ }, 
@@ -660,7 +729,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) */ },
@@ -687,12 +756,12 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
  */
 const char image_pattern_data[] =
    "\211\120\116\107\015\012\032\012\000\000\000\015\111\110\104"
-   "\122\000\000\000\004\000\000\000\004\010\002\000\000\000\046"
-   "\223\011\051\000\000\000\006\142\113\107\104\000\310\000\310"
-   "\000\310\052\045\225\037\000\000\000\032\111\104\101\124\170"
-   "\332\143\070\161\342\304\377\377\377\041\044\003\234\165\342"
-   "\304\011\006\234\062\000\125\200\052\251\125\174\360\223\000"
-   "\000\000\000\111\105\116\104\256\102\140\202";
+   "\122\000\000\000\004\000\000\000\004\010\006\000\000\000\251"
+   "\361\236\176\000\000\000\006\142\113\107\104\000\000\000\000"
+   "\000\000\371\103\273\177\000\000\000\033\111\104\101\124\010"
+   "\327\143\140\140\140\060\377\377\377\077\003\234\106\341\060"
+   "\060\230\063\020\124\001\000\161\021\031\241\034\364\030\143"
+   "\000\000\000\000\111\105\116\104\256\102\140\202";
 
 /*
  * 1x1 transparant PNG.
@@ -827,7 +896,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;
 
@@ -844,6 +913,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
@@ -872,7 +989,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)))
@@ -919,10 +1035,6 @@ static struct http_response *dispatch_known_cgi(struct client_state * csp,
       return cgi_error_memory();
    }
 
-   log_error(LOG_LEVEL_GPC, "%s%s cgi call", csp->http->hostport, csp->http->path);
-   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 3", 
-                            csp->ip_addr_str, csp->http->cmd); 
-
    /* 
     * Find and start the right CGI function
     */
@@ -935,10 +1047,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, CGI_PREFIX, sizeof(CGI_PREFIX)-1)))
-             )
+         if (d->harmless || referrer_is_safe(csp))
          {
             err = (d->handler)(csp, rsp, param_list);
          }
@@ -975,7 +1084,8 @@ static struct http_response *dispatch_known_cgi(struct client_state * csp,
          if (!err)
          {
             /* It worked */
-            return finish_http_response(rsp);
+            rsp->reason = RSP_REASON_CGI_CALL;
+            return finish_http_response(csp, rsp);
          }
          else
          {
@@ -1069,7 +1179,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;
@@ -1201,7 +1311,7 @@ jb_err get_number_param(struct client_state *csp,
          return JB_ERR_CGI_PARAMS;
       }
 
-      ch -= '0';
+      ch = (char)(ch - '0');
 
       /* Note:
        *
@@ -1215,7 +1325,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 */
@@ -1262,11 +1372,13 @@ struct http_response *error_response(struct client_state *csp,
       return cgi_error_memory();
    }
 
+#ifdef FEATURE_FORCE_LOAD
    if (csp->flags & CSP_FLAG_FORCED)
    {
       path = strdup(FORCE_PREFIX);
    }
    else
+#endif /* def FEATURE_FORCE_LOAD */
    {
       path = strdup("");
    }
@@ -1304,6 +1416,48 @@ struct http_response *error_response(struct client_state *csp,
          free_http_response(rsp);
          return cgi_error_memory();
       }
+      rsp->reason = RSP_REASON_NO_SUCH_DOMAIN;
+   }
+   else if (!strcmp(templatename, "forwarding-failed"))
+   {
+      const struct forward_spec * fwd = forward_url(csp->http, csp);
+      if (fwd == NULL)
+      {
+         log_error(LOG_LEVEL_FATAL, "gateway spec is NULL. This shouldn't happen!");
+         /* Never get here - LOG_LEVEL_FATAL causes program exit */
+      }
+
+      /*
+       * XXX: While the template is called forwarding-failed,
+       * it currently only handles socks forwarding failures.
+       */
+      assert(fwd->type != SOCKS_NONE);
+
+      /*
+       * Map failure reason, forwarding type and forwarder.
+       */
+      if (NULL == csp->error_message)
+      {
+         /*
+          * Either we forgot to record the failure reason,
+          * or the memory allocation failed.
+          */
+         log_error(LOG_LEVEL_ERROR, "Socks failure reason missing.");
+         csp->error_message = strdup("Failure reason missing. Check the log file for details.");
+      }
+      if (!err) err = map(exports, "gateway", 1, fwd->gateway_host, 1);
+      if (!err) err = map(exports, "forwarding-type", 1, (fwd->type == SOCKS_4) ?
+                         "socks4-" : "socks4a-", 1);
+      if (!err) err = map(exports, "error-message", 1, html_encode(csp->error_message), 0);
+
+      if (!err) rsp->status = strdup("503 Forwarding failure");
+      if ((rsp->status == NULL) || (NULL == csp->error_message) || err)
+      {
+         free_map(exports);
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+      rsp->reason = RSP_REASON_FORWARDING_FAILED;
    }
    else if (!strcmp(templatename, "connect-failed"))
    {
@@ -1314,6 +1468,7 @@ struct http_response *error_response(struct client_state *csp,
          free_http_response(rsp);
          return cgi_error_memory();
       }
+      rsp->reason = RSP_REASON_CONNECT_FAILED;
    }
 
    err = template_fill_for_cgi(csp, templatename, exports, rsp);
@@ -1323,7 +1478,7 @@ struct http_response *error_response(struct client_state *csp,
       return cgi_error_memory();
    }
 
-   return finish_http_response(rsp);
+   return finish_http_response(csp, rsp);
 }
 
 
@@ -1334,7 +1489,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...)
@@ -1354,10 +1511,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);
 }
@@ -1384,7 +1546,10 @@ void cgi_init_error_messages(void)
       "\r\n";
    cgi_error_memory_response->body =
       "<html>\r\n"
-      "<head><title>500 Internal Privoxy Error</title></head>\r\n"
+      "<head>\r\n"
+      " <title>500 Internal Privoxy Error</title>\r\n"
+      " <link rel=\"shortcut icon\" href=\"" CGI_PREFIX "error-favicon.ico\" type=\"image/x-icon\">"
+      "</head>\r\n"
       "<body>\r\n"
       "<h1>500 Internal Privoxy Error</h1>\r\n"
       "<p>Privoxy <b>ran out of memory</b> while processing your request.</p>\r\n"
@@ -1396,6 +1561,7 @@ void cgi_init_error_messages(void)
       strlen(cgi_error_memory_response->head);
    cgi_error_memory_response->content_length =
       strlen(cgi_error_memory_response->body);
+   cgi_error_memory_response->reason = RSP_REASON_OUT_OF_MEMORY;
 }
 
 
@@ -1448,7 +1614,10 @@ jb_err cgi_error_no_template(struct client_state *csp,
       "500 Internal Privoxy Error";
    static const char body_prefix[] =
       "<html>\r\n"
-      "<head><title>500 Internal Privoxy Error</title></head>\r\n"
+      "<head>\r\n"
+      " <title>500 Internal Privoxy Error</title>\r\n"
+      " <link rel=\"shortcut icon\" href=\"" CGI_PREFIX "error-favicon.ico\" type=\"image/x-icon\">"
+      "</head>\r\n"
       "<body>\r\n"
       "<h1>500 Internal Privoxy Error</h1>\r\n"
       "<p>Privoxy encountered an error while processing your request:</p>\r\n"
@@ -1490,7 +1659,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;
    }
@@ -1531,7 +1700,10 @@ jb_err cgi_error_unknown(struct client_state *csp,
       "500 Internal Privoxy Error";
    static const char body_prefix[] =
       "<html>\r\n"
-      "<head><title>500 Internal Privoxy Error</title></head>\r\n"
+      "<head>\r\n"
+      " <title>500 Internal Privoxy Error</title>\r\n"
+      " <link rel=\"shortcut icon\" href=\"" CGI_PREFIX "error-favicon.ico\" type=\"image/x-icon\">"
+      "</head>\r\n"
       "<body>\r\n"
       "<h1>500 Internal Privoxy Error</h1>\r\n"
       "<p>Privoxy encountered an error while processing your request:</p>\r\n"
@@ -1539,7 +1711,7 @@ jb_err cgi_error_unknown(struct client_state *csp,
    static const char body_suffix[] =
       "</b></p>\r\n"
       "<p>Please "
-      "<a href=\"http://sourceforge.net/tracker/?group_id=11118&atid=111118\">"
+      "<a href=\"http://sourceforge.net/tracker/?group_id=11118&amp;atid=111118\">"
       "file a bug report</a>.</p>\r\n"
       "</body>\r\n"
       "</html>\r\n";
@@ -1554,8 +1726,9 @@ jb_err cgi_error_unknown(struct client_state *csp,
    rsp->content_length = 0;
    rsp->head_length = 0;
    rsp->is_static = 0;
+   rsp->reason = RSP_REASON_INTERNAL_ERROR;
 
-   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)
@@ -1567,7 +1740,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;
    }
@@ -1705,6 +1878,9 @@ char *add_help_link(const char *item,
  *                HTTP header - e.g.:
  *                "Sun, 06 Nov 1994 08:49:37 GMT"
  *
+ *                XXX: Should probably get a third parameter for
+ *                the buffer size.
+ *
  * Parameters  :  
  *          1  :  time_offset = Time returned will be current time
  *                              plus this number of seconds.
@@ -1774,6 +1950,8 @@ void get_http_time(int time_offset, char *buf)
  *
  * Description :  Fill in the missing headers in an http response,
  *                and flatten the headers to an http head.
+ *                For HEAD requests the body is freed once
+ *                the Content-Length header is set.
  *
  * Parameters  :
  *          1  :  rsp = pointer to http_response to be processed
@@ -1782,7 +1960,7 @@ void get_http_time(int time_offset, char *buf)
  *                On error, free()s rsp and returns cgi_error_memory()
  *
  *********************************************************************/
-struct http_response *finish_http_response(struct http_response *rsp)
+struct http_response *finish_http_response(const struct client_state *csp, struct http_response *rsp)
 {
    char buf[BUFFER_SIZE];
    jb_err err;
@@ -1798,7 +1976,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);
 
    /* 
@@ -1810,10 +1988,28 @@ 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);
    }
 
+   if (0 == strcmpic(csp->http->gpc, "head"))
+   {
+      /*
+       * The client only asked for the head. Dispose
+       * the body and log an offensive message.
+       *
+       * While it may seem to be a bit inefficient to
+       * prepare the body if it isn't needed, it's the
+       * only way to get the Content-Length right for
+       * dynamic pages. We could have disposed the body
+       * earlier, but not without duplicating the
+       * Content-Length setting code above.
+       */
+      log_error(LOG_LEVEL_CGI, "Preparing to give head to %s.", csp->ip_addr_str);
+      freez(rsp->body);
+      rsp->content_length = 0;
+   }
+
    if (strncmpic(rsp->status, "302", 3))
    {
      /*
@@ -1875,7 +2071,7 @@ struct http_response *finish_http_response(struct http_response *rsp)
        * is older than Privoxy's error message, the server would send status code
        * 304 and the browser would display the outdated error message again and again.
        *
-       * For documents delivered with status code 404 or 503 we set "Last-Modified"
+       * For documents delivered with status code 403, 404 and 503 we set "Last-Modified"
        * to Tim Berners-Lee's birthday, which predates the age of any page on the web
        * and can be safely used to "revalidate" without getting a status code 304.
        *
@@ -1886,7 +2082,9 @@ 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, "403", 3)
+       || !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");
       }
@@ -1974,9 +2172,8 @@ void free_http_response(struct http_response *rsp)
  * Function    :  template_load
  *
  * Description :  CGI support function that loads a given HTML
- *                template from the confdir, ignoring comment
- *                lines and following #include statements up to
- *                a depth of 1.
+ *                template, ignoring comment lines and following
+ *                #include statements up to a depth of 1.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -2023,11 +2220,23 @@ jb_err template_load(struct client_state *csp, char **template_ptr,
       }
    }
 
-   /* Generate full path */
+   /*
+    * Generate full path using either templdir
+    * or confdir/templates as base directory.
+    */
+   if (NULL != csp->config->templdir)
+   {
+      templates_dir_path = strdup(csp->config->templdir);
+   }
+   else
+   {
+      templates_dir_path = make_path(csp->config->confdir, "templates");
+   }
 
-   templates_dir_path = make_path(csp->config->confdir, "templates");
    if (templates_dir_path == NULL)
    {
+      log_error(LOG_LEVEL_ERROR, "Out of memory while generating template path for %s.",
+         templatename);
       return JB_ERR_MEMORY;
    }
 
@@ -2035,6 +2244,8 @@ jb_err template_load(struct client_state *csp, char **template_ptr,
    free(templates_dir_path);
    if (full_path == NULL)
    {
+      log_error(LOG_LEVEL_ERROR, "Out of memory while generating full template path for %s.",
+         templatename);
       return JB_ERR_MEMORY;
    }
 
@@ -2043,6 +2254,7 @@ jb_err template_load(struct client_state *csp, char **template_ptr,
    file_buffer = strdup("");
    if (file_buffer == NULL)
    {
+      log_error(LOG_LEVEL_ERROR, "Not enough free memory to buffer %s.", full_path);
       free(full_path);
       return JB_ERR_MEMORY;
    }
@@ -2124,7 +2336,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
  *
  *********************************************************************/
@@ -2195,15 +2407,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;
+         }
       }
    }
 
@@ -2304,11 +2536,13 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
    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
@@ -2317,7 +2551,7 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
    if (!err) err = map_block_killer(exports, "can-toggle");
 #endif
 
-   snprintf(buf, 20, "%d", csp->config->hport);
+   snprintf(buf, sizeof(buf), "%d", csp->config->hport);
    if (!err) err = map(exports, "my-port", 1, buf, 1);
 
    if(!strcmp(CODE_STATUS, "stable"))
@@ -2386,7 +2620,7 @@ jb_err map_block_killer(struct map *exports, const char *name)
    assert(name);
    assert(strlen(name) < 490);
 
-   snprintf(buf, 1000, "if-%s-start.*if-%s-end", name, name);
+   snprintf(buf, sizeof(buf), "if-%s-start.*if-%s-end", name, name);
    return map(exports, buf, 1, "", 1);
 }
 
@@ -2416,7 +2650,7 @@ jb_err map_block_keep(struct map *exports, const char *name)
    assert(name);
    assert(strlen(name) < 490);
 
-   snprintf(buf, 500, "if-%s-start", name);
+   snprintf(buf, sizeof(buf), "if-%s-start", name);
    err = map(exports, buf, 1, "", 1);
 
    if (err)
@@ -2424,7 +2658,7 @@ jb_err map_block_keep(struct map *exports, const char *name)
       return err;
    }
 
-   snprintf(buf, 500, "if-%s-end", name);
+   snprintf(buf, sizeof(buf), "if-%s-end", name);
    return map(exports, buf, 1, "", 1);
 }
 
@@ -2463,7 +2697,7 @@ jb_err map_conditional(struct map *exports, const char *name, int choose_first)
    assert(name);
    assert(strlen(name) < 480);
 
-   snprintf(buf, 1000, (choose_first
+   snprintf(buf, sizeof(buf), (choose_first
       ? "else-not-%s@.*@endif-%s"
       : "if-%s-then@.*@else-not-%s"),
       name, name);
@@ -2474,7 +2708,7 @@ jb_err map_conditional(struct map *exports, const char *name, int choose_first)
       return err;
    }
 
-   snprintf(buf, 1000, (choose_first ? "if-%s-then" : "endif-%s"), name);
+   snprintf(buf, sizeof(buf), (choose_first ? "if-%s-then" : "endif-%s"), name);
    return map(exports, buf, 1, "", 1);
 }
 
@@ -2522,7 +2756,23 @@ char *make_menu(const char *self, const unsigned feature_flags)
 
       if (d->description && strcmp(d->name, self))
       {
-         string_append(&result, "<li><a href=\"" CGI_PREFIX);
+         char *html_encoded_prefix;
+
+         /*
+          * Line breaks would be great, but break
+          * the "blocked" template's JavaScript.
+          */
+         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);