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