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>
35 #endif /* USE_SYSLOG */
38 char configfile[256] = "", logfile[256] = "", username[256] = "", group[256] = "";
39 TEMPLATES *templates = NULL;
40 ACCESS_LIST *access_list = NULL;
41 HEADER_LIST *header_list = NULL;
42 FILTER_LIST *filter_list = NULL;
43 COOKIE_LIST *cookie_list = NULL;
44 REWRITE_LIST *rewrite_list = NULL;
45 MIME_LIST *mime_list = NULL;
46 REDIRECT_LIST *redirect_list = NULL;
47 KEYWORD_LIST *keyword_list = NULL;
48 FORWARD_LIST *forward_list = NULL;
49 EXTERNAL *external = NULL;
50 HASH_TABLE *dns_cache;
51 THREADLIST threads[MAXTHREADS];
52 NETWORK *network = NULL;
53 LOGBUFFER *logbuffer = NULL;
55 int main(int argc, char **argv)
58 char pidfile[256] = "";
60 struct passwd *pwd = NULL;
61 struct group *grp = NULL;
68 while ((x = getopt(argc, argv, "hp:c:l:d:u:g:")) != EOF) {
71 s_strncpy(configfile, optarg, sizeof(configfile));
74 s_strncpy(logfile, optarg, sizeof(logfile));
77 s_strncpy(pidfile, optarg, sizeof(pidfile));
80 loglevel = atoi(optarg);
83 s_strncpy(username, optarg, sizeof(username));
86 s_strncpy(group, optarg, sizeof(group));
95 memset(argv[argc], 0, strlen(argv[argc]));
98 if (stat(configfile, &fileinfo) == -1) {
99 fprintf(stderr, "couldn't stat %s\n", configfile);
103 fprintf(stderr, "config file option missing\n");
107 if (strcmp(group, "")) {
108 grp = getgrnam(group);
110 fprintf(stderr, "getgrnam: unknown group\n");
114 x = setgid(grp->gr_gid);
120 grp = getgrgid(getgid());
122 fprintf(stderr, "setgrgid: unknown group");
126 s_strncpy(group, grp->gr_name, sizeof(group));
129 if (strcmp(username, "")) {
130 pwd = getpwnam(username);
132 fprintf(stderr, "getpwnam: unknown user\n");
136 x = setuid(pwd->pw_uid);
142 pwd = getpwuid(getuid());
144 fprintf(stderr, "getpwuid: unknown user");
148 s_strncpy(username, pwd->pw_name, sizeof(username));
155 fprintf(stderr, "failed to fork daemon\n");
160 x = pid_check(pidfile);
173 close(STDOUT_FILENO);
174 close(STDERR_FILENO);
184 void show_help(char **argv)
186 fprintf(stderr, "MiddleMan filtering proxy server v%s (c)2002 Jason McLaughlin\n\n", MMAN_VERSION);
187 fprintf(stderr, "Usage: %s [options]\n\n", argv[0]);
188 fprintf(stderr, " -c <file> : location of config file\n");
190 fprintf(stderr, " -l <file> : file to log actvity to\n");
192 fprintf(stderr, " -p <file> : PID file\n");
193 fprintf(stderr, " -u <username> : run as alternate user\n");
194 fprintf(stderr, " -g <groupname> : run in altername group\n");
195 fprintf(stderr, " -d <level> : set log level (default: %d)\n", loglevel);
196 fprintf(stderr, " -h : help\n\n");
197 fprintf(stderr, " Add any of the following to specify logging detail:\n");
198 fprintf(stderr, " 1 = requests\n");
199 fprintf(stderr, " 2 = network\n");
200 fprintf(stderr, " 4 = url filtering\n");
201 fprintf(stderr, " 8 = header filtering\n");
202 fprintf(stderr, " 16 = mime filtering\n");
203 fprintf(stderr, " 32 = cookie filtering\n");
204 fprintf(stderr, " 64 = redirections\n");
205 fprintf(stderr, " 128 = templates\n");
206 fprintf(stderr, " 256 = keyword filtering\n");
207 fprintf(stderr, " 512 = warnings\n");
208 fprintf(stderr, " 1024 = errors\n");
209 fprintf(stderr, " 2048 = debug\n");
213 check if pidfile exists and whether or not the pid inside is active, otherwise
216 int pid_check(char *pidfile)
221 fptr = fopen(pidfile, "r");
225 x = fscanf(fptr, "%d", &i);
231 x = kill(i, SIGCHLD);
239 fptr = fopen(pidfile, "w");
243 fprintf(fptr, "%u\n", (unsigned int) getpid());
250 things that only need to be done once at startup
257 openlog("mman", LOG_PID, LOG_DAEMON);
260 logbuffer = xmalloc(sizeof(LOGBUFFER));
261 logbuffer->entries = 0;
262 logbuffer->size = LOGBUFFERSIZE;
263 logbuffer->head = logbuffer->tail = NULL;
264 pthread_rwlock_init(&logbuffer->lock, NULL);
269 for (i = 0; i < MAXTHREADS; i++) {
270 threads[i].flags = THREAD_UNUSED;
271 pthread_mutex_init(&threads[i].lock, NULL);
275 pcre_malloc = (void *) xmalloc;
277 config_load(3, configfile);
279 dns_cache = hash_create(DNS_HASH_SIZE);
282 int config_load(int overwrite, char *file)
286 xml_list = xml_load(NULL, file);
288 if (xml_list == NULL)
291 if (access_list != NULL) {
292 pthread_rwlock_wrlock(&access_list->lock);
296 access_ll_free(access_list->allow);
297 access_ll_free(access_list->deny);
298 access_list->allow = access_list->deny = NULL;
301 access_load(access_list, xml_list);
303 pthread_rwlock_unlock(&access_list->lock);
305 access_list = access_load(NULL, xml_list);
307 if (filter_list != NULL) {
308 pthread_rwlock_wrlock(&filter_list->lock);
312 filter_ll_free(filter_list->allow);
313 filter_ll_free(filter_list->deny);
314 filter_list->allow = filter_list->deny = NULL;
315 FREE_AND_NULL(filter_list->dtemplate);
318 filter_load(filter_list, xml_list);
320 pthread_rwlock_unlock(&filter_list->lock);
322 filter_list = filter_load(NULL, xml_list);
324 if (header_list != NULL) {
325 pthread_rwlock_wrlock(&header_list->lock);
329 header_ll_free(header_list->allow);
330 header_ll_free(header_list->deny);
331 header_ll_free(header_list->insert);
332 header_list->allow = header_list->deny = header_list->insert = NULL;
335 header_load(header_list, xml_list);
337 pthread_rwlock_unlock(&header_list->lock);
339 header_list = header_load(NULL, xml_list);
341 if (cookie_list != NULL) {
342 pthread_rwlock_wrlock(&cookie_list->lock);
346 cookie_ll_free(cookie_list->allow);
347 cookie_ll_free(cookie_list->deny);
348 cookie_list->allow = cookie_list->deny = NULL;
351 cookie_load(cookie_list, xml_list);
353 pthread_rwlock_unlock(&cookie_list->lock);
355 cookie_list = cookie_load(NULL, xml_list);
357 if (rewrite_list != NULL) {
358 pthread_rwlock_wrlock(&rewrite_list->lock);
361 rewrite_list->id = 0;
362 rewrite_list_free(rewrite_list->rewrite);
363 rewrite_list->rewrite = NULL;
366 rewrite_load(rewrite_list, xml_list);
368 pthread_rwlock_unlock(&rewrite_list->lock);
370 rewrite_list = rewrite_load(NULL, xml_list);
372 if (mime_list != NULL) {
373 pthread_rwlock_wrlock(&mime_list->lock);
377 mime_ll_free(mime_list->allow);
378 mime_ll_free(mime_list->deny);
379 mime_list->allow = mime_list->deny = NULL;
380 FREE_AND_NULL(mime_list->dtemplate);
383 mime_load(mime_list, xml_list);
385 pthread_rwlock_unlock(&mime_list->lock);
387 mime_list = mime_load(NULL, xml_list);
389 if (redirect_list != NULL) {
390 pthread_rwlock_wrlock(&redirect_list->lock);
393 redirect_list->id = 0;
394 redirect_list_free(redirect_list->redirect_list);
395 redirect_list->redirect_list = NULL;
398 redirect_load(redirect_list, xml_list);
400 pthread_rwlock_unlock(&redirect_list->lock);
402 redirect_list = redirect_load(NULL, xml_list);
404 if (keyword_list != NULL) {
405 pthread_rwlock_wrlock(&keyword_list->lock);
408 keyword_list->id = 0;
409 keyword_list_free(keyword_list->keyword_list);
410 keyword_list->keyword_list = NULL;
413 keyword_load(keyword_list, xml_list);
415 pthread_rwlock_unlock(&keyword_list->lock);
417 keyword_list = keyword_load(NULL, xml_list);
419 if (forward_list != NULL) {
420 pthread_rwlock_wrlock(&forward_list->lock);
423 forward_list->id = 0;
424 forward_list_free(forward_list->forward_list);
425 forward_list->forward_list = NULL;
428 forward_load(forward_list, xml_list);
430 pthread_rwlock_unlock(&forward_list->lock);
432 forward_list = forward_load(NULL, xml_list);
434 if (templates != NULL) {
435 pthread_rwlock_wrlock(&templates->lock);
439 templates_list_free(templates->template_list);
440 templates->template_list = NULL;
441 FREE_AND_NULL(templates->path);
444 templates_load(templates, xml_list);
446 pthread_rwlock_unlock(&templates->lock);
448 templates = templates_load(NULL, xml_list);
450 if (external != NULL) {
451 pthread_rwlock_wrlock(&external->lock);
455 external_list_free(external->external_list);
456 external->external_list = NULL;
459 external_load(external, xml_list);
461 pthread_rwlock_unlock(&external->lock);
463 external = external_load(NULL, xml_list);
465 /* only do this at startup */
466 if (overwrite == 3) {
467 if (network != NULL) {
468 pthread_rwlock_wrlock(&network->lock);
470 network = network_load(network, xml_list);
471 network_check(network);
473 pthread_rwlock_unlock(&network->lock);
475 network = network_load(NULL, xml_list);
476 network_check(network);
480 xml_list_free(xml_list);
486 main event loop; accept connections, check access list, then create thread to
492 CONNECTION *connection;
497 connection = net_accept(-1);
499 if (connection != NULL) {
500 if (access_check(access_list, connection, NULL, NULL)) {
501 x = process_new(connection);
503 putlog(MMLOG_ERROR, "failed to create thread for %s", connection->ip);
504 net_close(connection);
506 putlog(MMLOG_NETWORK, "refused connect from %s on port %d", connection->ip, connection->port);
508 net_close(connection);
514 static void sigchld(int signo)
516 while (waitpid(-1,NULL,WNOHANG)>0);
522 int process_new(CONNECTION * connection)
524 int perr, thread = -1, i;
525 pthread_attr_t thread_attr;
527 /* fork() before enqueue to threads[] */
528 signal(SIGCHLD,sigchld);
535 for (i = 0; i < MAXTHREADS && thread == -1; i++) {
536 pthread_mutex_lock(&threads[i].lock);
537 if (threads[i].flags & THREAD_UNUSED) {
540 threads[i].flags = THREAD_IDLE;
541 threads[i].host = NULL;
542 threads[i].file = NULL;
543 threads[i].method = NULL;
545 threads[i].requests = 0;
547 threads[i].ip = xstrdup(connection->ip);
549 pthread_mutex_unlock(&threads[i].lock);
555 connection->thread = thread;
557 pthread_attr_init(&thread_attr);
558 pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
560 pthread_attr_setschedpolicy(&thread_attr, SCHED_FIFO);
562 process_entry(connection);
564 pthread_attr_destroy(&thread_attr);
570 entry function for new threads
572 void process_entry(CONNECTION * connection)
575 struct HTTP_HEADER_LIST *http_header_list;
576 struct FILTER_LIST_LIST *filter_match;
577 char *headbuf = NULL, *ptr, buf[4096];
578 struct url_command_t **url_command;
582 /* write log message here so the pid matches */
583 putlog(MMLOG_NETWORK, "allowed connect from %s on port %d", connection->ip, connection->port);
585 pthread_mutex_lock(&threads[connection->thread].lock);
586 threads[connection->thread].flags = THREAD_CHEADERWAIT;
587 threads[connection->thread].pid = (unsigned int) getpid();
588 pthread_mutex_unlock(&threads[connection->thread].lock);
591 /* reset bypass mask to the one provided by the access rule */
592 connection->bypass = connection->obypass;
594 headbuf = header_get(connection, CLIENT, (connection->request) ? KEEPTIMEOUT : TIMEOUT);
595 if (headbuf == NULL) {
596 if (!connection->request) {
597 putlog(MMLOG_WARN, "timeout waiting for header from %s", connection->ip);
599 template_send(templates, "badrequest", connection, 400);
605 /* pass the client header through the rewrite rules before parsing */
606 /* note: can't bypass this with a url command */
607 filebuf = filebuf_new();
608 filebuf->data = headbuf;
609 filebuf->size = strlen(headbuf) + 1;
611 rewrite_do(rewrite_list, connection, filebuf, REWRITE_CLIENT, TRUE);
613 headbuf = filebuf->data;
614 filebuf->data = NULL;
616 filebuf_free(filebuf);
618 connection->header = http_header_parse_request(headbuf);
621 if (connection->header == NULL) {
622 if (!connection->request) {
623 putlog(MMLOG_WARN, "invalid header reveived from %s", connection->ip);
625 template_send(templates, "badrequest", connection, 400);
631 /* determine if the connection should be kept alive with the information gathered
632 so far, this is incase a template or web interface is used */
633 if (connection->header->type == HTTP_CONNECT)
634 connection->keepalive_client = FALSE;
635 else if (connection->header->keepalive == FALSE || connection->header->proxy_keepalive == FALSE)
636 connection->keepalive_client = FALSE;
637 else if (connection->header->proxy_keepalive == TRUE || connection->header->keepalive == TRUE)
638 connection->keepalive_client = TRUE;
639 else if (connection->header->version == HTTP_HTTP11)
640 connection->keepalive_client = TRUE;
642 connection->keepalive_client = FALSE;
644 if ((connection->access & ACCESS_BYPASS) && connection->header->url_command != NULL) {
645 for (url_command = connection->header->url_command; *url_command; url_command++) {
646 if (!strcasecmp((*url_command)->command, "bypass")) {
647 ptr = (*url_command)->options;
651 for (; *ptr; ptr++) {
661 connection->bypass |= FEATURE_FILTER;
663 connection->bypass &= ~FEATURE_FILTER;
667 connection->bypass |= FEATURE_HEADER;
669 connection->bypass &= ~FEATURE_HEADER;
673 connection->bypass |= FEATURE_MIME;
675 connection->bypass &= ~FEATURE_MIME;
679 connection->bypass |= FEATURE_REDIRECT;
681 connection->bypass &= ~FEATURE_REDIRECT;
685 connection->bypass |= FEATURE_COOKIES;
687 connection->bypass &= ~FEATURE_COOKIES;
691 connection->bypass |= FEATURE_REWRITE;
693 connection->bypass &= ~FEATURE_REWRITE;
697 connection->bypass |= FEATURE_EXTERNAL;
699 connection->bypass &= ~FEATURE_EXTERNAL;
703 connection->bypass |= FEATURE_FORWARD;
705 connection->bypass &= ~FEATURE_FORWARD;
709 connection->bypass |= FEATURE_KEYWORDS;
711 connection->bypass &= ~FEATURE_KEYWORDS;
716 connection->bypass = ~0;
721 if (connection->authenticate == TRUE && connection->header->proxy_authorization == NULL) {
722 header = header_new();
723 header->type = HTTP_RESP;
725 header->content_length = 0;
726 header->proxy_authenticate = xstrdup("Basic");
728 header_send(header, connection, CLIENT, HEADER_RESP);
730 http_header_free(header);
733 } else if (connection->header->proxy_authorization != NULL) {
734 ptr = strchr(connection->header->proxy_authorization, ' ');
738 ret = from64tobits(buf, ptr);
741 putlog(MMLOG_DEBUG, "%s", buf);
743 ptr = strchr(buf, ':');
747 ret = access_check(access_list, connection, buf, ++ptr);
749 connection->authenticate = FALSE;
753 if (connection->authenticate == TRUE) {
754 header = header_new();
755 header->type = HTTP_RESP;
757 header->content_length = 0;
758 header->proxy_authenticate = xstrdup("Basic");
760 header_send(header, connection, CLIENT, HEADER_RESP);
762 http_header_free(header);
768 /* redirect the request if any matching rules found (redirect_do will fill in the host, file and port
769 members of the connection struct if any are found) */
770 x = redirect_do(redirect_list, connection, REDIRECT_REQUEST);
772 /* 302 redirect sent, no need to continue */
776 pthread_mutex_lock(&threads[connection->thread].lock);
777 FREE_AND_STRDUP(threads[connection->thread].host, connection->header->host);
778 FREE_AND_STRDUP(threads[connection->thread].file, connection->header->file);
779 FREE_AND_STRDUP(threads[connection->thread].method, connection->header->method);
780 threads[connection->thread].port = connection->header->port;
781 pthread_mutex_unlock(&threads[connection->thread].lock);
783 http_header_list = header_filter(header_list, connection);
784 connection->header->header_filtered = http_header_list;
786 if (connection->header->type != HTTP_CONNECT) {
787 if (connection->header->type != HTTP_REQUEST && strcasecmp(connection->header->proto, "http")) {
788 /* only http protocol is supported */
789 template_send(templates, "badprotocol", connection, 501);
794 if ((connection->header->type != HTTP_REQUEST && !strcasecmp(connection->header->host, INTERFACEURL)) || (connection->header->type == HTTP_REQUEST && !strncasecmp(&connection->header->file[1], INTERFACEURL, strlen(INTERFACEURL)))) {
795 /* request for web interface */
796 putlog(MMLOG_REQUEST, "request for web interface from %s", connection->ip);
798 interface_handle_request(connection);
803 if ((connection->header->type == HTTP_REQUEST && !(connection->access & ACCESS_HTTP)) || (connection->header->type == HTTP_PROXY && !(connection->access & ACCESS_PROXY))) {
804 template_send(templates, "noaccess", connection, 404);
808 } else if (!(connection->access & ACCESS_CONNECT)) {
809 template_send(templates, "noaccess", connection, 404);
814 if (connection->header->type == HTTP_REQUEST && connection->header->host == NULL) {
815 if ((connection->access & ACCESS_TRANSPARENT) && connection->header->host_header != NULL) {
816 /* use Host: header if it's there */
817 ptr = strchr(connection->header->host_header, ':');
818 connection->header->host = xstrndup(connection->header->host_header, (ptr != NULL) ? ptr - connection->header->host_header : strlen(connection->header->host_header));
820 connection->header->port = atoi(&ptr[1]);
822 /* this feature causes a recursion where the proxy keeps
823 connecting to itself if an HTTP request is made to the proxy
824 which doesn't match a redirect rule and isn't a request for the web interface.
825 There's no reliable way to detect this except to forbid connections
826 to websites on the same port as the proxy
828 if (connection->header->port == connection->port) {
829 template_send(templates, "nofile", connection, 404);
834 /* not a request for web interface, no host header, and no matching redirect rule */
835 template_send(templates, "nofile", connection, 404);
840 for (url_command = connection->header->url_command; url_command && *url_command; url_command++) {
841 if (!strcasecmp((*url_command)->command, "filter")) {
842 filter_check_show(connection);
848 filter_match = filter_check(filter_list, connection);
849 if (filter_match != NULL) {
850 putlog(MMLOG_FILTER, "blocked %s%s", connection->header->host, connection->header->file);
852 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);
854 pthread_rwlock_unlock(&filter_list->lock);
858 pthread_rwlock_unlock(&filter_list->lock);
860 if (connection->site_host != NULL && (strcasecmp(connection->header->host, connection->site_host) || connection->header->port != connection->site_port)) {
861 /* not a request for same host/port as previous request */
862 if (connection->server != NULL) {
863 sock_close(connection->server);
864 connection->server = NULL;
868 if (connection->server == NULL) {
869 /* check if this request should be forwarded through another proxy */
870 /* forward_do will fill in all the necessary members of the connection struct */
871 connection->proxy_type = PROXY_DIRECT;
872 forward_do(forward_list, connection);
874 x = protocol_start(connection);
878 FREE_AND_NULL(connection->site_host);
879 connection->site_host = xstrdup(connection->header->host);
880 connection->site_port = connection->header->port;
883 if (connection->header->type != HTTP_CONNECT)
884 x = protocol_http(connection);
886 x = protocol_connect(connection);
889 putlog(MMLOG_HEADER, "error reading header from %s", connection->header->host);
892 sock_flush(connection->client);
894 connection->request++;
896 http_header_free(connection->header);
898 putlog(MMLOG_DEBUG, "keepalive_client = %d", connection->keepalive_client);
900 if (!connection->keepalive_client) {
901 if (connection->server != NULL) {
902 sock_close(connection->server);
903 connection->server = NULL;
907 } else if (!connection->keepalive_server && connection->server != NULL) {
908 sock_close(connection->server);
909 connection->server = NULL;
913 pthread_mutex_lock(&threads[connection->thread].lock);
914 threads[connection->thread].flags = THREAD_CHEADERWAIT;
915 FREE_AND_NULL(threads[connection->thread].host);
916 FREE_AND_NULL(threads[connection->thread].file);
917 FREE_AND_NULL(threads[connection->thread].method);
918 threads[connection->thread].port = 0;
919 threads[connection->thread].requests++;
920 pthread_mutex_unlock(&threads[connection->thread].lock);
922 connection->header = NULL;
923 connection->rheader = NULL;
925 FREE_AND_NULL(connection->proxy_host);
928 if (connection->server != NULL)
929 sock_close(connection->server);
931 putlog(MMLOG_NETWORK, "%s disconnected after making %d requests", connection->ip, connection->request);
933 pthread_mutex_lock(&threads[connection->thread].lock);
934 threads[connection->thread].flags = THREAD_UNUSED;
935 FREE_AND_NULL(threads[connection->thread].ip);
936 FREE_AND_NULL(threads[connection->thread].host);
937 FREE_AND_NULL(threads[connection->thread].file);
938 FREE_AND_NULL(threads[connection->thread].method);
939 pthread_mutex_unlock(&threads[connection->thread].lock);
941 net_close(connection);
947 save all config settings to a file
949 int config_save(char *filename)
952 XML_LIST *xml_list = NULL;
954 /* reconstruct config file for all sections */
955 xml_list = network_xml(network, xml_list);
956 xml_list = templates_xml(templates, xml_list);
957 xml_list = external_xml(external, xml_list);
958 xml_list = access_xml(access_list, xml_list);
959 xml_list = header_xml(header_list, xml_list);
960 xml_list = cookie_xml(cookie_list, xml_list);
961 xml_list = redirect_xml(redirect_list, xml_list);
962 xml_list = keyword_xml(keyword_list, xml_list);
963 xml_list = forward_xml(forward_list, xml_list);
964 xml_list = filter_xml(filter_list, xml_list);
965 xml_list = mime_xml(mime_list, xml_list);
966 xml_list = rewrite_xml(rewrite_list, xml_list);
968 if (xml_list == NULL)
971 while (xml_list->prev != NULL)
972 xml_list = xml_list->prev;
974 ret = xml_save(xml_list, filename);
976 xml_list_free(xml_list);