-const char jcc_rcs[] = "$Id: jcc.c,v 1.142 2007/08/05 13:50:26 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.152 2007/10/04 18:03:34 fabiankeil Exp $";
/*********************************************************************
*
* File : $Source: /cvsroot/ijbswa/current/jcc.c,v $
*
* Revisions :
* $Log: jcc.c,v $
+ * Revision 1.152 2007/10/04 18:03:34 fabiankeil
+ * - Fix a crash when parsing invalid requests whose first header
+ * is rejected by get_header(). Regression (re?)introduced
+ * in r1.143 by yours truly.
+ * - Move ACTION_VANILLA_WAFER handling into parsers.c's
+ * client_cookie_adder() to make sure send-vanilla-wafer can be
+ * controlled through tags (and thus regression-tested).
+ *
+ * Revision 1.151 2007/09/29 10:21:16 fabiankeil
+ * - Move get_filter_function() from jcc.c to filters.c
+ * so the filter functions can be static.
+ * - Don't bother filtering body-less responses.
+ *
+ * Revision 1.150 2007/09/28 16:39:29 fabiankeil
+ * Execute content filters through execute_content_filter().
+ *
+ * Revision 1.149 2007/09/04 15:08:48 fabiankeil
+ * Initialize req to NULL to make sure it's defined if the
+ * first read_socket() call fails. Reported by icmp30.
+ *
+ * Revision 1.148 2007/08/26 16:47:13 fabiankeil
+ * Add Stephen Gildea's --pre-chroot-nslookup patch [#1276666],
+ * extensive comments moved to user manual.
+ *
+ * Revision 1.147 2007/08/25 14:42:40 fabiankeil
+ * Don't crash if a broken header filter wiped out the request line.
+ *
+ * Revision 1.146 2007/08/20 17:09:32 fabiankeil
+ * Fix byte_count calculation in case of flushes
+ * and don't parse the server headers a second time.
+ *
+ * Revision 1.145 2007/08/19 13:13:31 fabiankeil
+ * - If there's a connection problem after we already forwarded
+ * parts of the original content, just hang up. Fixes BR#1776724.
+ * - Fix warnings about unused code on mingw32.
+ * - In case of flushes, calculate the byte count
+ * less incorrectly (I think).
+ *
+ * Revision 1.144 2007/08/11 14:43:22 fabiankeil
+ * Add some more prototypes for static functions.
+ *
+ * Revision 1.143 2007/08/05 13:58:19 fabiankeil
+ * Comment out request_contains_null_bytes() until it's used again.
+ *
* Revision 1.142 2007/08/05 13:50:26 fabiankeil
* #1763173 from Stefan Huehner: s@const static@static const@
* and declare some more functions static.
int g_terminate = 0;
#endif
-static void listen_loop(void);
+#if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA)
+static void sig_handler(int the_signal);
+#endif
+static int client_protocol_is_unsupported(const struct client_state *csp, char *req);
+static jb_err get_request_destination_elsewhere(struct client_state *csp, struct list *headers);
+static jb_err get_server_headers(struct client_state *csp);
+static const char *crunch_reason(const struct http_response *rsp);
+static void send_crunch_response(struct client_state *csp, struct http_response *rsp);
+/*
+ * static int request_contains_null_bytes(const struct client_state *csp, char *buf, int len);
+ */
+static void build_request_line(struct client_state *csp, const struct forward_spec *fwd, char **request_line);
+static jb_err change_request_destination(struct client_state *csp);
static void chat(struct client_state *csp);
+static void serve(struct client_state *csp);
+#if defined(unix)
+static void usage(const char *myname);
+#endif
+static void initialize_mutexes(void);
+static jb_socket bind_port_helper(struct configuration_spec *config);
+static void listen_loop(void);
+
#ifdef AMIGA
void serve(struct client_state *csp);
#else /* ifndef AMIGA */
int received_hup_signal = 0;
#endif /* defined unix */
-/* The vanilla wafer. */
-static const char VANILLA_WAFER[] =
- "NOTICE=TO_WHOM_IT_MAY_CONCERN_"
- "Do_not_send_me_any_copyrighted_information_other_than_the_"
- "document_that_I_am_requesting_or_any_of_its_necessary_components._"
- "In_particular_do_not_send_me_any_cookies_that_"
- "are_subject_to_a_claim_of_copyright_by_anybody._"
- "Take_notice_that_I_refuse_to_be_bound_by_any_license_condition_"
- "(copyright_or_otherwise)_applying_to_any_cookie._";
-
/* HTTP snipplets. */
static const char CSUCCEED[] =
"HTTP/1.0 200 Connection established\n"
"Empty server or forwarder response.\r\n"
"The connection was closed without sending any data.\r\n";
+#if 0
/* XXX: should be a template */
static const char NULL_BYTE_RESPONSE[] =
"HTTP/1.0 400 Bad request received from browser\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n"
"Bad request. Null byte(s) before end of request.\r\n";
+#endif
/* XXX: should be a template */
static const char MESSED_UP_REQUEST_RESPONSE[] =
/* A function to crunch a response */
typedef struct http_response *(*crunch_func_ptr)(struct client_state *);
-typedef char *(*filter_function_ptr)();
-
/* Crunch function flags */
#define CF_NO_FLAGS 0
/* Cruncher applies to forced requests as well */
const int flags;
};
+static int crunch_response_triggered(struct client_state *csp, const struct cruncher crunchers[]);
+
/* Complete list of cruncher functions */
static const struct cruncher crunchers_all[] = {
{ direct_response, CF_COUNT_AS_REJECT|CF_IGNORE_FORCE},
}
-/*********************************************************************
- *
- * Function : get_filter_function
- *
- * Description : Decides which content filter function has
- * to be applied (if any).
- *
- * XXX: Doesn't handle filter_popups()
- * because of the different prototype. Probably
- * we should ditch filter_popups() anyway, it's
- * even less reliable than popup blocking based
- * on pcrs filters.
- *
- * Parameters :
- * 1 : csp = Current client state (buffers, headers, etc...)
- *
- * Returns : The content filter function to run, or
- * NULL if no content filter is active
- *
- *********************************************************************/
-static filter_function_ptr get_filter_function(struct client_state *csp)
-{
- filter_function_ptr filter_function = NULL;
-
- /*
- * Are we enabling text mode by force?
- */
- if (csp->action->flags & ACTION_FORCE_TEXT_MODE)
- {
- /*
- * Do we really have to?
- */
- if (csp->content_type & CT_TEXT)
- {
- log_error(LOG_LEVEL_HEADER, "Text mode is already enabled.");
- }
- else
- {
- csp->content_type |= CT_TEXT;
- log_error(LOG_LEVEL_HEADER, "Text mode enabled by force. Take cover!");
- }
- }
-
- if (!(csp->content_type & CT_DECLARED))
- {
- /*
- * The server didn't bother to declare a MIME-Type.
- * Assume it's text that can be filtered.
- *
- * This also regulary happens with 304 responses,
- * therefore logging anything here would cause
- * too much noise.
- */
- csp->content_type |= CT_TEXT;
- }
-
-
- /*
- * Choose the applying filter function based on
- * the content type and action settings.
- */
- if ((csp->content_type & CT_TEXT) &&
- (csp->rlist != NULL) &&
- (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
- {
- filter_function = pcrs_filter_response;
- }
- else if ((csp->content_type & CT_GIF) &&
- (csp->action->flags & ACTION_DEANIMATE))
- {
- filter_function = gif_deanimate_response;
- }
- else if ((csp->content_type & CT_JPEG) &&
- (csp->action->flags & ACTION_JPEG_INSPECT))
- {
- filter_function = jpeg_inspect_response;
- }
-
- return filter_function;
-}
-
/*********************************************************************
*
* Function : chat
char buf[BUFFER_SIZE];
char *hdr;
char *p;
- char *req;
+ char *req = NULL;
fd_set rfds;
int n;
jb_socket maxfd;
} while ((NULL != req) && ('\0' == *req));
- if (NULL != req)
+ if ((NULL != req) && ('\0' != *req))
{
/* Request received. Validate and parse it. */
list_append_list_unique(csp->headers, headers);
destroy_list(headers);
- /*
- * If the user has not supplied any wafers, and the user has not
- * told us to suppress the vanilla wafer, then send the vanilla wafer.
- */
- if (list_is_empty(csp->action->multi[ACTION_MULTI_WAFER])
- && ((csp->action->flags & ACTION_VANILLA_WAFER) != 0))
- {
- enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER);
- }
-
err = sed(client_patterns, add_client_headers, csp);
if (JB_ERR_OK != err)
{
}
csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
- if (strcmp(http->cmd, csp->headers->first->str))
+ /* Check request line for rewrites. */
+ if ((NULL == csp->headers->first->str)
+ || (strcmp(http->cmd, csp->headers->first->str) &&
+ (JB_ERR_OK != change_request_destination(csp))))
{
/*
- * A header filter rewrote the request line,
- * modify the http request accordingly.
+ * A header filter broke the request line - bail out.
*/
- if (JB_ERR_OK != change_request_destination(csp))
- {
- write_socket(csp->cfd, MESSED_UP_REQUEST_RESPONSE, strlen(MESSED_UP_REQUEST_RESPONSE));
- /* XXX: Use correct size */
- log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request generated\" 500 0", csp->ip_addr_str);
- log_error(LOG_LEVEL_ERROR, "Invalid request line after applying header filters.");
+ write_socket(csp->cfd, MESSED_UP_REQUEST_RESPONSE, strlen(MESSED_UP_REQUEST_RESPONSE));
+ /* XXX: Use correct size */
+ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request generated\" 500 0", csp->ip_addr_str);
+ log_error(LOG_LEVEL_ERROR, "Invalid request line after applying header filters.");
- free_http_request(http);
- return;
- }
+ free_http_request(http);
+ return;
}
/* decide how to route the HTTP request */
/* Never get here - LOG_LEVEL_FATAL causes program exit */
}
- /* build the http request to send to the server
+ /*
+ * build the http request to send to the server
* we have to do one of the following:
*
* create = use the original HTTP request to create a new
"CONNECT already confirmed. Unable to tell the client about the problem.");
return;
}
+ else if (byte_count)
+ {
+ /*
+ * Just hang up. We already transmitted the original headers
+ * and parts of the original content and therefore missed the
+ * chance to send an error message (without risking data corruption).
+ *
+ * XXX: we could retry with a fancy range request here.
+ */
+ log_error(LOG_LEVEL_ERROR, "Already forwarded the original headers. "
+ "Unable to tell the client about the problem.");
+ return;
+ }
rsp = error_response(csp, "connect-failed", errno);
- if(rsp)
+ if (rsp)
{
send_crunch_response(csp, rsp);
}
*/
if (content_filter)
{
+ p = execute_content_filter(csp, content_filter);
/*
* If the content filter fails, use the original
* buffer and length.
* (see p != NULL ? p : csp->iob->cur below)
*/
- if (NULL == (p = (*content_filter)(csp)))
+ if (NULL == p)
{
csp->content_length = (size_t)(csp->iob->eod - csp->iob->cur);
}
int flushed;
log_error(LOG_LEVEL_ERROR, "Flushing header and buffers. Stepping back from filtering.");
- if (JB_ERR_OK != sed(server_patterns, add_server_headers, csp))
- {
- log_error(LOG_LEVEL_FATAL, "Failed to parse server headers.");
- }
+
hdr = list_to_text(csp->headers);
if (hdr == NULL)
{
return;
}
-
- if (crunch_response_triggered(csp, crunchers_light))
- {
- /*
- * One of the tags created by a server-header
- * tagger triggered a crunch. We already
- * delivered the crunch response to the client
- * and are done here after cleaning up.
- */
- freez(hdr);
- return;
- }
-
hdrlen = strlen(hdr);
if (write_socket(csp->cfd, hdr, hdrlen)
return;
}
- byte_count += hdrlen + (size_t)flushed + (size_t)len;
+ /*
+ * Reset the byte_count to the amount of bytes
+ * we just flushed. len will be added a few lines below,
+ * hdrlen doesn't matter for LOG_LEVEL_CLF.
+ */
+ byte_count = (size_t)flushed;
freez(hdr);
content_filter = NULL;
server_body = 1;
-
}
}
else
#endif
+#if defined(unix)
/*********************************************************************
*
* Function : usage
#endif /* defined(unix) */
"[--help] "
#if defined(unix)
- "[--no-daemon] [--pidfile pidfile] [--user user[.group]] "
+ "[--no-daemon] [--pidfile pidfile] [--pre-chroot-nslookup hostname] [--user user[.group]] "
#endif /* defined(unix) */
"[--version] [configfile]\n"
"Aborting\n", myname);
exit(2);
}
+#endif /* defined(unix) */
/*********************************************************************
struct group *grp = NULL;
char *p;
int do_chroot = 0;
+ char *pre_chroot_nslookup_to_load_resolver = NULL;
#endif
Argc = argc;
#endif
;
+ init_log_module(Argv[0]);
+
/*
* Parse the command line arguments
*
if (p != NULL) *--p = '\0';
}
+ else if (strcmp(argv[argc_pos], "--pre-chroot-nslookup" ) == 0)
+ {
+ if (++argc_pos == argc) usage(argv[0]);
+ pre_chroot_nslookup_to_load_resolver = strdup(argv[argc_pos]);
+ }
+
else if (strcmp(argv[argc_pos], "--chroot" ) == 0)
{
do_chroot = 1;
close ( fd );
}
#endif /* 1 */
- /* FIXME: should close stderr (fd 2) here too, but the test
- * for existence
- * and load config file is done in listen_loop() and puts
- * some messages on stderr there.
+ /*
+ * stderr (fd 2) will be closed later on, when the
+ * log file has been parsed.
*/
close( 0 );
{
log_error(LOG_LEVEL_FATAL, "Home directory for %s undefined", pw->pw_name);
}
+ /* Read the time zone file from /etc before doing chroot. */
+ tzset();
+ if (NULL != pre_chroot_nslookup_to_load_resolver
+ && '\0' != pre_chroot_nslookup_to_load_resolver[0])
+ {
+ /* Initialize resolver library. */
+ (void) resolve_hostname_to_ip(pre_chroot_nslookup_to_load_resolver);
+ }
if (chroot(pw->pw_dir) < 0)
{
log_error(LOG_LEVEL_FATAL, "Cannot chroot to %s", pw->pw_dir);
*/
if (received_hup_signal)
{
- init_error_log(Argv[0], config->logfile, config->debug);
+ init_error_log(Argv[0], config->logfile);
received_hup_signal = 0;
}
#endif