+ /* Function that does the content filtering for the current request */
+ filter_function_ptr content_filter = NULL;
+
+ /* Skeleton for HTTP response, if we should intercept the request */
+ struct http_response *rsp;
+
+ /* Temporary copy of the client's headers before they get enlisted in csp->headers */
+ struct list header_list;
+ struct list *headers = &header_list;
+
+ http = csp->http;
+
+ memset(buf, 0, sizeof(buf));
+
+ /*
+ * Read the client's request. Note that since we're not using select() we
+ * could get blocked here if a client connected, then didn't say anything!
+ */
+
+ do
+ {
+ len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+
+ if (len <= 0) break; /* error! */
+
+ /*
+ * If there is no memory left for buffering the
+ * request, there is nothing we can do but hang up
+ */
+ if (add_to_iob(csp, buf, len))
+ {
+ return;
+ }
+
+ req = get_header(csp);
+
+ } while ((NULL != req) && ('\0' == *req));
+
+ if (NULL != req)
+ {
+ /* Request received. Validate and parse it. */
+
+#if 0
+ /*
+ * XXX: Temporary disabled to prevent problems
+ * with POST requests whose bodies are allowed to
+ * contain NULL bytes. BR#1730105.
+ *
+ * The main purpose of this check is to properly
+ * log stuff like BitTorrent traffic and other junk
+ * that hits public proxies. It's not required for
+ * Privoxy to functions as those requests are discarded
+ * later on anyway.
+ *
+ * It probably should be rewritten to only check
+ * the head of the request. Another option would
+ * be to let all POST requests pass, although that
+ * may not be good enough.
+ */
+ if (request_contains_null_bytes(csp, buf, len))
+ {
+ /* NULL bytes found and dealt with, just hang up. */
+ return;
+ }
+#endif
+
+ /* Does the request line look invalid? */
+ if (client_protocol_is_unsupported(csp, req))
+ {
+ /*
+ * Yes. The request has already been
+ * answered with a error response, the buffers
+ * were freed and we're done with chatting.
+ */
+ return;
+ }
+
+#ifdef FEATURE_FORCE_LOAD
+ /*
+ * If this request contains the FORCE_PREFIX and blocks
+ * aren't enforced, get rid of it and set the force flag.
+ */
+ if (strstr(req, FORCE_PREFIX))
+ {
+ if (csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
+ {
+ log_error(LOG_LEVEL_FORCE,
+ "Ignored force prefix in request: \"%s\".", req);
+ }
+ else
+ {
+ strclean(req, FORCE_PREFIX);
+ log_error(LOG_LEVEL_FORCE, "Enforcing request: \"%s\".", req);
+ csp->flags |= CSP_FLAG_FORCED;
+ }
+ }
+
+#endif /* def FEATURE_FORCE_LOAD */
+ err = parse_http_request(req, http, csp);
+ if (JB_ERR_OK != err)
+ {
+ log_error(LOG_LEVEL_ERROR, "Couldn't parse request: %s.", jb_err_to_string(err));
+ }
+
+ freez(req);
+ }
+
+ if (http->cmd == NULL)
+ {
+ write_socket(csp->cfd, CHEADER, strlen(CHEADER));
+ /* XXX: Use correct size */
+ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str);
+ log_error(LOG_LEVEL_ERROR, "Invalid header received from %s.", csp->ip_addr_str);
+
+ free_http_request(http);
+ return;
+ }
+
+ /* grab the rest of the client's headers */
+ init_list(headers);
+ for (;;)
+ {
+ if ( ( ( p = get_header(csp) ) != NULL) && ( *p == '\0' ) )
+ {
+ len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+ if (len <= 0)
+ {
+ log_error(LOG_LEVEL_ERROR, "read from client failed: %E");
+ return;
+ }
+
+ /*
+ * If there is no memory left for buffering the
+ * request, there is nothing we can do but hang up
+ */
+ if (add_to_iob(csp, buf, len))
+ {
+ return;
+ }
+ continue;
+ }
+
+ if (p == NULL) break;
+
+ enlist(headers, p);
+ freez(p);
+
+ }
+
+ if (http->host == NULL)
+ {
+ /*
+ * If we still don't know the request destination,
+ * the request is invalid or the client uses
+ * Privoxy without its knowledge.
+ */
+ if (JB_ERR_OK != get_request_destination_elsewhere(csp, headers))
+ {
+ /*
+ * Our attempts to get the request destination
+ * elsewhere failed or Privoxy is configured
+ * to only accept proxy requests.
+ *
+ * An error response has already been send
+ * and we're done here.
+ */
+ return;
+ }
+ }
+
+ /*
+ * Determine the actions for this URL
+ */
+#ifdef FEATURE_TOGGLE
+ if (!(csp->flags & CSP_FLAG_TOGGLED_ON))
+ {
+ /* Most compatible set of actions (i.e. none) */
+ init_current_action(csp->action);
+ }
+ else
+#endif /* ndef FEATURE_TOGGLE */
+ {
+ url_actions(http, csp);
+ }
+
+ /*
+ * Save a copy of the original request for logging
+ */
+ http->ocmd = strdup(http->cmd);
+
+ if (http->ocmd == NULL)
+ {
+ log_error(LOG_LEVEL_FATAL, "Out of memory copying HTTP request line");
+ }
+
+ enlist(csp->headers, http->cmd);
+
+ /* Append the previously read headers */
+ 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)
+ {
+ assert(err == JB_ERR_PARSE);
+ log_error(LOG_LEVEL_FATAL, "Failed to parse client headers");
+ }
+ csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
+
+ if (strcmp(http->cmd, csp->headers->first->str))
+ {
+ /*
+ * A header filter rewrote the request line,
+ * modify the http request accordingly.
+ */
+ 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.");
+
+ free_http_request(http);
+ return;
+ }
+ }
+
+ /* decide how to route the HTTP request */
+ if (NULL == (fwd = forward_url(http, csp)))
+ {
+ log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!? This can't happen!");
+ /* Never get here - LOG_LEVEL_FATAL causes program exit */
+ }
+
+ /* 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
+ * HTTP request that has either the path component
+ * without the http://domainspec (w/path) or the
+ * full orininal URL (w/url)
+ * Note that the path and/or the HTTP version may
+ * have been altered by now.
+ *
+ * connect = Open a socket to the host:port of the server
+ * and short-circuit server and client socket.
+ *
+ * pass = Pass the request unchanged if forwarding a CONNECT
+ * request to a parent proxy. Note that we'll be sending
+ * the CFAIL message ourselves if connecting to the parent
+ * fails, but we won't send a CSUCCEED message if it works,
+ * since that would result in a double message (ours and the
+ * parent's). After sending the request to the parent, we simply
+ * tunnel.
+ *
+ * here's the matrix:
+ * SSL
+ * 0 1
+ * +--------+--------+
+ * | | |
+ * 0 | create | connect|
+ * | w/path | |
+ * Forwarding +--------+--------+
+ * | | |
+ * 1 | create | pass |
+ * | w/url | |
+ * +--------+--------+
+ *
+ */
+
+ /*
+ * Check if a CONNECT request is allowable:
+ * In the absence of a +limit-connect action, allow only port 443.
+ * If there is an action, allow whatever matches the specificaton.
+ */
+ if(http->ssl)
+ {
+ if( ( !(csp->action->flags & ACTION_LIMIT_CONNECT) && csp->http->port != 443)
+ || (csp->action->flags & ACTION_LIMIT_CONNECT
+ && !match_portlist(csp->action->string[ACTION_STRING_LIMIT_CONNECT], csp->http->port)) )
+ {
+ if (csp->action->flags & ACTION_TREAT_FORBIDDEN_CONNECTS_LIKE_BLOCKS)
+ {
+ /*
+ * The response may confuse some clients,
+ * but makes unblocking easier.
+ */
+ log_error(LOG_LEVEL_ERROR, "Marking suspicious CONNECT request from %s for blocking.",
+ csp->ip_addr_str);
+ csp->action->flags |= ACTION_BLOCK;
+ http->ssl = 0;
+ }
+ else
+ {
+ write_socket(csp->cfd, CFORBIDDEN, strlen(CFORBIDDEN));
+ log_error(LOG_LEVEL_CONNECT, "Denying suspicious CONNECT request from %s", csp->ip_addr_str);
+ log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 403 0", csp->ip_addr_str);
+
+ list_remove_all(csp->headers);
+
+ return;
+ }
+ }
+ }
+
+ if (http->ssl == 0)
+ {
+ freez(csp->headers->first->str);
+ build_request_line(csp, fwd, &csp->headers->first->str);
+ }
+
+ hdr = list_to_text(csp->headers);
+ if (hdr == NULL)
+ {
+ /* FIXME Should handle error properly */
+ log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header");
+ }
+
+ /*
+ * We have a request. Check if one of the crunchers wants it.
+ */
+ if (crunch_response_triggered(csp, crunchers_all))
+ {
+ /*
+ * Yes. The client got the crunch response
+ * and we are done here after cleaning up.
+ */
+ freez(hdr);
+ list_remove_all(csp->headers);
+
+ return;
+ }
+
+ /*
+ * The headers can't be removed earlier because
+ * they were still needed for the referrer check
+ * in case of CGI crunches.
+ *
+ * XXX: Would it be worth to move the referrer check
+ * into client_referrer() and set a flag if it's trusted?
+ */
+ list_remove_all(csp->headers);
+
+ log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path);
+
+ if (fwd->forward_host)
+ {
+ log_error(LOG_LEVEL_CONNECT, "via %s:%d to: %s",
+ fwd->forward_host, fwd->forward_port, http->hostport);
+ }
+ else
+ {
+ log_error(LOG_LEVEL_CONNECT, "to %s", http->hostport);
+ }
+
+ /* here we connect to the server, gateway, or the forwarder */
+
+ while ( (csp->sfd = forwarded_connect(fwd, http, csp))
+ && (errno == EINVAL) && (forwarded_connect_retries++ < max_forwarded_connect_retries))
+ {
+ log_error(LOG_LEVEL_ERROR, "failed request #%u to connect to %s. Trying again.",
+ forwarded_connect_retries, http->hostport);
+ }
+
+ if (csp->sfd == JB_INVALID_SOCKET)
+ {
+ if (fwd->type != SOCKS_NONE)
+ {
+ /* Socks error. */
+ rsp = error_response(csp, "forwarding-failed", errno);
+ }
+ else if (errno == EINVAL)
+ {
+ rsp = error_response(csp, "no-such-domain", errno);
+ }
+ else
+ {
+ rsp = error_response(csp, "connect-failed", errno);
+ log_error(LOG_LEVEL_CONNECT, "connect to: %s failed: %E",
+ http->hostport);
+ }
+
+
+ /* Write the answer to the client */
+ if (rsp != NULL)
+ {
+ send_crunch_response(csp, rsp);
+ }
+
+ freez(hdr);
+ return;
+ }
+
+ if (fwd->forward_host || (http->ssl == 0))
+ {
+ /* write the client's (modified) header to the server
+ * (along with anything else that may be in the buffer)
+ */
+
+ if (write_socket(csp->sfd, hdr, strlen(hdr))
+ || (flush_socket(csp->sfd, csp) < 0))
+ {
+ log_error(LOG_LEVEL_CONNECT, "write header to: %s failed: %E",
+ http->hostport);
+
+ rsp = error_response(csp, "connect-failed", errno);
+
+ if(rsp)
+ {
+ send_crunch_response(csp, rsp);
+ }
+
+ freez(hdr);
+ return;
+ }
+ }
+ else
+ {
+ /*
+ * We're running an SSL tunnel and we're not forwarding,
+ * so just send the "connect succeeded" message to the
+ * client, flush the rest, and get out of the way.
+ */
+ if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED)))
+ {
+ freez(hdr);
+ return;
+ }
+ IOB_RESET(csp);
+ }
+
+ log_error(LOG_LEVEL_CONNECT, "to %s successful", http->hostport);
+
+ /* we're finished with the client's header */
+ freez(hdr);
+
+ maxfd = ( csp->cfd > csp->sfd ) ? csp->cfd : csp->sfd;
+
+ /* pass data between the client and server
+ * until one or the other shuts down the connection.
+ */
+
+ server_body = 0;
+
+ for (;;)
+ {
+#ifdef __OS2__
+ /*
+ * FD_ZERO here seems to point to an errant macro which crashes.
+ * So do this by hand for now...
+ */
+ memset(&rfds,0x00,sizeof(fd_set));
+#else
+ FD_ZERO(&rfds);
+#endif
+ FD_SET(csp->cfd, &rfds);
+ FD_SET(csp->sfd, &rfds);
+
+ n = select((int)maxfd+1, &rfds, NULL, NULL, NULL);
+
+ if (n < 0)
+ {
+ log_error(LOG_LEVEL_ERROR, "select() failed!: %E");
+ return;
+ }
+
+ /* this is the body of the browser's request
+ * just read it and write it.
+ */
+
+ if (FD_ISSET(csp->cfd, &rfds))
+ {
+ len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+
+ if (len <= 0)
+ {
+ break; /* "game over, man" */
+ }
+
+ if (write_socket(csp->sfd, buf, (size_t)len))
+ {
+ log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+ return;
+ }
+ continue;
+ }
+
+ /*
+ * The server wants to talk. It could be the header or the body.
+ * If `hdr' is null, then it's the header otherwise it's the body.
+ * FIXME: Does `hdr' really mean `host'? No.
+ */
+
+
+ if (FD_ISSET(csp->sfd, &rfds))
+ {
+ fflush( 0 );
+ len = read_socket(csp->sfd, buf, sizeof(buf) - 1);
+
+ if (len < 0)
+ {
+ log_error(LOG_LEVEL_ERROR, "read from: %s failed: %E", http->host);
+
+ if (http->ssl && (fwd->forward_host == NULL))
+ {
+ /*
+ * Just hang up. We already confirmed the client's CONNECT
+ * request with status code 200 and unencrypted content is
+ * no longer welcome.
+ */
+ log_error(LOG_LEVEL_ERROR,
+ "CONNECT already confirmed. Unable to tell the client about the problem.");
+ return;
+ }
+
+ rsp = error_response(csp, "connect-failed", errno);
+
+ if(rsp)
+ {
+ send_crunch_response(csp, rsp);
+ }
+
+ return;
+ }
+
+ /* Add a trailing zero. This lets filter_popups
+ * use string operations.
+ */
+ buf[len] = '\0';
+
+#ifdef FEATURE_KILL_POPUPS
+ /* Filter the popups on this read. */
+ if (block_popups_now)
+ {
+ filter_popups(buf, csp);
+ }
+#endif /* def FEATURE_KILL_POPUPS */
+
+ /* Normally, this would indicate that we've read
+ * as much as the server has sent us and we can
+ * close the client connection. However, Microsoft
+ * in its wisdom has released IIS/5 with a bug that
+ * prevents it from sending the trailing \r\n in
+ * a 302 redirect header (and possibly other headers).
+ * To work around this if we've haven't parsed
+ * a full header we'll append a trailing \r\n
+ * and see if this now generates a valid one.
+ *
+ * This hack shouldn't have any impacts. If we've
+ * already transmitted the header or if this is a
+ * SSL connection, then we won't bother with this
+ * hack. So we only work on partially received
+ * headers. If we append a \r\n and this still
+ * doesn't generate a valid header, then we won't
+ * transmit anything to the client.
+ */
+ if (len == 0)
+ {
+
+ if (server_body || http->ssl)
+ {
+ /*
+ * If we have been buffering up the document,
+ * now is the time to apply content modification
+ * and send the result to the client.
+ */
+ if (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)))
+ {
+ csp->content_length = (size_t)(csp->iob->eod - csp->iob->cur);
+ }
+
+ if (JB_ERR_OK != sed(server_patterns_light, NULL, csp))
+ {
+ log_error(LOG_LEVEL_FATAL, "Failed to parse server headers.");
+ }
+
+ hdr = list_to_text(csp->headers);
+ if (hdr == NULL)
+ {
+ /* FIXME Should handle error properly */
+ log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
+ }
+
+ /*
+ * Shouldn't happen because this was the second sed run
+ * and tags are only created for the first one.
+ */
+ assert(!crunch_response_triggered(csp, crunchers_all));
+
+ if (write_socket(csp->cfd, hdr, strlen(hdr))
+ || write_socket(csp->cfd, p != NULL ? p : csp->iob->cur, csp->content_length))
+ {
+ log_error(LOG_LEVEL_ERROR, "write modified content to client failed: %E");
+ freez(hdr);
+ freez(p);
+ return;
+ }
+
+ freez(hdr);
+ freez(p);
+ }
+
+ break; /* "game over, man" */
+ }
+
+ /*
+ * This is NOT the body, so
+ * Let's pretend the server just sent us a blank line.
+ */
+ snprintf(buf, sizeof(buf), "\r\n");
+ len = (int)strlen(buf);
+
+ /*
+ * Now, let the normal header parsing algorithm below do its
+ * job. If it fails, we'll exit instead of continuing.
+ */
+
+ ms_iis5_hack = 1;
+ }
+
+ /*
+ * If this is an SSL connection or we're in the body
+ * of the server document, just write it to the client,
+ * unless we need to buffer the body for later content-filtering
+ */
+
+ if (server_body || http->ssl)
+ {
+ if (content_filter)
+ {
+ /*
+ * If there is no memory left for buffering the content, or the buffer limit
+ * has been reached, switch to non-filtering mode, i.e. make & write the
+ * header, flush the iob and buf, and get out of the way.
+ */
+ if (add_to_iob(csp, buf, len))
+ {
+ size_t hdrlen;
+ 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)
+ {
+ /*
+ * Memory is too tight to even generate the header.
+ * Send our static "Out-of-memory" page.
+ */
+ log_error(LOG_LEVEL_ERROR, "Out of memory while trying to flush.");
+ rsp = cgi_error_memory();
+ send_crunch_response(csp, rsp);
+
+ 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)
+ || ((flushed = flush_socket(csp->cfd, csp)) < 0)
+ || (write_socket(csp->cfd, buf, (size_t)len)))
+ {
+ log_error(LOG_LEVEL_CONNECT, "Flush header and buffers to client failed: %E");
+
+ freez(hdr);
+ return;
+ }
+
+ byte_count += hdrlen + (size_t)flushed + (size_t)len;
+ freez(hdr);
+ content_filter = NULL;
+ server_body = 1;
+
+ }
+ }
+ else
+ {
+ if (write_socket(csp->cfd, buf, (size_t)len))
+ {
+ log_error(LOG_LEVEL_ERROR, "write to client failed: %E");
+ return;
+ }
+ }
+ byte_count += (size_t)len;
+ continue;
+ }
+ else
+ {
+ /* we're still looking for the end of the
+ * server's header ... (does that make header
+ * parsing an "out of body experience" ?
+ */
+
+ /*
+ * buffer up the data we just read. If that fails,
+ * there's little we can do but send our static
+ * out-of-memory page.
+ */
+ if (add_to_iob(csp, buf, len))
+ {
+ log_error(LOG_LEVEL_ERROR, "Out of memory while looking for end of server headers.");
+ rsp = cgi_error_memory();
+ send_crunch_response(csp, rsp);
+
+ return;
+ }
+
+ /* Convert iob into something sed() can digest */
+ if (JB_ERR_PARSE == get_server_headers(csp))
+ {
+ if (ms_iis5_hack)
+ {
+ /* Well, we tried our MS IIS/5
+ * hack and it didn't work.
+ * The header is incomplete
+ * and there isn't anything
+ * we can do about it.
+ */
+ break;
+ }
+ else
+ {
+ /* Since we have to wait for
+ * more from the server before
+ * we can parse the headers
+ * we just continue here.
+ */
+ continue;
+ }
+ }
+
+ /* Did we actually get anything? */
+ if (NULL == csp->headers->first)
+ {
+ log_error(LOG_LEVEL_ERROR, "Empty server or forwarder response.");
+ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd);
+ write_socket(csp->cfd, NO_SERVER_DATA_RESPONSE, strlen(NO_SERVER_DATA_RESPONSE));
+ free_http_request(http);
+ return;
+ }
+
+ /* we have now received the entire header.
+ * filter it and send the result to the client
+ */
+ 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)
+ {
+ /* FIXME Should handle error properly */
+ log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
+ }
+
+ 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;
+ }
+ /* Buffer and pcrs filter this if appropriate. */
+
+ if (!http->ssl) /* We talk plaintext */
+ {
+
+#ifdef FEATURE_KILL_POPUPS
+ /* Start blocking popups if appropriate. */
+ if ((csp->content_type & CT_TEXT) && /* It's a text / * MIME-Type */
+ (csp->action->flags & ACTION_NO_POPUPS) != 0) /* Policy allows */
+ {
+ block_popups_now = 1;
+ /*
+ * Filter the part of the body that came in the same read
+ * as the last headers:
+ */
+ filter_popups(csp->iob->cur, csp);
+ }
+#endif /* def FEATURE_KILL_POPUPS */
+ content_filter = get_filter_function(csp);
+ }
+ /*
+ * Only write if we're not buffering for content modification
+ */
+ if (!content_filter)
+ {
+ /* write the server's (modified) header to
+ * the client (along with anything else that
+ * may be in the buffer)
+ */
+
+ if (write_socket(csp->cfd, hdr, strlen(hdr))
+ || ((len = flush_socket(csp->cfd, csp)) < 0))
+ {
+ log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E");
+
+ /* the write failed, so don't bother
+ * mentioning it to the client...
+ * it probably can't hear us anyway.
+ */
+ freez(hdr);
+ return;
+ }
+
+ byte_count += (size_t)len;
+ }
+
+ /* we're finished with the server's header */
+
+ freez(hdr);
+ server_body = 1;
+
+ /* If this was a MS IIS/5 hack then it means
+ * the server has already closed the
+ * connection. Nothing more to read. Time
+ * to bail.
+ */
+ if (ms_iis5_hack)
+ {
+ break;
+ }
+ }
+ continue;
+ }
+
+ return; /* huh? we should never get here */
+ }
+
+ if (csp->content_length == 0)
+ {
+ /*
+ * If Privoxy didn't recalculate the
+ * Content-Lenght, byte_count is still
+ * correct.
+ */
+ csp->content_length = byte_count;
+ }
+
+ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %d",
+ csp->ip_addr_str, http->ocmd, csp->content_length);
+}
+
+
+/*********************************************************************
+ *
+ * Function : serve
+ *
+ * Description : This is little more than chat. We only "serve" to
+ * to close any socket that chat may have opened.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+#ifdef AMIGA
+void serve(struct client_state *csp)
+#else /* ifndef AMIGA */
+static void serve(struct client_state *csp)
+#endif /* def AMIGA */
+{
+ chat(csp);
+ close_socket(csp->cfd);
+
+ if (csp->sfd != JB_INVALID_SOCKET)
+ {
+ close_socket(csp->sfd);
+ }
+
+ csp->flags &= ~CSP_FLAG_ACTIVE;
+
+}
+
+
+#ifdef __BEOS__
+/*********************************************************************
+ *
+ * Function : server_thread
+ *
+ * Description : We only exist to call `serve' in a threaded environment.
+ *
+ * Parameters :
+ * 1 : data = Current client state (buffers, headers, etc...)
+ *
+ * Returns : Always 0.
+ *
+ *********************************************************************/
+static int32 server_thread(void *data)
+{
+ serve((struct client_state *) data);
+ return 0;
+
+}
+#endif
+
+
+/*********************************************************************
+ *
+ * Function : usage
+ *
+ * Description : Print usage info & exit.
+ *
+ * Parameters : Pointer to argv[0] for identifying ourselves
+ *
+ * Returns : No. ,-)
+ *
+ *********************************************************************/
+void usage(const char *myname)
+{
+ printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n"
+ "Usage: %s "
+#if defined(unix)
+ "[--chroot] "
+#endif /* defined(unix) */
+ "[--help] "
+#if defined(unix)
+ "[--no-daemon] [--pidfile pidfile] [--user user[.group]] "
+#endif /* defined(unix) */
+ "[--version] [configfile]\n"
+ "Aborting\n", myname);
+
+ exit(2);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : initialize_mutexes
+ *
+ * Description : Prepares mutexes if mutex support is available.
+ *
+ * Parameters : None
+ *
+ * Returns : Void, exits in case of errors.
+ *
+ *********************************************************************/
+void initialize_mutexes()
+{
+ int err = 0;
+
+#ifdef FEATURE_PTHREAD
+ /*
+ * Prepare global mutex semaphores
+ */
+ err = pthread_mutex_init(&log_mutex, 0);
+
+ if (!err) err = pthread_mutex_init(&log_init_mutex, 0);
+
+ /*
+ * XXX: The assumptions below are a bit naive
+ * and can cause locks that aren't necessary.
+ *
+ * For example older FreeBSD versions (< 6.x?)
+ * have no gethostbyname_r, but gethostbyname is
+ * thread safe.
+ */
+#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R)
+ if (!err) err = pthread_mutex_init(&resolver_mutex, 0);
+#endif /* !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) */
+ /*
+ * XXX: should we use a single mutex for
+ * localtime() and gmtime() as well?
+ */
+#ifndef HAVE_GMTIME_R
+ if (!err) err = pthread_mutex_init(&gmtime_mutex, 0);
+#endif /* ndef HAVE_GMTIME_R */
+
+#ifndef HAVE_LOCALTIME_R
+ if (!err) err = pthread_mutex_init(&localtime_mutex, 0);
+#endif /* ndef HAVE_GMTIME_R */
+
+#ifndef HAVE_RANDOM
+ if (!err) err = pthread_mutex_init(&rand_mutex, 0);
+#endif /* ndef HAVE_RANDOM */
+#endif /* FEATURE_PTHREAD */