+/*********************************************************************
+ *
+ * Function : client_referrer
+ *
+ * Description : Handle the "referer" config setting properly.
+ * Called from `sed'.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_referrer(struct client_state *csp, char **header)
+{
+ const char *newval;
+ const char *host;
+ char *referer;
+ size_t hostlenght;
+
+#ifdef FEATURE_FORCE_LOAD
+ /* Since the referrer can include the prefix even
+ * if the request itself is non-forced, we must
+ * clean it unconditionally
+ */
+ strclean(*header, FORCE_PREFIX);
+#endif /* def FEATURE_FORCE_LOAD */
+
+ /*
+ * Are we sending referer?
+ */
+ if ((csp->action->flags & ACTION_HIDE_REFERER) == 0)
+ {
+ return JB_ERR_OK;
+ }
+
+ newval = csp->action->string[ACTION_STRING_REFERER];
+
+ if ((0 != strcmpic(newval, "conditional-block")))
+ {
+ freez(*header);
+ }
+ if ((newval == NULL) || (0 == strcmpic(newval, "block")) )
+ {
+ /*
+ * Blocking referer
+ */
+ log_error(LOG_LEVEL_HEADER, "Referer crunched!");
+ return JB_ERR_OK;
+ }
+ else if (0 == strcmpic(newval, "conditional-block"))
+ {
+ /*
+ * Block referer if host has changed.
+ */
+
+ if (NULL == (host = strdup(csp->http->hostport)))
+ {
+ freez(*header);
+ log_error(LOG_LEVEL_HEADER, "Referer crunched! Couldn't allocate memory for temporary host copy.");
+ return JB_ERR_MEMORY;
+ }
+ if (NULL == (referer = strdup(*header)))
+ {
+ freez(*header);
+ freez(host);
+ log_error(LOG_LEVEL_HEADER, "Referer crunched! Couldn't allocate memory for temporary referer copy.");
+ return JB_ERR_MEMORY;
+ }
+ hostlenght = strlen(host);
+ if ( hostlenght < (strlen(referer)-17) ) /*referer begins with 'Referer: http[s]://'*/
+ {
+ /*Shorten referer to make sure the referer is blocked
+ *if www.example.org/www.example.com-shall-see-the-referer/
+ *links to www.example.com/
+ */
+ referer[hostlenght+17] = '\0';
+ }
+ if ( 0 == strstr(referer, host)) /*Host has changed*/
+ {
+ log_error(LOG_LEVEL_HEADER, "New host is: %s. Crunching %s!", host, *header);
+ freez(*header);
+ }
+ else
+ {
+ log_error(LOG_LEVEL_HEADER, "%s (not modified, still on %s)", *header, host);
+ }
+ freez(referer);
+ freez(host);
+ return JB_ERR_OK;
+ }
+ else if (0 != strcmpic(newval, "forge"))
+ {
+ /*
+ * We have a specific (fixed) referer we want to send.
+ */
+ if ((0 != strncmpic(newval, "http://", 7)) && (0 != strncmpic(newval, "https://", 8)))
+ {
+ log_error(LOG_LEVEL_HEADER, "Parameter: +referrer{%s} is a bad idea, but I don't care.", newval);
+ }
+ *header = strdup("Referer: ");
+ string_append(header, newval);
+ log_error(LOG_LEVEL_HEADER, "Referer overwritten with: %s", *header);
+
+ return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+ }
+ else
+ {
+ /*
+ * Forge a referer as http://[hostname:port of REQUEST]/
+ * to fool stupid checks for in-site links
+ */
+
+ *header = strdup("Referer: http://");
+ string_append(header, csp->http->hostport);
+ string_append(header, "/");
+ log_error(LOG_LEVEL_HEADER, "Referer forged to: %s", *header);
+
+ return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+ }
+}
+
+/*********************************************************************
+ *
+ * Function : client_accept_language
+ *
+ * Description : Handle the "Accept-Language" config setting properly.
+ * Called from `sed'.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_accept_language(struct client_state *csp, char **header)
+{
+ const char *newval;
+
+ /*
+ * Are we messing with the Accept-Language?
+ */
+ if ((csp->action->flags & ACTION_HIDE_ACCEPT_LANGUAGE) == 0)
+ {
+ /*I don't think so*/
+ return JB_ERR_OK;
+ }
+
+ newval = csp->action->string[ACTION_STRING_LANGUAGE];
+
+ if ((newval == NULL) || (0 == strcmpic(newval, "block")) )
+ {
+ /*
+ * Blocking Accept-Language header
+ */
+ log_error(LOG_LEVEL_HEADER, "Crunching Accept-Language!");
+ freez(*header);
+ return JB_ERR_OK;
+ }
+ else
+ {
+ /*
+ * Replacing Accept-Language header
+ */
+ freez(*header);
+ *header = strdup("Accept-Language: ");
+ string_append(header, newval);
+
+ if (*header == NULL)
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Insufficent memory. Accept-Language header crunched without replacement.");
+ }
+ else
+ {
+ log_error(LOG_LEVEL_HEADER,
+ "Accept-Language header crunched and replaced with: %s", *header);
+ }
+ }
+ return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+}
+
+/*********************************************************************
+ *
+ * Function : crunch_client_header
+ *
+ * Description : Crunch client header if it matches a string supplied by the
+ * user. Called from `sed'.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success and always succeeds
+ *
+ *********************************************************************/
+jb_err crunch_client_header(struct client_state *csp, char **header)
+{
+ const char *crunch_pattern;
+ /*Is there a header to crunch*/
+
+ if ((csp->action->flags & ACTION_CRUNCH_CLIENT_HEADER))
+ {
+ crunch_pattern = csp->action->string[ACTION_STRING_CLIENT_HEADER];
+
+ /*Is the current header the lucky one?*/
+ if (strstr(*header, crunch_pattern))
+ {
+ log_error(LOG_LEVEL_HEADER, "Crunching client header: %s (contains: %s)", *header, crunch_pattern);
+ freez(*header);
+ }
+ }
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : client_uagent
+ *
+ * Description : Handle the "user-agent" config setting properly
+ * and remember its original value to enable browser
+ * bug workarounds. Called from `sed'.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_uagent(struct client_state *csp, char **header)
+{
+ const char *newval;
+
+ if ((csp->action->flags & ACTION_HIDE_USER_AGENT) == 0)
+ {
+ return JB_ERR_OK;
+ }
+
+ newval = csp->action->string[ACTION_STRING_USER_AGENT];
+ if (newval == NULL)
+ {
+ return JB_ERR_OK;
+ }
+
+ freez(*header);
+ *header = strdup("User-Agent: ");
+ string_append(header, newval);
+
+ log_error(LOG_LEVEL_HEADER, "Modified: %s", *header);
+
+ return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+}
+
+/*********************************************************************
+ *
+ * Function : client_ua
+ *
+ * Description : Handle "ua-" headers properly. Called from `sed'.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_ua(struct client_state *csp, char **header)
+{
+ if ((csp->action->flags & ACTION_HIDE_USER_AGENT) != 0)
+ {
+ log_error(LOG_LEVEL_HEADER, "crunched User-Agent!");
+ freez(*header);
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : client_from
+ *
+ * Description : Handle the "from" config setting properly.
+ * Called from `sed'.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_from(struct client_state *csp, char **header)
+{
+ const char *newval;
+
+ if ((csp->action->flags & ACTION_HIDE_FROM) == 0)
+ {
+ return JB_ERR_OK;
+ }
+
+ freez(*header);
+
+ newval = csp->action->string[ACTION_STRING_FROM];
+
+ /*
+ * Are we blocking the e-mail address?
+ */
+ if ((newval == NULL) || (0 == strcmpic(newval, "block")) )
+ {
+ log_error(LOG_LEVEL_HEADER, "crunched From!");
+ return JB_ERR_OK;
+ }
+
+ log_error(LOG_LEVEL_HEADER, " modified");
+
+ *header = strdup("From: ");
+ string_append(header, newval);
+
+ return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : client_send_cookie
+ *
+ * Description : Handle the "cookie" header properly. Called from `sed'.
+ * If cookie is accepted, add it to the cookie_list,
+ * else we crunch it. Mmmmmmmmmmm ... cookie ......
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_send_cookie(struct client_state *csp, char **header)
+{
+ jb_err result = JB_ERR_OK;
+
+ if ((csp->action->flags & ACTION_NO_COOKIE_READ) == 0)
+ {
+ /* strlen("cookie: ") == 8 */
+ result = enlist(csp->cookie_list, *header + 8);
+ }
+ else
+ {
+ log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie -- yum!");
+ }
+
+ /*
+ * Always remove the cookie here. The cookie header
+ * will be sent at the end of the header.
+ */
+ freez(*header);
+
+ return result;
+}
+
+
+/*********************************************************************
+ *
+ * Function : client_x_forwarded
+ *
+ * Description : Handle the "x-forwarded-for" config setting properly,
+ * also used in the add_client_headers list. Called from `sed'.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_x_forwarded(struct client_state *csp, char **header)
+{
+ if ((csp->action->flags & ACTION_HIDE_FORWARDED) == 0)
+ {
+ /* Save it so we can re-add it later */
+ freez(csp->x_forwarded);
+ csp->x_forwarded = *header;
+
+ /*
+ * Always set *header = NULL, since this information
+ * will be sent at the end of the header.
+ */
+ *header = NULL;
+ }
+ else
+ {
+ freez(*header);
+ log_error(LOG_LEVEL_HEADER, "crunched x-forwarded-for!");
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : client_max_forwards
+ *
+ * Description : If the HTTP method is OPTIONS or TRACE, subtract one
+ * from the value of the Max-Forwards header field.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_max_forwards(struct client_state *csp, char **header)
+{
+ unsigned int max_forwards;
+
+ if ((0 == strcmpic(csp->http->gpc, "trace")) ||
+ (0 == strcmpic(csp->http->gpc, "options")))
+ {
+ if (1 == sscanf(*header, "Max-Forwards: %u", &max_forwards))
+ {
+ if (max_forwards-- > 0)
+ {
+ snprintf(*header, strlen(*header)+1, "Max-Forwards: %u", max_forwards);
+ log_error(LOG_LEVEL_HEADER, "Max-Forwards header for %s request replaced with: %s",
+ csp->http->gpc, *header);
+ }
+ else
+ {
+ /* FIXME: Follow spec and intercept the request. */
+ log_error(LOG_LEVEL_ERROR,
+ "Non-intercepted %s request with Max-Forwards zero!", csp->http->gpc);
+ }
+ }
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : client_host
+ *
+ * Description : If the request URI did not contain host and
+ * port information, parse and evaluate the Host
+ * header field.
+ *
+ * Also, kill ill-formed HOST: headers as sent by
+ * Apple's iTunes software when used with a proxy.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_host(struct client_state *csp, char **header)
+{
+ char *p, *q;
+
+ /*
+ * If the header field name is all upper-case, chances are that it's
+ * an ill-formed one from iTunes. BTW, killing innocent headers here is
+ * not a problem -- they are regenerated later.
+ */
+ if ((*header)[1] == 'O')
+ {
+ log_error(LOG_LEVEL_HEADER, "Killed all-caps Host header line: %s", *header);
+ freez(*header);
+ return JB_ERR_OK;
+ }
+
+ if (!csp->http->hostport || (*csp->http->hostport == '*') ||
+ *csp->http->hostport == ' ' || *csp->http->hostport == '\0')
+ {
+
+ if (NULL == (p = strdup((*header)+6)))
+ {
+ return JB_ERR_MEMORY;
+ }
+ chomp(p);
+ if (NULL == (q = strdup(p)))
+ {
+ freez(p);
+ return JB_ERR_MEMORY;
+ }
+
+ freez(csp->http->hostport);
+ csp->http->hostport = p;
+ freez(csp->http->host);
+ csp->http->host = q;
+ q = strchr(csp->http->host, ':');
+ if (q != NULL)
+ {
+ /* Terminate hostname and evaluate port string */
+ *q++ = '\0';
+ csp->http->port = atoi(q);
+ }
+ else
+ {
+ csp->http->port = csp->http->ssl ? 443 : 80;
+ }
+
+ log_error(LOG_LEVEL_HEADER, "New host and port from Host field: %s = %s:%d",
+ csp->http->hostport, csp->http->host, csp->http->port);
+ }
+
+ return JB_ERR_OK;
+}
+
+/*********************************************************************
+ *
+ * Function : client_if_modified_since
+ *
+ * Description : Remove or modify the If-Modified-Since header.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_if_modified_since(struct client_state *csp, char **header)
+{
+ char newheader[50];
+#ifdef HAVE_GMTIME_R
+ struct tm gmt;
+#endif
+ struct tm *timeptr = NULL;
+ time_t tm = 0;
+ const char *newval;
+ long int rtime;
+ long int hours, minutes, seconds;
+ int negative = 0;
+ char * endptr;
+
+ if ( 0 == strcmpic(*header, "If-Modified-Since: Wed, 08 Jun 1955 12:00:00 GMT"))
+ {
+ /*
+ * The client got an error message because of a temporary problem,
+ * the problem is gone and the client now tries to revalidate our
+ * error message on the real server. The revalidation would always
+ * end with the transmission of the whole document and there is
+ * no need to expose the bogus If-Modified-Since header.
+ */
+ log_error(LOG_LEVEL_HEADER, "Crunching useless If-Modified-Since header.");
+ freez(*header);
+ }
+ else if (csp->action->flags & ACTION_HIDE_IF_MODIFIED_SINCE)
+ {
+ newval = csp->action->string[ACTION_STRING_IF_MODIFIED_SINCE];
+
+ if ((0 == strcmpic(newval, "block")))
+ {
+ log_error(LOG_LEVEL_HEADER, "Crunching %s", *header);
+ freez(*header);
+ }
+ else /* add random value */
+ {
+ if ((timeptr = parse_header_time(*header, &tm)) == NULL)
+ {
+ log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s (crunching!)", *header);
+ freez(*header);
+ }
+ else
+ {
+ rtime = strtol(newval, &endptr, 0);
+ if(rtime)
+ {
+ log_error(LOG_LEVEL_HEADER, "Randomizing: %s (random range: %d minut%s)",
+ *header, rtime, (rtime == 1 || rtime == -1) ? "e": "es");
+ if(rtime < 0)
+ {
+ rtime *= -1;
+ negative = 1;
+ }
+ rtime *= 60;
+ rtime = pick_from_range(rtime);
+ }
+ else
+ {
+ log_error(LOG_LEVEL_ERROR, "Random range is 0. Assuming time transformation test.",
+ *header);
+ }
+ tm += rtime * (negative ? -1 : 1);
+#ifdef HAVE_GMTIME_R
+ timeptr = gmtime_r(&tm, &gmt);
+#elif FEATURE_PTHREAD
+ pthread_mutex_lock(&gmtime_mutex);
+ timeptr = gmtime(&tm);
+ pthread_mutex_unlock(&gmtime_mutex);
+#else
+ timeptr = gmtime(&tm);
+#endif
+ strftime(newheader, sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr);
+
+ freez(*header);
+ *header = strdup("If-Modified-Since: ");
+ string_append(header, newheader);
+
+ if (*header == NULL)
+ {
+ log_error(LOG_LEVEL_HEADER, "Insufficent memory, header crunched without replacement.");
+ return JB_ERR_MEMORY;
+ }
+
+ if(LOG_LEVEL_HEADER & debug) /* Save cycles if the user isn't interested. */
+ {
+ hours = rtime / 3600;
+ minutes = rtime / 60 % 60;
+ seconds = rtime % 60;
+
+ log_error(LOG_LEVEL_HEADER, "Randomized: %s (%s %d hou%s %d minut%s %d second%s",
+ *header, (negative) ? "subtracted" : "added", hours, (hours == 1) ? "r" : "rs",
+ minutes, (minutes == 1) ? "e" : "es", seconds, (seconds == 1) ? ")" : "s)");
+ }
+ }
+ }
+ }
+
+ return JB_ERR_OK;
+}