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);
505 net_close(connection);
508 putlog(MMLOG_NETWORK, "refused connect from %s on port %d", connection->ip, connection->port);
510 net_close(connection);
516 static void sigchld(int signo)
518 while (waitpid(-1,NULL,WNOHANG)>0);
524 int process_new(CONNECTION * connection)
526 int perr, thread = -1, i;
527 pthread_attr_t thread_attr;
529 for (i = 0; i < MAXTHREADS && thread == -1; i++) {
530 pthread_mutex_lock(&threads[i].lock);
531 if (threads[i].flags & THREAD_UNUSED) {
534 threads[i].flags = THREAD_IDLE;
535 threads[i].host = NULL;
536 threads[i].file = NULL;
537 threads[i].method = NULL;
539 threads[i].requests = 0;
541 threads[i].ip = xstrdup(connection->ip);
543 pthread_mutex_unlock(&threads[i].lock);
549 connection->thread = thread;
551 pthread_attr_init(&thread_attr);
552 pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
554 pthread_attr_setschedpolicy(&thread_attr, SCHED_FIFO);
556 signal(SIGCHLD,sigchld);
557 if (0==(perr=fork())) {
558 process_entry(connection);
561 perr=close(connection->client->fd)
563 pthread_attr_destroy(&thread_attr);
569 entry function for new threads
571 void process_entry(CONNECTION * connection)
574 struct HTTP_HEADER_LIST *http_header_list;
575 struct FILTER_LIST_LIST *filter_match;
576 char *headbuf = NULL, *ptr, buf[4096];
577 struct url_command_t **url_command;
581 /* write log message here so the pid matches */
582 putlog(MMLOG_NETWORK, "allowed connect from %s on port %d", connection->ip, connection->port);
584 pthread_mutex_lock(&threads[connection->thread].lock);
585 threads[connection->thread].flags = THREAD_CHEADERWAIT;
586 threads[connection->thread].pid = (unsigned int) getpid();
587 pthread_mutex_unlock(&threads[connection->thread].lock);
590 /* reset bypass mask to the one provided by the access rule */
591 connection->bypass = connection->obypass;
593 headbuf = header_get(connection, CLIENT, (connection->request) ? KEEPTIMEOUT : TIMEOUT);
594 if (headbuf == NULL) {
595 if (!connection->request) {
596 putlog(MMLOG_WARN, "timeout waiting for header from %s", connection->ip);
598 template_send(templates, "badrequest", connection, 400);
604 /* pass the client header through the rewrite rules before parsing */
605 /* note: can't bypass this with a url command */
606 filebuf = filebuf_new();
607 filebuf->data = headbuf;
608 filebuf->size = strlen(headbuf) + 1;
610 rewrite_do(rewrite_list, connection, filebuf, REWRITE_CLIENT, TRUE);
612 headbuf = filebuf->data;
613 filebuf->data = NULL;
615 filebuf_free(filebuf);
617 connection->header = http_header_parse_request(headbuf);
620 if (connection->header == NULL) {
621 if (!connection->request) {
622 putlog(MMLOG_WARN, "invalid header reveived from %s", connection->ip);
624 template_send(templates, "badrequest", connection, 400);
630 /* determine if the connection should be kept alive with the information gathered
631 so far, this is incase a template or web interface is used */
632 if (connection->header->type == HTTP_CONNECT)
633 connection->keepalive_client = FALSE;
634 else if (connection->header->keepalive == FALSE || connection->header->proxy_keepalive == FALSE)
635 connection->keepalive_client = FALSE;
636 else if (connection->header->proxy_keepalive == TRUE || connection->header->keepalive == TRUE)
637 connection->keepalive_client = TRUE;
638 else if (connection->header->version == HTTP_HTTP11)
639 connection->keepalive_client = TRUE;
641 connection->keepalive_client = FALSE;
643 if ((connection->access & ACCESS_BYPASS) && connection->header->url_command != NULL) {
644 for (url_command = connection->header->url_command; *url_command; url_command++) {
645 if (!strcasecmp((*url_command)->command, "bypass")) {
646 ptr = (*url_command)->options;
650 for (; *ptr; ptr++) {
660 connection->bypass |= FEATURE_FILTER;
662 connection->bypass &= ~FEATURE_FILTER;
666 connection->bypass |= FEATURE_HEADER;
668 connection->bypass &= ~FEATURE_HEADER;
672 connection->bypass |= FEATURE_MIME;
674 connection->bypass &= ~FEATURE_MIME;
678 connection->bypass |= FEATURE_REDIRECT;
680 connection->bypass &= ~FEATURE_REDIRECT;
684 connection->bypass |= FEATURE_COOKIES;
686 connection->bypass &= ~FEATURE_COOKIES;
690 connection->bypass |= FEATURE_REWRITE;
692 connection->bypass &= ~FEATURE_REWRITE;
696 connection->bypass |= FEATURE_EXTERNAL;
698 connection->bypass &= ~FEATURE_EXTERNAL;
702 connection->bypass |= FEATURE_FORWARD;
704 connection->bypass &= ~FEATURE_FORWARD;
708 connection->bypass |= FEATURE_KEYWORDS;
710 connection->bypass &= ~FEATURE_KEYWORDS;
715 connection->bypass = ~0;
720 if (connection->authenticate == TRUE && connection->header->proxy_authorization == NULL) {
721 header = header_new();
722 header->type = HTTP_RESP;
724 header->content_length = 0;
725 header->proxy_authenticate = xstrdup("Basic");
727 header_send(header, connection, CLIENT, HEADER_RESP);
729 http_header_free(header);
732 } else if (connection->header->proxy_authorization != NULL) {
733 ptr = strchr(connection->header->proxy_authorization, ' ');
737 ret = from64tobits(buf, ptr);
740 putlog(MMLOG_DEBUG, "%s", buf);
742 ptr = strchr(buf, ':');
746 ret = access_check(access_list, connection, buf, ++ptr);
748 connection->authenticate = FALSE;
752 if (connection->authenticate == TRUE) {
753 header = header_new();
754 header->type = HTTP_RESP;
756 header->content_length = 0;
757 header->proxy_authenticate = xstrdup("Basic");
759 header_send(header, connection, CLIENT, HEADER_RESP);
761 http_header_free(header);
767 /* redirect the request if any matching rules found (redirect_do will fill in the host, file and port
768 members of the connection struct if any are found) */
769 x = redirect_do(redirect_list, connection, REDIRECT_REQUEST);
771 /* 302 redirect sent, no need to continue */
775 pthread_mutex_lock(&threads[connection->thread].lock);
776 FREE_AND_STRDUP(threads[connection->thread].host, connection->header->host);
777 FREE_AND_STRDUP(threads[connection->thread].file, connection->header->file);
778 FREE_AND_STRDUP(threads[connection->thread].method, connection->header->method);
779 threads[connection->thread].port = connection->header->port;
780 pthread_mutex_unlock(&threads[connection->thread].lock);
782 http_header_list = header_filter(header_list, connection);
783 connection->header->header_filtered = http_header_list;
785 if (connection->header->type != HTTP_CONNECT) {
786 if (connection->header->type != HTTP_REQUEST && strcasecmp(connection->header->proto, "http")) {
787 /* only http protocol is supported */
788 template_send(templates, "badprotocol", connection, 501);
793 if ((connection->header->type != HTTP_REQUEST && !strcasecmp(connection->header->host, INTERFACEURL)) || (connection->header->type == HTTP_REQUEST && !strncasecmp(&connection->header->file[1], INTERFACEURL, strlen(INTERFACEURL)))) {
794 /* request for web interface */
795 putlog(MMLOG_REQUEST, "request for web interface from %s", connection->ip);
797 interface_handle_request(connection);
802 if ((connection->header->type == HTTP_REQUEST && !(connection->access & ACCESS_HTTP)) || (connection->header->type == HTTP_PROXY && !(connection->access & ACCESS_PROXY))) {
803 template_send(templates, "noaccess", connection, 404);
807 } else if (!(connection->access & ACCESS_CONNECT)) {
808 template_send(templates, "noaccess", connection, 404);
813 if (connection->header->type == HTTP_REQUEST && connection->header->host == NULL) {
814 if ((connection->access & ACCESS_TRANSPARENT) && connection->header->host_header != NULL) {
815 /* use Host: header if it's there */
816 ptr = strchr(connection->header->host_header, ':');
817 connection->header->host = xstrndup(connection->header->host_header, (ptr != NULL) ? ptr - connection->header->host_header : strlen(connection->header->host_header));
819 connection->header->port = atoi(&ptr[1]);
821 /* this feature causes a recursion where the proxy keeps
822 connecting to itself if an HTTP request is made to the proxy
823 which doesn't match a redirect rule and isn't a request for the web interface.
824 There's no reliable way to detect this except to forbid connections
825 to websites on the same port as the proxy
827 if (connection->header->port == connection->port) {
828 template_send(templates, "nofile", connection, 404);
833 /* not a request for web interface, no host header, and no matching redirect rule */
834 template_send(templates, "nofile", connection, 404);
839 for (url_command = connection->header->url_command; url_command && *url_command; url_command++) {
840 if (!strcasecmp((*url_command)->command, "filter")) {
841 filter_check_show(connection);
847 filter_match = filter_check(filter_list, connection);
848 if (filter_match != NULL) {
849 putlog(MMLOG_FILTER, "blocked %s%s", connection->header->host, connection->header->file);
851 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);
853 pthread_rwlock_unlock(&filter_list->lock);
857 pthread_rwlock_unlock(&filter_list->lock);
859 if (connection->site_host != NULL && (strcasecmp(connection->header->host, connection->site_host) || connection->header->port != connection->site_port)) {
860 /* not a request for same host/port as previous request */
861 if (connection->server != NULL) {
862 sock_close(connection->server);
863 connection->server = NULL;
867 if (connection->server == NULL) {
868 /* check if this request should be forwarded through another proxy */
869 /* forward_do will fill in all the necessary members of the connection struct */
870 connection->proxy_type = PROXY_DIRECT;
871 forward_do(forward_list, connection);
873 x = protocol_start(connection);
877 FREE_AND_NULL(connection->site_host);
878 connection->site_host = xstrdup(connection->header->host);
879 connection->site_port = connection->header->port;
882 if (connection->header->type != HTTP_CONNECT)
883 x = protocol_http(connection);
885 x = protocol_connect(connection);
888 putlog(MMLOG_HEADER, "error reading header from %s", connection->header->host);
891 sock_flush(connection->client);
893 connection->request++;
895 http_header_free(connection->header);
897 putlog(MMLOG_DEBUG, "keepalive_client = %d", connection->keepalive_client);
899 if (!connection->keepalive_client) {
900 if (connection->server != NULL) {
901 sock_close(connection->server);
902 connection->server = NULL;
906 } else if (!connection->keepalive_server && connection->server != NULL) {
907 sock_close(connection->server);
908 connection->server = NULL;
912 pthread_mutex_lock(&threads[connection->thread].lock);
913 threads[connection->thread].flags = THREAD_CHEADERWAIT;
914 FREE_AND_NULL(threads[connection->thread].host);
915 FREE_AND_NULL(threads[connection->thread].file);
916 FREE_AND_NULL(threads[connection->thread].method);
917 threads[connection->thread].port = 0;
918 threads[connection->thread].requests++;
919 pthread_mutex_unlock(&threads[connection->thread].lock);
921 connection->header = NULL;
922 connection->rheader = NULL;
924 FREE_AND_NULL(connection->proxy_host);
927 if (connection->server != NULL)
928 sock_close(connection->server);
930 putlog(MMLOG_NETWORK, "%s disconnected after making %d requests", connection->ip, connection->request);
932 pthread_mutex_lock(&threads[connection->thread].lock);
933 threads[connection->thread].flags = THREAD_UNUSED;
934 FREE_AND_NULL(threads[connection->thread].ip);
935 FREE_AND_NULL(threads[connection->thread].host);
936 FREE_AND_NULL(threads[connection->thread].file);
937 FREE_AND_NULL(threads[connection->thread].method);
938 pthread_mutex_unlock(&threads[connection->thread].lock);
940 net_close(connection);
946 save all config settings to a file
948 int config_save(char *filename)
951 XML_LIST *xml_list = NULL;
953 /* reconstruct config file for all sections */
954 xml_list = network_xml(network, xml_list);
955 xml_list = templates_xml(templates, xml_list);
956 xml_list = external_xml(external, xml_list);
957 xml_list = access_xml(access_list, xml_list);
958 xml_list = header_xml(header_list, xml_list);
959 xml_list = cookie_xml(cookie_list, xml_list);
960 xml_list = redirect_xml(redirect_list, xml_list);
961 xml_list = keyword_xml(keyword_list, xml_list);
962 xml_list = forward_xml(forward_list, xml_list);
963 xml_list = filter_xml(filter_list, xml_list);
964 xml_list = mime_xml(mime_list, xml_list);
965 xml_list = rewrite_xml(rewrite_list, xml_list);
967 if (xml_list == NULL)
970 while (xml_list->prev != NULL)
971 xml_list = xml_list->prev;
973 ret = xml_save(xml_list, filename);
975 xml_list_free(xml_list);