f188421f52641a9c67caf88b3689d454ae230589
[middleman.git] / src / main.c
1 /*
2     MiddleMan filtering proxy server
3     Copyright (C) 2002  Jason McLaughlin
4
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.
9
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.
14
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
18 */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <signal.h>
27 #include <fcntl.h>
28 #include <pwd.h>
29 #include <grp.h>
30 #include "proto.h"
31
32 #ifdef USE_SYSLOG
33 #include <syslog.h>
34 #endif                          /* USE_SYSLOG */
35
36 int loglevel = 2047;
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;
53
54 int main(int argc, char **argv)
55 {
56         int fpid, x;
57         char pidfile[256] = "";
58         struct stat fileinfo;
59         struct passwd *pwd = NULL;
60         struct group *grp = NULL;
61
62         if (argc < 2) {
63                 show_help(argv);
64                 exit(EXIT_SUCCESS);
65         }
66
67         while ((x = getopt(argc, argv, "hp:c:l:d:u:g:")) != EOF) {
68                 switch (x) {
69                 case 'c':
70                         s_strncpy(configfile, optarg, sizeof(configfile));
71                         break;
72                 case 'l':
73                         s_strncpy(logfile, optarg, sizeof(logfile));
74                         break;
75                 case 'p':
76                         s_strncpy(pidfile, optarg, sizeof(pidfile));
77                         break;
78                 case 'd':
79                         loglevel = atoi(optarg);
80                         break;
81                 case 'u':
82                         s_strncpy(username, optarg, sizeof(username));
83                         break;
84                 case 'g':
85                         s_strncpy(group, optarg, sizeof(group));
86                         break;
87                 case 'h':
88                         show_help(argv);
89                         exit(EXIT_SUCCESS);
90                 }
91         }
92
93         while (--argc > 0)
94                 memset(argv[argc], 0, strlen(argv[argc]));
95
96         if (*configfile) {
97                 if (stat(configfile, &fileinfo) == -1) {
98                         fprintf(stderr, "couldn't stat %s\n", configfile);
99                         exit(EXIT_FAILURE);
100                 }
101         } else {
102                 fprintf(stderr, "config file option missing\n");
103                 exit(EXIT_FAILURE);
104         }
105
106         if (strcmp(group, "")) {
107                 grp = getgrnam(group);
108                 if (grp == NULL) {
109                         fprintf(stderr, "getgrnam: unknown group\n");
110                         exit(EXIT_FAILURE);
111                 }
112
113                 x = setgid(grp->gr_gid);
114                 if (x == -1) {
115                         perror("setgid");
116                         exit(EXIT_FAILURE);
117                 }
118         } else {
119                 grp = getgrgid(getgid());
120                 if (grp == NULL) {
121                         fprintf(stderr, "setgrgid: unknown group");
122                         exit(EXIT_FAILURE);
123                 }
124
125                 s_strncpy(group, grp->gr_name, sizeof(group));
126         }
127
128         if (strcmp(username, "")) {
129                 pwd = getpwnam(username);
130                 if (pwd == NULL) {
131                         fprintf(stderr, "getpwnam: unknown user\n");
132                         exit(EXIT_FAILURE);
133                 }
134
135                 x = setuid(pwd->pw_uid);
136                 if (x == -1) {
137                         perror("setuid");
138                         exit(EXIT_FAILURE);
139                 }
140         } else {
141                 pwd = getpwuid(getuid());
142                 if (pwd == NULL) {
143                         fprintf(stderr, "getpwuid: unknown user");
144                         exit(EXIT_FAILURE);
145                 }
146
147                 s_strncpy(username, pwd->pw_name, sizeof(username));
148         }
149
150         fpid = fork();
151
152         switch (fpid) {
153         case -1:
154                 fprintf(stderr, "failed to fork daemon\n");
155                 exit(EXIT_FAILURE);
156                 break;
157         case 0:
158                 if (*pidfile) {
159                         x = pid_check(pidfile);
160
161                         switch (x) {
162                         case 0:
163                                 break;
164                         case -1:
165                                 exit(EXIT_FAILURE);
166                         default:
167                                 exit(EXIT_FAILURE);
168                         }
169                 }
170
171                 close(STDIN_FILENO);
172                 close(STDOUT_FILENO);
173                 close(STDERR_FILENO);
174
175                 mainloop();
176         default:
177                 setpgid(fpid, fpid);
178         }
179
180         exit(EXIT_SUCCESS);
181 }
182
183 void show_help(char **argv)
184 {
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");
188 #ifndef USE_SYSLOG
189         fprintf(stderr, " -l <file>  : file to log actvity to\n");
190 #endif
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");
209 }
210
211 /*
212 check if pidfile exists and whether or not the pid inside is active, otherwise
213 write current pid.
214 */
215 int pid_check(char *pidfile)
216 {
217         int i = 0, x;
218         FILE *fptr;
219
220         fptr = fopen(pidfile, "r");
221         if (fptr == NULL)
222                 goto makepidfile;
223
224         x = fscanf(fptr, "%d", &i);
225         if (x == 0) {
226                 fclose(fptr);
227                 goto makepidfile;
228         }
229
230         x = kill(i, SIGCHLD);
231         if (x == 0) {
232                 fclose(fptr);
233                 return i;
234         }
235
236       makepidfile:
237         unlink(pidfile);
238         fptr = fopen(pidfile, "w");
239         if (fptr == NULL)
240                 return TRUE;
241
242         fprintf(fptr, "%u\n", (unsigned int) getpid());
243
244         fclose(fptr);
245         return FALSE;
246 }
247
248 /*
249 things that only need to be done once at startup
250 */
251 void config()
252 {
253         int i;
254
255 #ifdef USE_SYSLOG
256         openlog("mman", LOG_PID, LOG_DAEMON);
257 #endif
258
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);
264
265         signal_setup();
266         net_init();
267
268         for (i = 0; i < MAXTHREADS; i++) {
269                 threads[i].flags = THREAD_UNUSED;
270                 pthread_mutex_init(&threads[i].lock, NULL);
271         }
272
273         pcre_free = xfree;
274         pcre_malloc = (void *) xmalloc;
275
276         config_load(3, configfile);
277
278         dns_cache = hash_create(DNS_HASH_SIZE);
279 }
280
281 int config_load(int overwrite, char *file)
282 {
283         XML_LIST *xml_list;
284
285         xml_list = xml_load(NULL, file);
286
287         if (xml_list == NULL)
288                 return FALSE;
289
290         if (access_list != NULL) {
291                 pthread_rwlock_wrlock(&access_list->lock);
292
293                 if (overwrite) {
294                         access_list->id = 0;
295                         access_ll_free(access_list->allow);
296                         access_ll_free(access_list->deny);
297                         access_list->allow = access_list->deny = NULL;
298                 }
299
300                 access_load(access_list, xml_list);
301
302                 pthread_rwlock_unlock(&access_list->lock);
303         } else
304                 access_list = access_load(NULL, xml_list);
305
306         if (filter_list != NULL) {
307                 pthread_rwlock_wrlock(&filter_list->lock);
308
309                 if (overwrite) {
310                         filter_list->id = 0;
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);
315                 }
316
317                 filter_load(filter_list, xml_list);
318
319                 pthread_rwlock_unlock(&filter_list->lock);
320         } else
321                 filter_list = filter_load(NULL, xml_list);
322
323         if (header_list != NULL) {
324                 pthread_rwlock_wrlock(&header_list->lock);
325
326                 if (overwrite) {
327                         header_list->id = 0;
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;
332                 }
333
334                 header_load(header_list, xml_list);
335
336                 pthread_rwlock_unlock(&header_list->lock);
337         } else
338                 header_list = header_load(NULL, xml_list);
339
340         if (cookie_list != NULL) {
341                 pthread_rwlock_wrlock(&cookie_list->lock);
342
343                 if (overwrite) {
344                         cookie_list->id = 0;
345                         cookie_ll_free(cookie_list->allow);
346                         cookie_ll_free(cookie_list->deny);
347                         cookie_list->allow = cookie_list->deny = NULL;
348                 }
349
350                 cookie_load(cookie_list, xml_list);
351
352                 pthread_rwlock_unlock(&cookie_list->lock);
353         } else
354                 cookie_list = cookie_load(NULL, xml_list);
355
356         if (rewrite_list != NULL) {
357                 pthread_rwlock_wrlock(&rewrite_list->lock);
358
359                 if (overwrite) {
360                         rewrite_list->id = 0;
361                         rewrite_list_free(rewrite_list->rewrite);
362                         rewrite_list->rewrite = NULL;
363                 }
364
365                 rewrite_load(rewrite_list, xml_list);
366
367                 pthread_rwlock_unlock(&rewrite_list->lock);
368         } else
369                 rewrite_list = rewrite_load(NULL, xml_list);
370
371         if (mime_list != NULL) {
372                 pthread_rwlock_wrlock(&mime_list->lock);
373
374                 if (overwrite) {
375                         mime_list->id = 0;
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);
380                 }
381
382                 mime_load(mime_list, xml_list);
383
384                 pthread_rwlock_unlock(&mime_list->lock);
385         } else
386                 mime_list = mime_load(NULL, xml_list);
387
388         if (redirect_list != NULL) {
389                 pthread_rwlock_wrlock(&redirect_list->lock);
390
391                 if (overwrite) {
392                         redirect_list->id = 0;
393                         redirect_list_free(redirect_list->redirect_list);
394                         redirect_list->redirect_list = NULL;
395                 }
396
397                 redirect_load(redirect_list, xml_list);
398
399                 pthread_rwlock_unlock(&redirect_list->lock);
400         } else
401                 redirect_list = redirect_load(NULL, xml_list);
402
403         if (keyword_list != NULL) {
404                 pthread_rwlock_wrlock(&keyword_list->lock);
405
406                 if (overwrite) {
407                         keyword_list->id = 0;
408                         keyword_list_free(keyword_list->keyword_list);
409                         keyword_list->keyword_list = NULL;
410                 }
411
412                 keyword_load(keyword_list, xml_list);
413
414                 pthread_rwlock_unlock(&keyword_list->lock);
415         } else
416                 keyword_list = keyword_load(NULL, xml_list);
417
418         if (forward_list != NULL) {
419                 pthread_rwlock_wrlock(&forward_list->lock);
420
421                 if (overwrite) {
422                         forward_list->id = 0;
423                         forward_list_free(forward_list->forward_list);
424                         forward_list->forward_list = NULL;
425                 }
426
427                 forward_load(forward_list, xml_list);
428
429                 pthread_rwlock_unlock(&forward_list->lock);
430         } else
431                 forward_list = forward_load(NULL, xml_list);
432
433         if (templates != NULL) {
434                 pthread_rwlock_wrlock(&templates->lock);
435
436                 if (overwrite) {
437                         templates->id = 0;
438                         templates_list_free(templates->template_list);
439                         templates->template_list = NULL;
440                         FREE_AND_NULL(templates->path);
441                 }
442
443                 templates_load(templates, xml_list);
444
445                 pthread_rwlock_unlock(&templates->lock);
446         } else
447                 templates = templates_load(NULL, xml_list);
448
449         if (external != NULL) {
450                 pthread_rwlock_wrlock(&external->lock);
451
452                 if (overwrite) {
453                         external->id = 0;
454                         external_list_free(external->external_list);
455                         external->external_list = NULL;
456                 }
457
458                 external_load(external, xml_list);
459
460                 pthread_rwlock_unlock(&external->lock);
461         } else
462                 external = external_load(NULL, xml_list);
463
464         /* only do this at startup */
465         if (overwrite == 3) {
466                 if (network != NULL) {
467                         pthread_rwlock_wrlock(&network->lock);
468
469                         network = network_load(network, xml_list);
470                         network_check(network);
471
472                         pthread_rwlock_unlock(&network->lock);
473                 } else {
474                         network = network_load(NULL, xml_list);
475                         network_check(network);
476                 }
477         }
478
479         xml_list_free(xml_list);
480
481         return TRUE;
482 }
483
484 /* 
485 main event loop; accept connections, check access list, then create thread to
486 continue connection.
487 */
488 void mainloop()
489 {
490         int x;
491         CONNECTION *connection;
492
493         config();
494
495         while (1) {
496                 connection = net_accept(-1);
497
498                 if (connection != NULL) {
499                         if (access_check(access_list, connection, NULL, NULL)) {
500                                 x = process_new(connection);
501                                 if (x != 0) {
502                                         putlog(MMLOG_ERROR, "failed to create thread for %s", connection->ip);
503
504                                         net_close(connection);
505                                 }
506                         } else {
507                                 putlog(MMLOG_NETWORK, "refused connect from %s on port %d", connection->ip, connection->port);
508
509                                 net_close(connection);
510                         }
511                 }
512         }
513 }
514
515 /*
516 create new thread
517 */
518 int process_new(CONNECTION * connection)
519 {
520         int perr, thread = -1, i;
521         pthread_t thread_id;
522         pthread_attr_t thread_attr;
523
524         for (i = 0; i < MAXTHREADS && thread == -1; i++) {
525                 pthread_mutex_lock(&threads[i].lock);
526                 if (threads[i].flags & THREAD_UNUSED) {
527                         thread = i;
528
529                         threads[i].flags = THREAD_IDLE;
530                         threads[i].host = NULL;
531                         threads[i].file = NULL;
532                         threads[i].method = NULL;
533                         threads[i].port = 0;
534                         threads[i].requests = 0;
535
536                         threads[i].ip = xstrdup(connection->ip);
537                 }
538                 pthread_mutex_unlock(&threads[i].lock);
539         }
540
541         if (thread == -1)
542                 return -1;
543
544         connection->thread = thread;
545
546         pthread_attr_init(&thread_attr);
547         pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
548         if (getuid() == 0)
549                 pthread_attr_setschedpolicy(&thread_attr, SCHED_FIFO);
550
551         perr = pthread_create(&thread_id, &thread_attr, (void *) process_entry, (void *) connection);
552
553         pthread_attr_destroy(&thread_attr);
554
555         return perr;
556 }
557
558 /*
559 entry function for new threads
560 */
561 void process_entry(CONNECTION * connection)
562 {
563         int x, ret;
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;
568         FILEBUF *filebuf;
569         HEADER *header;
570
571         /* write log message here so the pid matches */
572         putlog(MMLOG_NETWORK, "allowed connect from %s on port %d", connection->ip, connection->port);
573
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);
578
579         while (1) {
580                 /* reset bypass mask to the one provided by the access rule */
581                 connection->bypass = connection->obypass;
582
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);
587
588                                 template_send(templates, "badrequest", connection, 400);
589                         }
590
591                         break;
592                 }
593
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;
599
600                 rewrite_do(rewrite_list, connection, filebuf, REWRITE_CLIENT, TRUE);
601
602                 headbuf = filebuf->data;
603                 filebuf->data = NULL;
604
605                 filebuf_free(filebuf);
606
607                 connection->header = http_header_parse_request(headbuf);
608                 xfree(headbuf);
609
610                 if (connection->header == NULL) {
611                         if (!connection->request) {
612                                 putlog(MMLOG_WARN, "invalid header reveived from %s", connection->ip);
613
614                                 template_send(templates, "badrequest", connection, 400);
615                         }
616
617                         break;
618                 }
619
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;
630                 else
631                         connection->keepalive_client = FALSE;
632
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;
637                                         if (ptr != NULL) {
638                                                 x = TRUE;
639
640                                                 for (; *ptr; ptr++) {
641                                                         switch (*ptr) {
642                                                         case '+':
643                                                                 x = TRUE;
644                                                                 break;
645                                                         case '-':
646                                                                 x = FALSE;
647                                                                 break;
648                                                         case 'f':
649                                                                 if (x == TRUE)
650                                                                         connection->bypass |= FEATURE_FILTER;
651                                                                 else
652                                                                         connection->bypass &= ~FEATURE_FILTER;
653                                                                 break;
654                                                         case 'h':
655                                                                 if (x == TRUE)
656                                                                         connection->bypass |= FEATURE_HEADER;
657                                                                 else
658                                                                         connection->bypass &= ~FEATURE_HEADER;
659                                                                 break;
660                                                         case 'm':
661                                                                 if (x == TRUE)
662                                                                         connection->bypass |= FEATURE_MIME;
663                                                                 else
664                                                                         connection->bypass &= ~FEATURE_MIME;
665                                                                 break;
666                                                         case 'r':
667                                                                 if (x == TRUE)
668                                                                         connection->bypass |= FEATURE_REDIRECT;
669                                                                 else
670                                                                         connection->bypass &= ~FEATURE_REDIRECT;
671                                                                 break;
672                                                         case 'c':
673                                                                 if (x == TRUE)
674                                                                         connection->bypass |= FEATURE_COOKIES;
675                                                                 else
676                                                                         connection->bypass &= ~FEATURE_COOKIES;
677                                                                 break;
678                                                         case 'w':
679                                                                 if (x == TRUE)
680                                                                         connection->bypass |= FEATURE_REWRITE;
681                                                                 else
682                                                                         connection->bypass &= ~FEATURE_REWRITE;
683                                                                 break;
684                                                         case 'e':
685                                                                 if (x == TRUE)
686                                                                         connection->bypass |= FEATURE_EXTERNAL;
687                                                                 else
688                                                                         connection->bypass &= ~FEATURE_EXTERNAL;
689                                                                 break;
690                                                         case 'p':
691                                                                 if (x == TRUE)
692                                                                         connection->bypass |= FEATURE_FORWARD;
693                                                                 else
694                                                                         connection->bypass &= ~FEATURE_FORWARD;
695                                                                 break;
696                                                         case 'k':
697                                                                 if (x == TRUE)
698                                                                         connection->bypass |= FEATURE_KEYWORDS;
699                                                                 else
700                                                                         connection->bypass &= ~FEATURE_KEYWORDS;
701                                                                 break;
702                                                         }
703                                                 }
704                                         } else
705                                                 connection->bypass = ~0;
706                                 }
707                         }
708                 }
709
710                 if (connection->authenticate == TRUE && connection->header->proxy_authorization == NULL) {
711                         header = header_new();
712                         header->type = HTTP_RESP;
713                         header->code = 407;
714                         header->content_length = 0;
715                         header->proxy_authenticate = xstrdup("Basic");
716
717                         header_send(header, connection, CLIENT, HEADER_RESP);
718
719                         http_header_free(header);
720
721                         goto skip;
722                 } else if (connection->header->proxy_authorization != NULL) {
723                         ptr = strchr(connection->header->proxy_authorization, ' ');
724                         if (ptr != NULL) {
725                                 ptr++;
726
727                                 ret = from64tobits(buf, ptr);
728                                 buf[ret] = '\0';
729
730                                 putlog(MMLOG_DEBUG, "%s", buf);
731
732                                 ptr = strchr(buf, ':');
733                                 if (ptr != NULL) {
734                                         *ptr = '\0';
735
736                                         ret = access_check(access_list, connection, buf, ++ptr);
737                                         if (ret)
738                                                 connection->authenticate = FALSE;
739                                 }
740                         }
741
742                         if (connection->authenticate == TRUE) {
743                                 header = header_new();
744                                 header->type = HTTP_RESP;
745                                 header->code = 407;
746                                 header->content_length = 0;
747                                 header->proxy_authenticate = xstrdup("Basic");
748
749                                 header_send(header, connection, CLIENT, HEADER_RESP);
750
751                                 http_header_free(header);
752
753                                 goto skip;
754                         }
755                 }
756
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);
760                 if (x == TRUE) {
761                         /* 302 redirect sent, no need to continue */
762                         goto skip;
763                 }
764
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);
771
772                 http_header_list = header_filter(header_list, connection);
773                 connection->header->header_filtered = http_header_list;
774
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);
779
780                                 goto skip;
781                         }
782
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);
786
787                                 interface_handle_request(connection);
788
789                                 goto skip;
790                         }
791
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);
794
795                                 goto skip;
796                         }
797                 } else if (!(connection->access & ACCESS_CONNECT)) {
798                         template_send(templates, "noaccess", connection, 404);
799
800                         goto skip;
801                 }
802
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));
808                                 if (ptr != NULL)
809                                         connection->header->port = atoi(&ptr[1]);
810
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
816                                  */
817                                 if (connection->header->port == connection->port) {
818                                         template_send(templates, "nofile", connection, 404);
819
820                                         goto skip;
821                                 }
822                         } else {
823                                 /* not a request for web interface, no host header, and no matching redirect rule */
824                                 template_send(templates, "nofile", connection, 404);
825                                 goto skip;
826                         }
827                 }
828
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);
832
833                                 goto skip;
834                         }
835                 }
836
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);
840
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);
842
843                         pthread_rwlock_unlock(&filter_list->lock);
844
845                         goto skip;
846                 }
847                 pthread_rwlock_unlock(&filter_list->lock);
848
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;
854                         }
855                 }
856
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);
862
863                         x = protocol_start(connection);
864                         if (x == -1)
865                                 goto skip;
866
867                         FREE_AND_NULL(connection->site_host);
868                         connection->site_host = xstrdup(connection->header->host);
869                         connection->site_port = connection->header->port;
870                 }
871
872                 if (connection->header->type != HTTP_CONNECT)
873                         x = protocol_http(connection);
874                 else
875                         x = protocol_connect(connection);
876
877                 if (x < 0)
878                         putlog(MMLOG_HEADER, "error reading header from %s", connection->header->host);
879
880               skip:
881                 sock_flush(connection->client);
882
883                 connection->request++;
884
885                 http_header_free(connection->header);
886
887                 putlog(MMLOG_DEBUG, "keepalive_client = %d", connection->keepalive_client);
888
889                 if (!connection->keepalive_client) {
890                         if (connection->server != NULL) {
891                                 sock_close(connection->server);
892                                 connection->server = NULL;
893                         }
894
895                         break;
896                 } else if (!connection->keepalive_server && connection->server != NULL) {
897                         sock_close(connection->server);
898                         connection->server = NULL;
899                 }
900
901
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);
910
911                 connection->header = NULL;
912                 connection->rheader = NULL;
913
914                 FREE_AND_NULL(connection->proxy_host);
915         }
916
917         if (connection->server != NULL)
918                 sock_close(connection->server);
919
920         putlog(MMLOG_NETWORK, "%s disconnected after making %d requests", connection->ip, connection->request);
921
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);
929
930         net_close(connection);
931
932         return;
933 }
934
935 /*
936 save all config settings to a file
937 */
938 int config_save(char *filename)
939 {
940         int ret;
941         XML_LIST *xml_list = NULL;
942
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);
956
957         if (xml_list == NULL)
958                 return FALSE;
959
960         while (xml_list->prev != NULL)
961                 xml_list = xml_list->prev;
962
963         ret = xml_save(xml_list, filename);
964
965         xml_list_free(xml_list);
966
967         return ret;
968 }