2 MiddleMan filtering proxy server
3 Copyright (C) 2002 Jason McLaughlin
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include <sys/types.h>
27 #include "../libntlm/ntlm.h"
30 extern TEMPLATES *templates;
31 extern REWRITE_LIST *rewrite_list;
32 extern EXTERNAL *external;
33 extern MIME_LIST *mime_list;
34 extern REDIRECT_LIST *redirect_list;
35 extern KEYWORD_LIST *keyword_list;
36 extern THREADLIST threads[];
38 int protocol_start(CONNECTION * connection)
42 switch (connection->proxy_type) {
44 proxyfd = net_connect(connection->proxy_host, connection->proxy_port);
46 putlog(MMLOG_ERROR, "connection to proxy server failed");
48 connection->server = sock_new(proxyfd);
52 proxyfd = net_connect(connection->proxy_host, connection->proxy_port);
55 connection->server = sock_new(proxyfd);
57 x = net_socks4(connection, connection->header->host, connection->header->port);
59 putlog(MMLOG_ERROR, "connection to socks4 server failed");
61 template_send(templates, error_to_template(x), connection, 404);
69 proxyfd = net_connect(connection->header->host, connection->header->port);
71 connection->server = sock_new(proxyfd);
75 template_send(templates, error_to_template(proxyfd), connection, 404);
77 connection->server = NULL;
85 int protocol_reconnect(CONNECTION * connection)
89 sock_close(connection->server);
90 sock_flush(connection->client);
92 ret = protocol_start(connection);
98 This function handles HTTP requests
100 int protocol_http(CONNECTION * connection)
102 int auth = FALSE, ret;
103 char *headbuf, buf[8096], *ptr;
104 FILEBUF *filebuf, *ext_filebuf, *postdata = NULL;
105 struct EXTERNAL_LIST_LIST *external_list = NULL;
106 struct MIME_LIST_LIST *mime_list_list;
107 struct url_command_t **url_command;
109 pthread_mutex_lock(&threads[connection->thread].lock);
110 threads[connection->thread].flags |= THREAD_HTTP;
111 pthread_mutex_unlock(&threads[connection->thread].lock);
113 if (connection->header->content_length != -1) {
114 /* the browser wants to send something to the remote site after the header (POST, PUT, etc.) */
115 putlog(MMLOG_DEBUG, "post length: %d", connection->header->content_length);
117 /* unfortunately, this _needs_ to be buffered.. otherwise we won't have any POST data
118 after authentication */
119 postdata = http_transfer_filebuf(connection, CLIENT);
120 rewrite_do(rewrite_list, connection, postdata, REWRITE_POST, TRUE);
122 connection->header->chunked = FALSE;
123 connection->header->content_length = postdata->size;
126 header_send(connection->header, connection, SERVER, (connection->proxy_type == PROXY_NORMAL) ? HEADER_FORWARD : HEADER_DIRECT);
128 /* I really hate using goto's */
130 if (postdata != NULL)
131 net_filebuf_send(postdata, connection, SERVER);
134 headbuf = header_get(connection, SERVER, TIMEOUT);
135 if (headbuf == NULL) {
136 if (!connection->keepalive_server && auth == FALSE) {
137 template_send(templates, "noconnect", connection, 503);
139 if (postdata != NULL)
140 filebuf_free(postdata);
144 /* keep-alive connection closed, or authorization is being done on
145 a non-keepalive proxy */
146 ret = protocol_reconnect(connection);
148 if (postdata != NULL)
149 filebuf_free(postdata);
154 header_send(connection->header, connection, SERVER, (connection->proxy_type == PROXY_NORMAL) ? HEADER_FORWARD : HEADER_DIRECT);
155 if (postdata != NULL)
156 net_filebuf_send(postdata, connection, SERVER);
158 headbuf = header_get(connection, SERVER, TIMEOUT);
159 if (headbuf == NULL) {
160 if (postdata != NULL)
161 filebuf_free(postdata);
168 /* pass the server's header through the rewrite rules */
169 filebuf = filebuf_new();
170 filebuf->data = headbuf;
171 filebuf->size = strlen(headbuf) + 1;
173 rewrite_do(rewrite_list, connection, filebuf, REWRITE_SERVER, TRUE);
175 headbuf = filebuf->data;
176 filebuf->data = NULL;
178 filebuf_free(filebuf);
180 connection->rheader = http_header_parse_response(headbuf);
181 if (connection->rheader == NULL) {
182 if (postdata != NULL)
183 filebuf_free(postdata);
185 template_send(templates, "badresponse", connection, 400);
191 if (connection->rheader->code == 100) {
192 /* server sent notice to continue POST, go back and retrieve final header
193 (according to the RFC, the server should not send this unless the client sends an Expect:
194 header; however... IIS will sometimes send this anyways)
196 http_header_free(connection->rheader);
201 connection->rheader->host = xstrdup(connection->header->host);
202 connection->rheader->file = xstrdup(connection->header->file);
204 if (connection->keepalive_client) {
205 if (connection->rheader->content_length == -1 && !connection->rheader->chunked) {
206 /* connection can't be kept alive if we can't determine when the remote site's data ends */
207 connection->keepalive_client = FALSE;
208 connection->keepalive_server = FALSE;
209 } else if (connection->rheader->keepalive == TRUE || connection->rheader->proxy_keepalive == TRUE)
210 connection->keepalive_server = TRUE;
211 else if (connection->rheader->version == HTTP_HTTP11)
212 /* keep-alive is assumed for HTTP/1.1 unless any of the above conditions are met */
213 connection->keepalive_server = TRUE;
215 connection->keepalive_server = FALSE;
217 putlog(MMLOG_DEBUG, "keepalive_server = %d", connection->keepalive_server);
219 connection->keepalive_server = FALSE;
221 if (connection->rheader->code == 407 && connection->proxy_type == PROXY_NORMAL && auth == FALSE) {
222 /* authenticate with the other proxy */
223 if (proxy_authenticate(connection)) {
224 http_header_free(connection->rheader);
229 /* authentication finished, retrieve the header again */
232 FREE_AND_NULL(connection->rheader->proxy_authenticate);
234 /* just continue, user will see 407 warning from proxy in browser */
237 if (postdata != NULL)
238 filebuf_free(postdata);
240 /* send template instead of web server's content when a template name matches the code */
241 snprintf(buf, sizeof(buf), "%d", connection->rheader->code);
242 ret = template_send(templates, buf, connection, connection->rheader->code);
246 if (connection->rheader->location != NULL && (connection->rheader->code == 301 || connection->rheader->code == 302)) {
247 /* redirect_do will replace the Location: header if any rules match */
248 redirect_do(redirect_list, connection, REDIRECT_HEADER);
250 /* add URL command to location: header so the feature(s) are still bypassed when
251 browser follows redirect */
252 if (connection->header->url_command != NULL) {
253 ptr = url_command_add(connection, connection->rheader->location);
254 xfree(connection->rheader->location);
255 connection->rheader->location = ptr;
259 /* no message body regardless of what header says for HEAD requests and certain return codes */
260 if (!strcasecmp(connection->header->method, "HEAD") || connection->rheader->code == 304 || connection->rheader->code == 204 || (connection->rheader->code >= 100 && connection->rheader->code < 200)) {
261 header_send(connection->rheader, connection, CLIENT, HEADER_RESP);
266 for (url_command = connection->header->url_command; url_command && *url_command; url_command++) {
267 if (!strcasecmp((*url_command)->command, "mime")) {
268 /* show matching MIME filter entries, if any, for the requested URL */
269 mime_check_show(connection);
271 connection->keepalive_server = FALSE;
277 mime_list_list = mime_check(mime_list, connection);
278 if (mime_list_list != NULL) {
279 putlog(MMLOG_MIME, "blocked mime-type %s from %s%s", (connection->rheader->content_type != NULL) ? connection->rheader->content_type : "(none)", connection->header->host, connection->header->file);
281 template_send(templates, (mime_list_list->template != NULL) ? mime_list_list->template : (mime_list->dtemplate != NULL) ? mime_list->dtemplate : "blocked", connection, 200);
283 /* we have to drop the connection since we're already transferring something */
284 connection->keepalive_server = FALSE;
286 pthread_rwlock_unlock(&mime_list->lock);
290 pthread_rwlock_unlock(&mime_list->lock);
292 putlog(MMLOG_REQUEST, "%s %s:%d%s", connection->header->method, connection->header->host, connection->header->port, connection->header->file);
294 if (BUFFERMAX == -1 || connection->rheader->content_length <= BUFFERMAX) {
295 connection->buffer = rewrite_do(rewrite_list, connection, NULL, REWRITE_BODY, FALSE);
297 if (connection->buffer == FALSE) {
298 external_list = external_find(external, connection);
299 pthread_rwlock_unlock(&external->lock);
301 if (external_list != NULL)
302 connection->buffer = TRUE;
305 if (connection->buffer == FALSE)
306 connection->buffer = keyword_check(keyword_list, connection, NULL, FALSE);
308 if (connection->buffer == FALSE) {
309 for (url_command = connection->header->url_command; url_command && *url_command; url_command++) {
310 if (!strcasecmp((*url_command)->command, "score"))
311 connection->buffer = TRUE;
315 connection->buffer = FALSE;
317 if (connection->buffer == FALSE) {
318 header_send(connection->rheader, connection, CLIENT, HEADER_RESP);
320 http_transfer(connection, SERVER);
322 /* this file is to be buffered and processed before sending it to the browser */
323 filebuf = http_transfer_filebuf(connection, SERVER);
325 /* decompress gzip-encoded content */
326 if (connection->rheader->content_encoding != NULL) {
327 if (!strcasecmp(connection->rheader->content_encoding, "gzip")) {
328 ret = filebuf_ungzip(filebuf);
330 putlog(MMLOG_ERROR, "failed to decompress gzip encoded content");
332 /* failed to decompress, just send as-is */
336 putlog(MMLOG_WARN, "unhandled content encoding: %s", connection->rheader->content_encoding);
342 /* check keyword score */
343 ret = keyword_check(keyword_list, connection, filebuf, TRUE);
345 for (url_command = connection->header->url_command; url_command && *url_command; url_command++) {
346 if (!strcasecmp((*url_command)->command, "score")) {
347 score_show(connection, ret);
349 filebuf_free(filebuf);
356 pthread_rwlock_rdlock(&keyword_list->lock);
357 if (ret >= keyword_list->threshold) {
358 putlog(MMLOG_KEYWORDS, "page above threshold with a score of %d", ret);
360 template_send(templates, (keyword_list->template != NULL) ? keyword_list->template : "blocked", connection, 200);
362 pthread_rwlock_unlock(&keyword_list->lock);
364 filebuf_free(filebuf);
369 pthread_rwlock_unlock(&keyword_list->lock);
372 rewrite_do(rewrite_list, connection, filebuf, REWRITE_BODY, TRUE);
374 external_list = external_find(external, connection);
375 if (external_list != NULL && external_list->exec != NULL) {
376 ext_filebuf = external_exec(connection, external_list->exec, filebuf, external_list->type);
377 if (ext_filebuf != NULL) {
378 filebuf_free(filebuf);
379 filebuf = ext_filebuf;
381 if (external_list->newmime != NULL) {
382 FREE_AND_NULL(connection->rheader->content_type);
384 if (!strcasecmp(external_list->newmime, "STDIN")) {
385 /* try extracting Content-type header from external
387 connection->rheader->content_type = external_getmime(filebuf);
389 connection->rheader->content_type = xstrdup(external_list->newmime);
392 putlog(MMLOG_ERROR, "failed to execute external parser '%s'", external_list->exec);
394 pthread_rwlock_unlock(&external->lock);
396 FREE_AND_NULL(connection->rheader->content_encoding);
397 if (connection->header->accept_encoding != NULL) {
398 /* compress the content if the browser supports gzip encoding */
399 char *s, *accept_encoding_lower = xstrdup(connection->header->accept_encoding);
401 /* strcasestr(3) is not available on some systems; use strstr(3) instead */
402 for (s=accept_encoding_lower; *s; s++)
404 ptr = strstr(accept_encoding_lower, "gzip");
406 ret = filebuf_gzip(filebuf);
407 if (ret == TRUE) connection->rheader->content_encoding = xstrdup("gzip");
409 free(accept_encoding_lower);
413 putlog(MMLOG_DEBUG, "filebuf size: %d", filebuf->size);
415 connection->rheader->chunked = FALSE;
416 connection->rheader->content_length = filebuf->size;
418 if (connection->keepalive_client != FALSE && connection->header->keepalive != FALSE && connection->header->proxy_keepalive != FALSE) {
419 /* we may be able to do keep-alive now since the page was buffered and the size is now known */
420 if (connection->header->keepalive == TRUE || connection->header->proxy_keepalive == TRUE)
421 connection->keepalive_client = TRUE;
422 else if (connection->header->version == HTTP_HTTP11)
423 connection->keepalive_client = TRUE;
426 header_send(connection->rheader, connection, CLIENT, HEADER_RESP);
427 net_filebuf_send(filebuf, connection, CLIENT);
429 filebuf_free(filebuf);
433 http_header_free(connection->rheader);
439 This function handles CONNECT requests (HTTPS)
441 int protocol_connect(CONNECTION * connection)
443 pthread_mutex_lock(&threads[connection->thread].lock);
444 threads[connection->thread].flags |= THREAD_CONNECT;
445 pthread_mutex_unlock(&threads[connection->thread].lock);
447 putlog(MMLOG_REQUEST, "CONNECT %s:%d", connection->header->host, connection->header->port);
449 if (connection->proxy_type == PROXY_NORMAL)
450 header_send(connection->header, connection, SERVER, HEADER_FORWARD);
452 putsock(connection->client, "HTTP/1.1 Connection Established\r\n\r\n");
454 net_proxy(connection, -1);
460 get size of next chunk for pages using chunked encoding
462 unsigned int next_chunksize(CONNECTION * connection, int flags)
467 x = sock_getline((flags == SERVER) ? connection->server : connection->client, buf, sizeof(buf));
471 return strtol(buf, NULL, 16);
476 transfer http body from one socket to another
478 void http_transfer(CONNECTION * connection, int direction)
483 header = (direction == SERVER) ? connection->rheader : connection->header;
485 if (header->content_length != -1)
486 net_transfer(connection, direction, header->content_length);
487 else if (header->chunked)
489 x = next_chunksize(connection, direction);
490 putsock((direction == SERVER) ? connection->client : connection->server, "%x\r\n", x);
491 /* rfc says chunks must end in \r\n */
492 net_transfer(connection, direction, x + 2);
495 net_transfer(connection, direction, -1);
500 transfer http body into filebuf
502 FILEBUF *http_transfer_filebuf(CONNECTION * connection, int direction)
509 filebuf = filebuf_new();
511 header = (direction == SERVER) ? connection->rheader : connection->header;
512 sock = (direction == SERVER) ? connection->server : connection->client;
514 if (header->content_length != -1)
515 net_filebuf_read(filebuf, connection, direction, header->content_length);
516 else if (header->chunked)
518 x = next_chunksize(connection, direction);
519 net_filebuf_read(filebuf, connection, direction, x);
520 /* discard the \r\n from the server */
521 sock_getline(sock, NULL, -1);
524 net_filebuf_read(filebuf, connection, direction, -1);
530 read the body and discard it
532 void http_transfer_discard(CONNECTION * connection, int direction)
538 header = (direction == SERVER) ? connection->rheader : connection->header;
539 sock = (direction == SERVER) ? connection->server : connection->client;
541 if (header->content_length != -1)
542 net_filebuf_read(NULL, connection, direction, header->content_length);
543 else if (header->chunked)
545 x = next_chunksize(connection, direction);
546 net_filebuf_read(NULL, connection, direction, x);
547 /* discard the \r\n from the server */
548 sock_getline(sock, NULL, -1);
551 net_filebuf_read(NULL, connection, direction, -1);
555 int proxy_authenticate(CONNECTION * connection)
557 if (connection->rheader->proxy_authenticate == NULL)
560 if (!strcasecmp(connection->rheader->proxy_authenticate, "NTLM")) {
561 /* NTLM authentication */
562 return send_ntlm_response(connection);
563 } else if (!strncasecmp(connection->rheader->proxy_authenticate, "Basic", 5)) {
564 /* Basic authentication */
565 return send_basic_response(connection);
571 /* perform an NTLM handshake, the sequence of events looks like this:
574 2: C <-- S 401 Unauthorized
575 Proxy-Authenticate: NTLM
578 Proxy-Authorization: NTLM <base64-encoded type-1-message>
580 4: C <-- S 401 Unauthorized
581 Proxy-Authenticate: NTLM <base64-encoded type-2-message>
584 Proxy-Authorization: NTLM <base64-encoded type-3-message>
588 int send_ntlm_response(CONNECTION * connection)
591 unsigned char buf[4096], buf2[4096], *headbuf;
594 /* client must do keep-alive during authentication */
595 oldkeep = connection->keepalive_server;
596 connection->keepalive_server = TRUE;
598 /* don't need the body of the 407 message */
599 http_transfer_discard(connection, SERVER);
601 /* send type-1 message */
602 /* note: buildSmbNtml* knows how to deal with NULL arguments */
603 buildSmbNtlmAuthRequest((tSmbNtlmAuthRequest *) buf, connection->proxy_username, connection->proxy_domain);
604 to64frombits(buf2, buf, SmbLength((tSmbNtlmAuthResponse *) buf));
606 FREE_AND_NULL(connection->proxy_auth);
607 snprintf(buf, sizeof(buf), "NTLM %s", buf2);
608 connection->proxy_auth = xstrdup(buf);
610 header_send(connection->header, connection, SERVER, HEADER_FORWARD);
612 headbuf = header_get(connection, SERVER, TIMEOUT);
613 if (headbuf == NULL) {
614 /* squid and maybe some other proxies will disconnect after sending
615 the first 407 message regardless of Connection header */
616 ret = protocol_reconnect(connection);
620 header_send(connection->header, connection, SERVER, HEADER_FORWARD);
622 headbuf = header_get(connection, SERVER, TIMEOUT);
627 header = http_header_parse_response(headbuf);
632 http_header_free(connection->rheader);
633 connection->rheader = header;
635 if (header->proxy_authenticate == NULL || strncasecmp(header->proxy_authenticate, "NTLM ", 5))
638 http_transfer_discard(connection, SERVER);
640 /* parse type-2 message */
641 from64tobits(buf, &header->proxy_authenticate[5]);
642 buildSmbNtlmAuthResponse((tSmbNtlmAuthChallenge *) buf, (tSmbNtlmAuthResponse *) buf2, connection->proxy_username, connection->proxy_password);
644 /* send type-3 message */
645 to64frombits(buf, buf2, SmbLength((tSmbNtlmAuthResponse *) buf2));
646 snprintf(buf2, sizeof(buf2), "NTLM %s", buf);
648 FREE_AND_NULL(connection->proxy_auth);
649 connection->proxy_auth = xstrdup(buf2);
651 header_send(connection->header, connection, SERVER, HEADER_FORWARD);
653 connection->keepalive_server = oldkeep;
658 connection->keepalive_server = oldkeep;
663 /* basic proxy authentication */
664 int send_basic_response(CONNECTION * connection)
666 char buf[4096], buf2[4096];
668 /* discard body of 407 authentication required message */
669 http_transfer_discard(connection, SERVER);
671 snprintf(buf, sizeof(buf), "%s:%s", (connection->proxy_username != NULL) ? connection->proxy_username : "", (connection->proxy_password != NULL) ? connection->proxy_password : "");
672 to64frombits(buf2, buf, strlen(buf));
674 snprintf(buf, sizeof(buf), "Basic %s", buf2);
676 FREE_AND_NULL(connection->proxy_auth);
677 connection->proxy_auth = xstrdup(buf);
679 header_send(connection->header, connection, SERVER, HEADER_FORWARD);