:pserver:anonymous@cvs.middle-man.sourceforge.net:/cvsroot/middle-man middleman
[middleman.git] / src / external.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 <string.h>
22 #include <stdlib.h>
23 #include <ctype.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 #include "proto.h"
29
30 /*
31 load <external> section from XML config into a linked list
32 */
33 EXTERNAL *external_load(EXTERNAL * external, XML_LIST * xml_list)
34 {
35         EXTERNAL *tmp_list = external;
36         struct EXTERNAL_LIST_LIST *external_list = NULL;
37
38         if (tmp_list == NULL) {
39                 tmp_list = xmalloc(sizeof(EXTERNAL));
40                 tmp_list->external_list = NULL;
41                 tmp_list->id = 0;
42                 tmp_list->enabled = TRUE;
43
44                 external = tmp_list;
45
46                 pthread_rwlock_init(&tmp_list->lock, NULL);
47         } else
48                 external_list = tmp_list->external_list;
49
50         while ((xml_list = xml_section(xml_list, "<external>"))) {
51                 XML_LIST_LOOP(xml_list, "<external>") {
52                         XML_LIST_CMP(xml_list, "<item>") {
53                                 external_list = external_list_new(external_list);
54                                 external_list->id = external->id++;
55
56                                 if (tmp_list->external_list == NULL)
57                                         tmp_list->external_list = external_list;
58                                 XML_LIST_LOOP(xml_list, "<item>") {
59                                         XML_LIST_CMP(xml_list, "<enabled>") {
60                                                 xml_list = xml_list->next;
61                                                 if (xml_list->type == XML_VALUE) {
62                                                         if (!strcasecmp(xml_list->item, "false"))
63                                                                 external_list->enabled = FALSE;
64                                                         else
65                                                                 external_list->enabled = TRUE;
66                                                 }
67                                         }
68                                         XML_LIST_CMP(xml_list, "<comment>") {
69                                                 xml_list = xml_list->next;
70                                                 if (xml_list->type == XML_VALUE)
71                                                         external_list_insert(external_list, xml_list->item, NULL, NULL, NULL, NULL, NULL, NULL);
72                                         }
73                                         XML_LIST_CMP(xml_list, "<mime>") {
74                                                 xml_list = xml_list->next;
75                                                 if (xml_list->type == XML_VALUE)
76                                                         external_list_insert(external_list, NULL, xml_list->item, NULL, NULL, NULL, NULL, NULL);
77                                         }
78                                         XML_LIST_CMP(xml_list, "<host>") {
79                                                 xml_list = xml_list->next;
80                                                 if (xml_list->type == XML_VALUE)
81                                                         external_list_insert(external_list, NULL, NULL, xml_list->item, NULL, NULL, NULL, NULL);
82                                         }
83                                         XML_LIST_CMP(xml_list, "<file>") {
84                                                 xml_list = xml_list->next;
85                                                 if (xml_list->type == XML_VALUE)
86                                                         external_list_insert(external_list, NULL, NULL, NULL, xml_list->item, NULL, NULL, NULL);
87                                         }
88                                         XML_LIST_CMP(xml_list, "<exec>") {
89                                                 xml_list = xml_list->next;
90                                                 if (xml_list->type == XML_VALUE)
91                                                         external_list_insert(external_list, NULL, NULL, NULL, NULL, xml_list->item, NULL, NULL);
92                                         }
93                                         XML_LIST_CMP(xml_list, "<newmime>") {
94                                                 xml_list = xml_list->next;
95                                                 if (xml_list->type == XML_VALUE)
96                                                         external_list_insert(external_list, NULL, NULL, NULL, NULL, NULL, xml_list->item, NULL);
97                                         }
98                                         XML_LIST_CMP(xml_list, "<type>") {
99                                                 xml_list = xml_list->next;
100                                                 if (xml_list->type == XML_VALUE)
101                                                         external_list_insert(external_list, NULL, NULL, NULL, NULL, NULL, NULL, xml_list->item);
102                                         }
103                                 }
104                         }
105                         XML_LIST_CMP(xml_list, "<enabled>") {
106                                 xml_list = xml_list->next;
107                                 if (xml_list->type == XML_VALUE) {
108                                         if (!strcasecmp(xml_list->item, "false"))
109                                                 tmp_list->enabled = FALSE;
110                                         else
111                                                 tmp_list->enabled = TRUE;
112                                 }
113                         }
114                 }
115         }
116
117         return external;
118 }
119
120 XML_LIST *external_xml(EXTERNAL * external, XML_LIST * xml_list)
121 {
122         char *ptr;
123         struct EXTERNAL_LIST_LIST *el;
124
125         if (external == NULL)
126                 return xml_list;
127
128         pthread_rwlock_rdlock(&external->lock);
129
130         xml_list = xml_list_add(xml_list, "<external>", XML_TAG);
131
132         xml_list = xml_list_add(xml_list, "<enabled>", XML_TAG);
133         xml_list = xml_list_add(xml_list, (external->enabled == TRUE) ? "true" : "false", XML_VALUE);
134         xml_list = xml_list_add(xml_list, "</enabled>", XML_TAG);
135
136         for (el = external->external_list; el; el = el->next) {
137                 xml_list = xml_list_add(xml_list, "<item>", XML_TAG);
138
139                 xml_list = xml_list_add(xml_list, "<enabled>", XML_TAG);
140                 xml_list = xml_list_add(xml_list, (el->enabled == TRUE) ? "true" : "false", XML_VALUE);
141                 xml_list = xml_list_add(xml_list, "</enabled>", XML_TAG);
142
143                 if (el->comment != NULL) {
144                         xml_list = xml_list_add(xml_list, "<comment>", XML_TAG);
145                         ptr = string_to_xml(el->comment);
146                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
147                         xfree(ptr);
148                         xml_list = xml_list_add(xml_list, "</comment>", XML_TAG);
149                 }
150
151                 if (el->host != NULL) {
152                         xml_list = xml_list_add(xml_list, "<host>", XML_TAG);
153                         ptr = string_to_xml(el->host);
154                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
155                         xfree(ptr);
156                         xml_list = xml_list_add(xml_list, "</host>", XML_TAG);
157                 }
158
159                 if (el->file != NULL) {
160                         xml_list = xml_list_add(xml_list, "<file>", XML_TAG);
161                         ptr = string_to_xml(el->file);
162                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
163                         xfree(ptr);
164                         xml_list = xml_list_add(xml_list, "</file>", XML_TAG);
165                 }
166
167                 if (el->mime != NULL) {
168                         xml_list = xml_list_add(xml_list, "<mime>", XML_TAG);
169                         ptr = string_to_xml(el->mime);
170                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
171                         xfree(ptr);
172                         xml_list = xml_list_add(xml_list, "</mime>", XML_TAG);
173                 }
174
175                 if (el->newmime != NULL) {
176                         xml_list = xml_list_add(xml_list, "<newmime>", XML_TAG);
177                         ptr = string_to_xml(el->newmime);
178                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
179                         xfree(ptr);
180                         xml_list = xml_list_add(xml_list, "</newmime>", XML_TAG);
181                 }
182
183                 if (el->exec != NULL) {
184                         xml_list = xml_list_add(xml_list, "<exec>", XML_TAG);
185                         ptr = string_to_xml(el->exec);
186                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
187                         xfree(ptr);
188                         xml_list = xml_list_add(xml_list, "</exec>", XML_TAG);
189                 }
190
191                 xml_list = xml_list_add(xml_list, "<type>", XML_TAG);
192                 xml_list = xml_list_add(xml_list, (el->type == EXTERNAL_FILE) ? "file" : "pipe", XML_VALUE);
193                 xml_list = xml_list_add(xml_list, "</type>", XML_TAG);
194
195                 xml_list = xml_list_add(xml_list, "</item>", XML_TAG);
196         }
197
198         xml_list = xml_list_add(xml_list, "</external>", XML_TAG);
199
200         pthread_rwlock_unlock(&external->lock);
201
202         return xml_list;
203 }
204
205 void external_list_insert(struct EXTERNAL_LIST_LIST *x, char *a, char *b, char *c, char *d, char *e, char *f, char *g)
206 {
207         if (a != NULL) {
208                 FREE_AND_NULL(x->comment);
209
210                 if (strcmp(a, ""))
211                         x->comment = xstrdup(a);
212         }
213         if (b != NULL) {
214                 if (x->me != NULL)
215                         reg_free(x->me);
216                 FREE_AND_NULL(x->mime);
217
218                 if (strcmp(b, "")) {
219                         x->mime = xstrdup(b);
220                         x->me = reg_compile(b, REGFLAGS);
221                 } else
222                         x->me = NULL;
223         }
224         if (c != NULL) {
225                 if (x->he != NULL)
226                         reg_free(x->he);
227                 FREE_AND_NULL(x->host);
228
229                 if (strcmp(c, "")) {
230                         x->host = xstrdup(c);
231                         x->he = reg_compile(c, REGFLAGS);
232                 } else
233                         x->he = NULL;
234         }
235         if (d != NULL) {
236                 if (x->fe != NULL)
237                         reg_free(x->fe);
238                 FREE_AND_NULL(x->file);
239
240                 if (strcmp(d, "")) {
241                         x->file = xstrdup(d);
242                         x->fe = reg_compile(d, REGFLAGS);
243                 } else
244                         x->fe = NULL;
245         }
246         if (e != NULL) {
247                 FREE_AND_NULL(x->exec);
248
249                 if (strcmp(e, ""))
250                         x->exec = xstrdup(e);
251         }
252         if (f != NULL) {
253                 FREE_AND_NULL(x->newmime);
254
255                 if (strcmp(f, ""))
256                         x->newmime = xstrdup(f);
257         }
258         if (g != NULL) {
259                 if (!strcasecmp(g, "file"))
260                         x->type = EXTERNAL_FILE;
261                 else
262                         x->type = EXTERNAL_PIPE;
263         }
264 }
265
266 struct EXTERNAL_LIST_LIST *external_list_new(struct EXTERNAL_LIST_LIST *x)
267 {
268         if (x == NULL) {
269                 x = xmalloc(sizeof(struct EXTERNAL_LIST_LIST));
270                 x->prev = NULL;
271         } else {
272                 while (x->next != NULL)
273                         x = x->next;
274                 x->next = xmalloc(sizeof(struct EXTERNAL_LIST_LIST));
275                 x->next->prev = x;
276                 x = x->next;
277         }
278         x->enabled = TRUE;
279         x->comment = NULL;
280         x->me = NULL;
281         x->he = NULL;
282         x->fe = NULL;
283         x->mime = NULL;
284         x->host = NULL;
285         x->file = NULL;
286         x->exec = NULL;
287         x->newmime = NULL;
288         x->type = EXTERNAL_PIPE;
289         x->next = NULL;
290
291         return x;
292 }
293
294 struct EXTERNAL_LIST_LIST *external_list_delete(struct EXTERNAL_LIST_LIST *x)
295 {
296         struct EXTERNAL_LIST_LIST *start = x;
297
298         while (start->prev != NULL)
299                 start = start->prev;
300
301         if (x->next != NULL)
302                 x->next->prev = x->prev;
303         if (x->prev != NULL)
304                 x->prev->next = x->next;
305         else
306                 start = start->next;
307
308         if (x->he != NULL)
309                 reg_free(x->he);
310         if (x->fe != NULL)
311                 reg_free(x->fe);
312         if (x->me != NULL)
313                 reg_free(x->me);
314         FREE_AND_NULL(x->comment);
315         FREE_AND_NULL(x->mime);
316         FREE_AND_NULL(x->host);
317         FREE_AND_NULL(x->file);
318         FREE_AND_NULL(x->exec);
319         FREE_AND_NULL(x->newmime);
320
321         xfree(x);
322
323         return start;
324 }
325
326 /*
327 free memory used by EXTERNAL-type data structure
328 */
329 void external_free(EXTERNAL * external)
330 {
331         if (!external)
332                 return;
333         external_list_free(external->external_list);
334
335         pthread_rwlock_destroy(&external->lock);
336
337         xfree(external);
338 }
339
340 void external_list_free(struct EXTERNAL_LIST_LIST *el)
341 {
342         struct EXTERNAL_LIST_LIST *tmp;
343
344         while (el != NULL) {
345                 tmp = el->next;
346
347                 if (el->me != NULL)
348                         reg_free(el->me);
349                 if (el->he != NULL)
350                         reg_free(el->he);
351                 if (el->fe != NULL)
352                         reg_free(el->fe);
353                 FREE_AND_NULL(el->comment);
354                 FREE_AND_NULL(el->mime);
355                 FREE_AND_NULL(el->host);
356                 FREE_AND_NULL(el->file);
357                 FREE_AND_NULL(el->exec);
358                 FREE_AND_NULL(el->newmime);
359
360                 xfree(el);
361                 el = tmp;
362         }
363 }
364
365
366 /*
367 return pointer to EXTERNAL list node matching the connection, if found
368 */
369 struct EXTERNAL_LIST_LIST *external_find(EXTERNAL * external, CONNECTION * connection)
370 {
371         int ret;
372         struct EXTERNAL_LIST_LIST *tmp_list;
373
374         pthread_rwlock_rdlock(&external->lock);
375
376         if (external->enabled == FALSE || connection->bypass & FEATURE_EXTERNAL)
377                 return NULL;
378
379         tmp_list = external->external_list;
380
381         for (; tmp_list != NULL; tmp_list = tmp_list->next) {
382                 if (tmp_list->enabled == FALSE)
383                         continue;
384
385                 if (tmp_list->me != NULL && connection->rheader->content_type != NULL) {
386                         ret = reg_exec(tmp_list->me, connection->rheader->content_type);
387                         if (ret)
388                                 continue;
389                 } else if (tmp_list->me != NULL) {
390                         ret = reg_exec(tmp_list->me, "");
391                         if (ret) continue;
392                 }
393
394                 if (tmp_list->he != NULL) {
395                         ret = reg_exec(tmp_list->he, connection->header->host);
396                         if (ret)
397                                 continue;
398                 }
399
400                 if (tmp_list->fe != NULL) {
401                         ret = reg_exec(tmp_list->fe, connection->header->file);
402                         if (ret)
403                                 continue;
404                 }
405
406                 return tmp_list;
407         }
408
409         return NULL;
410 }
411
412 /*
413 execute an external program, feed stdin the contents of filebuf if available, and
414 place program's output into a filebuf then return
415 */
416 FILEBUF *external_exec(CONNECTION * connection, char *exe, FILEBUF * filebuf, int flags)
417 {
418         int i = 0, x, ret, pid, ipipe[2], opipe[2], fd;
419         char buf[8096], *ptr, tmpfile[64], **args;
420         FILEBUF *ret_filebuf;
421         struct HTTP_HEADER_LIST *header_list = NULL;
422         struct pollfd pfd[2];
423
424         putlog(MMLOG_DEBUG, "executing %s", exe);
425
426         ret = pipe(ipipe);
427         if (ret == -1)
428                 return NULL;
429
430         ret = pipe(opipe);
431         if (ret == -1)
432                 goto error1;
433
434
435         if (flags == EXTERNAL_FILE) {
436                 s_strncpy(tmpfile, TEMPFILE, sizeof(tmpfile));
437
438                 fd = mkstemp(tmpfile);
439                 if (fd == -1)
440                         goto error2;
441
442                 ret = 0;
443
444                 while (ret != filebuf->size) {
445                         x = write(fd, &filebuf->data[ret], (filebuf->size - ret < BLOCKSIZE) ? filebuf->size - ret : BLOCKSIZE);
446                         if (x == -1)
447                                 break;
448
449                         ret += x;
450                 }
451
452                 close(fd);
453         }
454
455         pid = fork();
456         if (pid == -1)
457                 goto error2;
458
459         if (pid != 0) {
460                 close(ipipe[1]);
461                 close(opipe[0]);
462
463                 ret_filebuf = filebuf_new();
464
465                 pfd[0].fd = ipipe[0];
466                 pfd[0].events = POLLIN;
467
468                 if (flags == EXTERNAL_PIPE && filebuf != NULL) {
469                         pfd[1].fd = opipe[1];
470                         pfd[1].events = POLLOUT;
471                 } else {
472                         close(opipe[1]);
473                         pfd[1].events = 0;
474                         pfd[1].fd = -1;
475                 }
476
477                 fcntl(ipipe[0], F_SETFL, O_NONBLOCK);
478                 fcntl(opipe[1], F_SETFL, O_NONBLOCK);
479
480                 while (1) {
481                         ret = p_poll(pfd, 2, -1);
482
483                         if (ret > 0) {
484                                 if (pfd[0].revents & POLLIN) {
485                                         ret = read(ipipe[0], buf, sizeof(buf));
486                                         if (ret > 0)
487                                                 filebuf_add(ret_filebuf, buf, ret);
488                                         else
489                                                 goto cleanup;
490                                 } else if (pfd[0].revents & (POLLHUP | POLLERR))
491                                         goto cleanup;
492
493                                 if (pfd[1].revents & POLLOUT) {
494                                         ret = write(opipe[1], &filebuf->data[i], (filebuf->size - i < 8096) ? filebuf->size - i : 8096);
495                                         if (ret > 0)
496                                                 i += ret;
497                                         else {
498                                                 close(opipe[1]);
499
500                                                 goto cleanup;
501                                         }
502
503                                         if (i == filebuf->size) {
504                                                 close(opipe[1]);
505
506                                                 pfd[1].fd = -1;
507                                                 pfd[1].events = 0;
508                                         }
509                                 } else if (pfd[1].revents & (POLLHUP | POLLERR)) {
510                                         close(opipe[1]);
511
512                                         goto cleanup;
513                                 }
514
515                         }
516                 }
517         } else {
518                 close(opipe[1]);
519                 close(ipipe[0]);
520
521                 args = string_break(exe, ' ');
522                 if (flags == EXTERNAL_FILE) {
523                         for (i = 0; args[i]; i++);
524
525                         args = xrealloc(args, sizeof(char *) * (i + 2));
526                         args[i] = tmpfile;
527                         args[i + 1] = NULL;
528                 }
529
530                 p_clearenv();
531
532                 if (connection->header != NULL) {
533                         p_setenv("HTTP_FILE", connection->header->file, 1);
534                         if (connection->header->host != NULL)
535                                 p_setenv("HTTP_HOST", connection->header->host, 1);
536                         p_setenv("HTTP_METHOD", connection->header->method, 1);
537                         snprintf(buf, sizeof(buf), "%d", connection->header->port);
538                         p_setenv("HTTP_PORT", buf, 1);
539
540                         if (connection->header != NULL)
541                                 header_list = connection->header->header;
542                         while (header_list != NULL) {
543                                 ptr = env_header_format("CLIENT_", header_list->type);
544
545                                 p_setenv(ptr, header_list->value, 1);
546
547                                 xfree(ptr);
548
549                                 header_list = header_list->next;
550                         }
551
552                         if (connection->rheader != NULL)
553                                 header_list = connection->rheader->header;
554                         while (header_list != NULL) {
555                                 ptr = env_header_format("SERVER_", header_list->type);
556
557                                 p_setenv(ptr, header_list->value, 1);
558
559                                 xfree(ptr);
560
561                                 header_list = header_list->next;
562                         }
563                 }
564
565                 p_setenv("IP", connection->ip, 1);
566
567                 dup2(opipe[0], STDIN_FILENO);
568                 dup2(ipipe[1], STDOUT_FILENO);
569
570                 execvp(args[0], args);
571
572                 close(opipe[0]);
573                 close(ipipe[1]);
574
575                 exit(EXIT_FAILURE);
576         }
577
578       error2:
579         close(opipe[0]);
580         close(opipe[1]);
581
582       error1:
583         close(ipipe[0]);
584         close(ipipe[1]);
585
586         return NULL;
587
588       cleanup:
589
590         close(ipipe[0]);
591         waitpid(pid, &ret, 0);
592
593         if (!WIFEXITED(ret) || WEXITSTATUS(ret)) {
594                 /* return original content if process exits with an error */
595                 filebuf_free(ret_filebuf);
596
597                 ret_filebuf = NULL;
598         }
599
600         if (flags == EXTERNAL_FILE)
601                 unlink(tmpfile);
602
603         return ret_filebuf;
604 }
605
606 /*
607 extract Content-Type header from a filebuf and remove it
608 */
609 char *external_getmime(FILEBUF * filebuf)
610 {
611         int len;
612         char *ptr, *ret;
613
614         if (filebuf->size < 14 || strncasecmp(filebuf->data, "Content-type: ", 14))
615                 return NULL;
616
617         ptr = memchr(filebuf->data, '\n', filebuf->size);
618         if (ptr == NULL)
619                 return NULL;
620
621         len = ptr - &filebuf->data[14] - ((*(ptr - 1) == '\r') ? 1 : 0);
622
623         ret = xmalloc(len + 1);
624
625         s_strncpy(ret, &filebuf->data[14], len);
626
627         /* assume \r\n for second newline if first is \r\n */
628         len += 14 + ((filebuf->data[14 + len] == '\r') ? 4 : 2);
629
630         /* len can be lower if nothing but the header was sent */
631         if (len <= filebuf->size) {
632                 memcpy(filebuf->data, &filebuf->data[len], filebuf->size - len);
633                 filebuf_resize(filebuf, filebuf->size - len);
634         } else {
635                 FREE_AND_NULL(filebuf->data);
636                 filebuf->size = 0;
637         }
638
639         putlog(MMLOG_DEBUG, "external_getmime: %s", ret);
640
641         return ret;
642 }
643
644 /*
645 make an http header suitable as an environment variable
646 */
647 char *env_header_format(char *prefix, char *type)
648 {
649         int j = 0, i, x;
650         char *ret;
651
652         i = strlen(type);
653         if (prefix != NULL)
654                 j = strlen(prefix);
655
656         ret = xmalloc(i + j + 1);
657
658         x = i + j;
659
660         ret[x] = '\0';
661         if (j > 0)
662                 memcpy(ret, prefix, j);
663
664         for (; i >= 0; x--, i--) {
665                 if (type[i] == '-')
666                         ret[x] = '_';
667                 else
668                         ret[x] = toupper(type[i]);
669         }
670
671         return ret;
672 }