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>
34 #endif /* USE_SYSLOG */
37 char configfile[256] = "", logfile[256] = "", username[256] = "", group[256] = "";
38 TEMPLATES *templates = NULL;
39 ACCESS_LIST *access_list = NULL;
40 HEADER_LIST *header_list = NULL;
41 FILTER_LIST *filter_list = NULL;
42 COOKIE_LIST *cookie_list = NULL;
43 REWRITE_LIST *rewrite_list = NULL;
44 MIME_LIST *mime_list = NULL;
45 REDIRECT_LIST *redirect_list = NULL;
46 KEYWORD_LIST *keyword_list = NULL;
47 FORWARD_LIST *forward_list = NULL;
48 EXTERNAL *external = NULL;
49 HASH_TABLE *dns_cache;
50 THREADLIST threads[MAXTHREADS];
51 NETWORK *network = NULL;
52 LOGBUFFER *logbuffer = NULL;
54 int main(int argc, char **argv)
57 char pidfile[256] = "";
59 struct passwd *pwd = NULL;
60 struct group *grp = NULL;
67 while ((x = getopt(argc, argv, "hp:c:l:d:u:g:")) != EOF) {
70 s_strncpy(configfile, optarg, sizeof(configfile));
73 s_strncpy(logfile, optarg, sizeof(logfile));
76 s_strncpy(pidfile, optarg, sizeof(pidfile));
79 loglevel = atoi(optarg);
82 s_strncpy(username, optarg, sizeof(username));
85 s_strncpy(group, optarg, sizeof(group));
94 memset(argv[argc], 0, strlen(argv[argc]));
97 if (stat(configfile, &fileinfo) == -1) {
98 fprintf(stderr, "couldn't stat %s\n", configfile);
102 fprintf(stderr, "config file option missing\n");
106 if (strcmp(group, "")) {
107 grp = getgrnam(group);
109 fprintf(stderr, "getgrnam: unknown group\n");
113 x = setgid(grp->gr_gid);
119 grp = getgrgid(getgid());
121 fprintf(stderr, "setgrgid: unknown group");
125 s_strncpy(group, grp->gr_name, sizeof(group));
128 if (strcmp(username, "")) {
129 pwd = getpwnam(username);
131 fprintf(stderr, "getpwnam: unknown user\n");
135 x = setuid(pwd->pw_uid);
141 pwd = getpwuid(getuid());
143 fprintf(stderr, "getpwuid: unknown user");
147 s_strncpy(username, pwd->pw_name, sizeof(username));
154 fprintf(stderr, "failed to fork daemon\n");
159 x = pid_check(pidfile);
172 close(STDOUT_FILENO);
173 close(STDERR_FILENO);
183 void show_help(char **argv)
185 fprintf(stderr, "MiddleMan filtering proxy server v%s (c)2002 Jason McLaughlin\n\n", MMAN_VERSION);
186 fprintf(stderr, "Usage: %s [options]\n\n", argv[0]);
187 fprintf(stderr, " -c <file> : location of config file\n");
189 fprintf(stderr, " -l <file> : file to log actvity to\n");
191 fprintf(stderr, " -p <file> : PID file\n");
192 fprintf(stderr, " -u <username> : run as alternate user\n");
193 fprintf(stderr, " -g <groupname> : run in altername group\n");
194 fprintf(stderr, " -d <level> : set log level (default: %d)\n", loglevel);
195 fprintf(stderr, " -h : help\n\n");
196 fprintf(stderr, " Add any of the following to specify logging detail:\n");
197 fprintf(stderr, " 1 = requests\n");
198 fprintf(stderr, " 2 = network\n");
199 fprintf(stderr, " 4 = url filtering\n");
200 fprintf(stderr, " 8 = header filtering\n");
201 fprintf(stderr, " 16 = mime filtering\n");
202 fprintf(stderr, " 32 = cookie filtering\n");
203 fprintf(stderr, " 64 = redirections\n");
204 fprintf(stderr, " 128 = templates\n");
205 fprintf(stderr, " 256 = keyword filtering\n");
206 fprintf(stderr, " 512 = warnings\n");
207 fprintf(stderr, " 1024 = errors\n");
208 fprintf(stderr, " 2048 = debug\n");
212 check if pidfile exists and whether or not the pid inside is active, otherwise
215 int pid_check(char *pidfile)
220 fptr = fopen(pidfile, "r");
224 x = fscanf(fptr, "%d", &i);
230 x = kill(i, SIGCHLD);
238 fptr = fopen(pidfile, "w");
242 fprintf(fptr, "%u\n", (unsigned int) getpid());
249 things that only need to be done once at startup
256 openlog("mman", LOG_PID, LOG_DAEMON);
259 logbuffer = xmalloc(sizeof(LOGBUFFER));
260 logbuffer->entries = 0;
261 logbuffer->size = LOGBUFFERSIZE;
262 logbuffer->head = logbuffer->tail = NULL;
263 pthread_rwlock_init(&logbuffer->lock, NULL);
268 for (i = 0; i < MAXTHREADS; i++) {
269 threads[i].flags = THREAD_UNUSED;
270 pthread_mutex_init(&threads[i].lock, NULL);
274 pcre_malloc = (void *) xmalloc;
276 config_load(3, configfile);
278 dns_cache = hash_create(DNS_HASH_SIZE);
281 int config_load(int overwrite, char *file)
285 xml_list = xml_load(NULL, file);
287 if (xml_list == NULL)
290 if (access_list != NULL) {
291 pthread_rwlock_wrlock(&access_list->lock);
295 access_ll_free(access_list->allow);
296 access_ll_free(access_list->deny);
297 access_list->allow = access_list->deny = NULL;
300 access_load(access_list, xml_list);
302 pthread_rwlock_unlock(&access_list->lock);
304 access_list = access_load(NULL, xml_list);
306 if (filter_list != NULL) {
307 pthread_rwlock_wrlock(&filter_list->lock);
311 filter_ll_free(filter_list->allow);
312 filter_ll_free(filter_list->deny);
313 filter_list->allow = filter_list->deny = NULL;
314 FREE_AND_NULL(filter_list->dtemplate);
317 filter_load(filter_list, xml_list);
319 pthread_rwlock_unlock(&filter_list->lock);
321 filter_list = filter_load(NULL, xml_list);
323 if (header_list != NULL) {
324 pthread_rwlock_wrlock(&header_list->lock);
328 header_ll_free(header_list->allow);
329 header_ll_free(header_list->deny);
330 header_ll_free(header_list->insert);
331 header_list->allow = header_list->deny = header_list->insert = NULL;
334 header_load(header_list, xml_list);
336 pthread_rwlock_unlock(&header_list->lock);
338 header_list = header_load(NULL, xml_list);
340 if (cookie_list != NULL) {
341 pthread_rwlock_wrlock(&cookie_list->lock);
345 cookie_ll_free(cookie_list->allow);
346 cookie_ll_free(cookie_list->deny);
347 cookie_list->allow = cookie_list->deny = NULL;
350 cookie_load(cookie_list, xml_list);
352 pthread_rwlock_unlock(&cookie_list->lock);
354 cookie_list = cookie_load(NULL, xml_list);
356 if (rewrite_list != NULL) {
357 pthread_rwlock_wrlock(&rewrite_list->lock);
360 rewrite_list->id = 0;
361 rewrite_list_free(rewrite_list->rewrite);
362 rewrite_list->rewrite = NULL;
365 rewrite_load(rewrite_list, xml_list);
367 pthread_rwlock_unlock(&rewrite_list->lock);
369 rewrite_list = rewrite_load(NULL, xml_list);
371 if (mime_list != NULL) {
372 pthread_rwlock_wrlock(&mime_list->lock);
376 mime_ll_free(mime_list->allow);
377 mime_ll_free(mime_list->deny);
378 mime_list->allow = mime_list->deny = NULL;
379 FREE_AND_NULL(mime_list->dtemplate);
382 mime_load(mime_list, xml_list);
384 pthread_rwlock_unlock(&mime_list->lock);
386 mime_list = mime_load(NULL, xml_list);
388 if (redirect_list != NULL) {
389 pthread_rwlock_wrlock(&redirect_list->lock);
392 redirect_list->id = 0;
393 redirect_list_free(redirect_list->redirect_list);
394 redirect_list->redirect_list = NULL;
397 redirect_load(redirect_list, xml_list);
399 pthread_rwlock_unlock(&redirect_list->lock);
401 redirect_list = redirect_load(NULL, xml_list);
403 if (keyword_list != NULL) {
404 pthread_rwlock_wrlock(&keyword_list->lock);
407 keyword_list->id = 0;
408 keyword_list_free(keyword_list->keyword_list);
409 keyword_list->keyword_list = NULL;
412 keyword_load(keyword_list, xml_list);
414 pthread_rwlock_unlock(&keyword_list->lock);
416 keyword_list = keyword_load(NULL, xml_list);
418 if (forward_list != NULL) {
419 pthread_rwlock_wrlock(&forward_list->lock);
422 forward_list->id = 0;
423 forward_list_free(forward_list->forward_list);
424 forward_list->forward_list = NULL;
427 forward_load(forward_list, xml_list);
429 pthread_rwlock_unlock(&forward_list->lock);
431 forward_list = forward_load(NULL, xml_list);
433 if (templates != NULL) {
434 pthread_rwlock_wrlock(&templates->lock);
438 templates_list_free(templates->template_list);
439 templates->template_list = NULL;
440 FREE_AND_NULL(templates->path);
443 templates_load(templates, xml_list);
445 pthread_rwlock_unlock(&templates->lock);
447 templates = templates_load(NULL, xml_list);
449 if (external != NULL) {
450 pthread_rwlock_wrlock(&external->lock);
454 external_list_free(external->external_list);
455 external->external_list = NULL;
458 external_load(external, xml_list);
460 pthread_rwlock_unlock(&external->lock);
462 external = external_load(NULL, xml_list);
464 /* only do this at startup */
465 if (overwrite == 3) {
466 if (network != NULL) {
467 pthread_rwlock_wrlock(&network->lock);
469 network = network_load(network, xml_list);
470 network_check(network);
472 pthread_rwlock_unlock(&network->lock);
474 network = network_load(NULL, xml_list);
475 network_check(network);
479 xml_list_free(xml_list);
485 main event loop; accept connections, check access list, then create thread to
491 CONNECTION *connection;
496 connection = net_accept(-1);
498 if (connection != NULL) {
499 if (access_check(access_list, connection, NULL, NULL)) {
500 x = process_new(connection);
502 putlog(MMLOG_ERROR, "failed to create thread for %s", connection->ip);
504 net_close(connection);
507 putlog(MMLOG_NETWORK, "refused connect from %s on port %d", connection->ip, connection->port);
509 net_close(connection);
518 int process_new(CONNECTION * connection)
520 int perr, thread = -1, i;
522 pthread_attr_t thread_attr;
524 for (i = 0; i < MAXTHREADS && thread == -1; i++) {
525 pthread_mutex_lock(&threads[i].lock);
526 if (threads[i].flags & THREAD_UNUSED) {
529 threads[i].flags = THREAD_IDLE;
530 threads[i].host = NULL;
531 threads[i].file = NULL;
532 threads[i].method = NULL;
534 threads[i].requests = 0;
536 threads[i].ip = xstrdup(connection->ip);
538 pthread_mutex_unlock(&threads[i].lock);
544 connection->thread = thread;
546 pthread_attr_init(&thread_attr);
547 pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
549 pthread_attr_setschedpolicy(&thread_attr, SCHED_FIFO);
551 perr = pthread_create(&thread_id, &thread_attr, (void *) process_entry, (void *) connection);
553 pthread_attr_destroy(&thread_attr);
559 entry function for new threads
561 void process_entry(CONNECTION * connection)
564 struct HTTP_HEADER_LIST *http_header_list;
565 struct FILTER_LIST_LIST *filter_match;
566 char *headbuf = NULL, *ptr, buf[4096];
567 struct url_command_t **url_command;
571 /* write log message here so the pid matches */
572 putlog(MMLOG_NETWORK, "allowed connect from %s on port %d", connection->ip, connection->port);
574 pthread_mutex_lock(&threads[connection->thread].lock);
575 threads[connection->thread].flags = THREAD_CHEADERWAIT;
576 threads[connection->thread].pid = (unsigned int) getpid();
577 pthread_mutex_unlock(&threads[connection->thread].lock);
580 /* reset bypass mask to the one provided by the access rule */
581 connection->bypass = connection->obypass;
583 headbuf = header_get(connection, CLIENT, (connection->request) ? KEEPTIMEOUT : TIMEOUT);
584 if (headbuf == NULL) {
585 if (!connection->request) {
586 putlog(MMLOG_WARN, "timeout waiting for header from %s", connection->ip);
588 template_send(templates, "badrequest", connection, 400);
594 /* pass the client header through the rewrite rules before parsing */
595 /* note: can't bypass this with a url command */
596 filebuf = filebuf_new();
597 filebuf->data = headbuf;
598 filebuf->size = strlen(headbuf) + 1;
600 rewrite_do(rewrite_list, connection, filebuf, REWRITE_CLIENT, TRUE);
602 headbuf = filebuf->data;
603 filebuf->data = NULL;
605 filebuf_free(filebuf);
607 connection->header = http_header_parse_request(headbuf);
610 if (connection->header == NULL) {
611 if (!connection->request) {
612 putlog(MMLOG_WARN, "invalid header reveived from %s", connection->ip);
614 template_send(templates, "badrequest", connection, 400);
620 /* determine if the connection should be kept alive with the information gathered
621 so far, this is incase a template or web interface is used */
622 if (connection->header->type == HTTP_CONNECT)
623 connection->keepalive_client = FALSE;
624 else if (connection->header->keepalive == FALSE || connection->header->proxy_keepalive == FALSE)
625 connection->keepalive_client = FALSE;
626 else if (connection->header->proxy_keepalive == TRUE || connection->header->keepalive == TRUE)
627 connection->keepalive_client = TRUE;
628 else if (connection->header->version == HTTP_HTTP11)
629 connection->keepalive_client = TRUE;
631 connection->keepalive_client = FALSE;
633 if ((connection->access & ACCESS_BYPASS) && connection->header->url_command != NULL) {
634 for (url_command = connection->header->url_command; *url_command; url_command++) {
635 if (!strcasecmp((*url_command)->command, "bypass")) {
636 ptr = (*url_command)->options;
640 for (; *ptr; ptr++) {
650 connection->bypass |= FEATURE_FILTER;
652 connection->bypass &= ~FEATURE_FILTER;
656 connection->bypass |= FEATURE_HEADER;
658 connection->bypass &= ~FEATURE_HEADER;
662 connection->bypass |= FEATURE_MIME;
664 connection->bypass &= ~FEATURE_MIME;
668 connection->bypass |= FEATURE_REDIRECT;
670 connection->bypass &= ~FEATURE_REDIRECT;
674 connection->bypass |= FEATURE_COOKIES;
676 connection->bypass &= ~FEATURE_COOKIES;
680 connection->bypass |= FEATURE_REWRITE;
682 connection->bypass &= ~FEATURE_REWRITE;
686 connection->bypass |= FEATURE_EXTERNAL;
688 connection->bypass &= ~FEATURE_EXTERNAL;
692 connection->bypass |= FEATURE_FORWARD;
694 connection->bypass &= ~FEATURE_FORWARD;
698 connection->bypass |= FEATURE_KEYWORDS;
700 connection->bypass &= ~FEATURE_KEYWORDS;
705 connection->bypass = ~0;
710 if (connection->authenticate == TRUE && connection->header->proxy_authorization == NULL) {
711 header = header_new();
712 header->type = HTTP_RESP;
714 header->content_length = 0;
715 header->proxy_authenticate = xstrdup("Basic");
717 header_send(header, connection, CLIENT, HEADER_RESP);
719 http_header_free(header);
722 } else if (connection->header->proxy_authorization != NULL) {
723 ptr = strchr(connection->header->proxy_authorization, ' ');
727 ret = from64tobits(buf, ptr);
730 putlog(MMLOG_DEBUG, "%s", buf);
732 ptr = strchr(buf, ':');
736 ret = access_check(access_list, connection, buf, ++ptr);
738 connection->authenticate = FALSE;
742 if (connection->authenticate == TRUE) {
743 header = header_new();
744 header->type = HTTP_RESP;
746 header->content_length = 0;
747 header->proxy_authenticate = xstrdup("Basic");
749 header_send(header, connection, CLIENT, HEADER_RESP);
751 http_header_free(header);
757 /* redirect the request if any matching rules found (redirect_do will fill in the host, file and port
758 members of the connection struct if any are found) */
759 x = redirect_do(redirect_list, connection, REDIRECT_REQUEST);
761 /* 302 redirect sent, no need to continue */
765 pthread_mutex_lock(&threads[connection->thread].lock);
766 FREE_AND_STRDUP(threads[connection->thread].host, connection->header->host);
767 FREE_AND_STRDUP(threads[connection->thread].file, connection->header->file);
768 FREE_AND_STRDUP(threads[connection->thread].method, connection->header->method);
769 threads[connection->thread].port = connection->header->port;
770 pthread_mutex_unlock(&threads[connection->thread].lock);
772 http_header_list = header_filter(header_list, connection);
773 connection->header->header_filtered = http_header_list;
775 if (connection->header->type != HTTP_CONNECT) {
776 if (connection->header->type != HTTP_REQUEST && strcasecmp(connection->header->proto, "http")) {
777 /* only http protocol is supported */
778 template_send(templates, "badprotocol", connection, 501);
783 if ((connection->header->type != HTTP_REQUEST && !strcasecmp(connection->header->host, INTERFACEURL)) || (connection->header->type == HTTP_REQUEST && !strncasecmp(&connection->header->file[1], INTERFACEURL, strlen(INTERFACEURL)))) {
784 /* request for web interface */
785 putlog(MMLOG_REQUEST, "request for web interface from %s", connection->ip);
787 interface_handle_request(connection);
792 if ((connection->header->type == HTTP_REQUEST && !(connection->access & ACCESS_HTTP)) || (connection->header->type == HTTP_PROXY && !(connection->access & ACCESS_PROXY))) {
793 template_send(templates, "noaccess", connection, 404);
797 } else if (!(connection->access & ACCESS_CONNECT)) {
798 template_send(templates, "noaccess", connection, 404);
803 if (connection->header->type == HTTP_REQUEST && connection->header->host == NULL) {
804 if ((connection->access & ACCESS_TRANSPARENT) && connection->header->host_header != NULL) {
805 /* use Host: header if it's there */
806 ptr = strchr(connection->header->host_header, ':');
807 connection->header->host = xstrndup(connection->header->host_header, (ptr != NULL) ? ptr - connection->header->host_header : strlen(connection->header->host_header));
809 connection->header->port = atoi(&ptr[1]);
811 /* this feature causes a recursion where the proxy keeps
812 connecting to itself if an HTTP request is made to the proxy
813 which doesn't match a redirect rule and isn't a request for the web interface.
814 There's no reliable way to detect this except to forbid connections
815 to websites on the same port as the proxy
817 if (connection->header->port == connection->port) {
818 template_send(templates, "nofile", connection, 404);
823 /* not a request for web interface, no host header, and no matching redirect rule */
824 template_send(templates, "nofile", connection, 404);
829 for (url_command = connection->header->url_command; url_command && *url_command; url_command++) {
830 if (!strcasecmp((*url_command)->command, "filter")) {
831 filter_check_show(connection);
837 filter_match = filter_check(filter_list, connection);
838 if (filter_match != NULL) {
839 putlog(MMLOG_FILTER, "blocked %s%s", connection->header->host, connection->header->file);
841 template_send(templates, (filter_match->template != NULL) ? filter_match->template : (filter_list->dtemplate != NULL) ? filter_list->dtemplate : "blocked", connection, (connection->header->type == HTTP_CONNECT) ? 404 : 200);
843 pthread_rwlock_unlock(&filter_list->lock);
847 pthread_rwlock_unlock(&filter_list->lock);
849 if (connection->site_host != NULL && (strcasecmp(connection->header->host, connection->site_host) || connection->header->port != connection->site_port)) {
850 /* not a request for same host/port as previous request */
851 if (connection->server != NULL) {
852 sock_close(connection->server);
853 connection->server = NULL;
857 if (connection->server == NULL) {
858 /* check if this request should be forwarded through another proxy */
859 /* forward_do will fill in all the necessary members of the connection struct */
860 connection->proxy_type = PROXY_DIRECT;
861 forward_do(forward_list, connection);
863 x = protocol_start(connection);
867 FREE_AND_NULL(connection->site_host);
868 connection->site_host = xstrdup(connection->header->host);
869 connection->site_port = connection->header->port;
872 if (connection->header->type != HTTP_CONNECT)
873 x = protocol_http(connection);
875 x = protocol_connect(connection);
878 putlog(MMLOG_HEADER, "error reading header from %s", connection->header->host);
881 sock_flush(connection->client);
883 connection->request++;
885 http_header_free(connection->header);
887 putlog(MMLOG_DEBUG, "keepalive_client = %d", connection->keepalive_client);
889 if (!connection->keepalive_client) {
890 if (connection->server != NULL) {
891 sock_close(connection->server);
892 connection->server = NULL;
896 } else if (!connection->keepalive_server && connection->server != NULL) {
897 sock_close(connection->server);
898 connection->server = NULL;
902 pthread_mutex_lock(&threads[connection->thread].lock);
903 threads[connection->thread].flags = THREAD_CHEADERWAIT;
904 FREE_AND_NULL(threads[connection->thread].host);
905 FREE_AND_NULL(threads[connection->thread].file);
906 FREE_AND_NULL(threads[connection->thread].method);
907 threads[connection->thread].port = 0;
908 threads[connection->thread].requests++;
909 pthread_mutex_unlock(&threads[connection->thread].lock);
911 connection->header = NULL;
912 connection->rheader = NULL;
914 FREE_AND_NULL(connection->proxy_host);
917 if (connection->server != NULL)
918 sock_close(connection->server);
920 putlog(MMLOG_NETWORK, "%s disconnected after making %d requests", connection->ip, connection->request);
922 pthread_mutex_lock(&threads[connection->thread].lock);
923 threads[connection->thread].flags = THREAD_UNUSED;
924 FREE_AND_NULL(threads[connection->thread].ip);
925 FREE_AND_NULL(threads[connection->thread].host);
926 FREE_AND_NULL(threads[connection->thread].file);
927 FREE_AND_NULL(threads[connection->thread].method);
928 pthread_mutex_unlock(&threads[connection->thread].lock);
930 net_close(connection);
936 save all config settings to a file
938 int config_save(char *filename)
941 XML_LIST *xml_list = NULL;
943 /* reconstruct config file for all sections */
944 xml_list = network_xml(network, xml_list);
945 xml_list = templates_xml(templates, xml_list);
946 xml_list = external_xml(external, xml_list);
947 xml_list = access_xml(access_list, xml_list);
948 xml_list = header_xml(header_list, xml_list);
949 xml_list = cookie_xml(cookie_list, xml_list);
950 xml_list = redirect_xml(redirect_list, xml_list);
951 xml_list = keyword_xml(keyword_list, xml_list);
952 xml_list = forward_xml(forward_list, xml_list);
953 xml_list = filter_xml(filter_list, xml_list);
954 xml_list = mime_xml(mime_list, xml_list);
955 xml_list = rewrite_xml(rewrite_list, xml_list);
957 if (xml_list == NULL)
960 while (xml_list->prev != NULL)
961 xml_list = xml_list->prev;
963 ret = xml_save(xml_list, filename);
965 xml_list_free(xml_list);