/* MiddleMan filtering proxy server Copyright (C) 2002 Jason McLaughlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include "proto.h" /* load section from XML config into a linked list */ EXTERNAL *external_load(EXTERNAL * external, XML_LIST * xml_list) { EXTERNAL *tmp_list = external; struct EXTERNAL_LIST_LIST *external_list = NULL; if (tmp_list == NULL) { tmp_list = xmalloc(sizeof(EXTERNAL)); tmp_list->external_list = NULL; tmp_list->id = 0; tmp_list->enabled = TRUE; external = tmp_list; pthread_rwlock_init(&tmp_list->lock, NULL); } else external_list = tmp_list->external_list; while ((xml_list = xml_section(xml_list, ""))) { XML_LIST_LOOP(xml_list, "") { XML_LIST_CMP(xml_list, "") { external_list = external_list_new(external_list); external_list->id = external->id++; if (tmp_list->external_list == NULL) tmp_list->external_list = external_list; XML_LIST_LOOP(xml_list, "") { XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) { if (!strcasecmp(xml_list->item, "false")) external_list->enabled = FALSE; else external_list->enabled = TRUE; } } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) external_list_insert(external_list, xml_list->item, NULL, NULL, NULL, NULL, NULL, NULL); } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) external_list_insert(external_list, NULL, xml_list->item, NULL, NULL, NULL, NULL, NULL); } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) external_list_insert(external_list, NULL, NULL, xml_list->item, NULL, NULL, NULL, NULL); } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) external_list_insert(external_list, NULL, NULL, NULL, xml_list->item, NULL, NULL, NULL); } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) external_list_insert(external_list, NULL, NULL, NULL, NULL, xml_list->item, NULL, NULL); } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) external_list_insert(external_list, NULL, NULL, NULL, NULL, NULL, xml_list->item, NULL); } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) external_list_insert(external_list, NULL, NULL, NULL, NULL, NULL, NULL, xml_list->item); } } } XML_LIST_CMP(xml_list, "") { xml_list = xml_list->next; if (xml_list->type == XML_VALUE) { if (!strcasecmp(xml_list->item, "false")) tmp_list->enabled = FALSE; else tmp_list->enabled = TRUE; } } } } return external; } XML_LIST *external_xml(EXTERNAL * external, XML_LIST * xml_list) { char *ptr; struct EXTERNAL_LIST_LIST *el; if (external == NULL) return xml_list; pthread_rwlock_rdlock(&external->lock); xml_list = xml_list_add(xml_list, "", XML_TAG); xml_list = xml_list_add(xml_list, "", XML_TAG); xml_list = xml_list_add(xml_list, (external->enabled == TRUE) ? "true" : "false", XML_VALUE); xml_list = xml_list_add(xml_list, "", XML_TAG); for (el = external->external_list; el; el = el->next) { xml_list = xml_list_add(xml_list, "", XML_TAG); xml_list = xml_list_add(xml_list, "", XML_TAG); xml_list = xml_list_add(xml_list, (el->enabled == TRUE) ? "true" : "false", XML_VALUE); xml_list = xml_list_add(xml_list, "", XML_TAG); if (el->comment != NULL) { xml_list = xml_list_add(xml_list, "", XML_TAG); ptr = string_to_xml(el->comment); xml_list = xml_list_add(xml_list, ptr, XML_VALUE); xfree(ptr); xml_list = xml_list_add(xml_list, "", XML_TAG); } if (el->host != NULL) { xml_list = xml_list_add(xml_list, "", XML_TAG); ptr = string_to_xml(el->host); xml_list = xml_list_add(xml_list, ptr, XML_VALUE); xfree(ptr); xml_list = xml_list_add(xml_list, "", XML_TAG); } if (el->file != NULL) { xml_list = xml_list_add(xml_list, "", XML_TAG); ptr = string_to_xml(el->file); xml_list = xml_list_add(xml_list, ptr, XML_VALUE); xfree(ptr); xml_list = xml_list_add(xml_list, "", XML_TAG); } if (el->mime != NULL) { xml_list = xml_list_add(xml_list, "", XML_TAG); ptr = string_to_xml(el->mime); xml_list = xml_list_add(xml_list, ptr, XML_VALUE); xfree(ptr); xml_list = xml_list_add(xml_list, "", XML_TAG); } if (el->newmime != NULL) { xml_list = xml_list_add(xml_list, "", XML_TAG); ptr = string_to_xml(el->newmime); xml_list = xml_list_add(xml_list, ptr, XML_VALUE); xfree(ptr); xml_list = xml_list_add(xml_list, "", XML_TAG); } if (el->exec != NULL) { xml_list = xml_list_add(xml_list, "", XML_TAG); ptr = string_to_xml(el->exec); xml_list = xml_list_add(xml_list, ptr, XML_VALUE); xfree(ptr); xml_list = xml_list_add(xml_list, "", XML_TAG); } xml_list = xml_list_add(xml_list, "", XML_TAG); xml_list = xml_list_add(xml_list, (el->type == EXTERNAL_FILE) ? "file" : "pipe", XML_VALUE); xml_list = xml_list_add(xml_list, "", XML_TAG); xml_list = xml_list_add(xml_list, "", XML_TAG); } xml_list = xml_list_add(xml_list, "", XML_TAG); pthread_rwlock_unlock(&external->lock); return xml_list; } void external_list_insert(struct EXTERNAL_LIST_LIST *x, char *a, char *b, char *c, char *d, char *e, char *f, char *g) { if (a != NULL) { FREE_AND_NULL(x->comment); if (strcmp(a, "")) x->comment = xstrdup(a); } if (b != NULL) { if (x->me != NULL) reg_free(x->me); FREE_AND_NULL(x->mime); if (strcmp(b, "")) { x->mime = xstrdup(b); x->me = reg_compile(b, REGFLAGS); } else x->me = NULL; } if (c != NULL) { if (x->he != NULL) reg_free(x->he); FREE_AND_NULL(x->host); if (strcmp(c, "")) { x->host = xstrdup(c); x->he = reg_compile(c, REGFLAGS); } else x->he = NULL; } if (d != NULL) { if (x->fe != NULL) reg_free(x->fe); FREE_AND_NULL(x->file); if (strcmp(d, "")) { x->file = xstrdup(d); x->fe = reg_compile(d, REGFLAGS); } else x->fe = NULL; } if (e != NULL) { FREE_AND_NULL(x->exec); if (strcmp(e, "")) x->exec = xstrdup(e); } if (f != NULL) { FREE_AND_NULL(x->newmime); if (strcmp(f, "")) x->newmime = xstrdup(f); } if (g != NULL) { if (!strcasecmp(g, "file")) x->type = EXTERNAL_FILE; else x->type = EXTERNAL_PIPE; } } struct EXTERNAL_LIST_LIST *external_list_new(struct EXTERNAL_LIST_LIST *x) { if (x == NULL) { x = xmalloc(sizeof(struct EXTERNAL_LIST_LIST)); x->prev = NULL; } else { while (x->next != NULL) x = x->next; x->next = xmalloc(sizeof(struct EXTERNAL_LIST_LIST)); x->next->prev = x; x = x->next; } x->enabled = TRUE; x->comment = NULL; x->me = NULL; x->he = NULL; x->fe = NULL; x->mime = NULL; x->host = NULL; x->file = NULL; x->exec = NULL; x->newmime = NULL; x->type = EXTERNAL_PIPE; x->next = NULL; return x; } struct EXTERNAL_LIST_LIST *external_list_delete(struct EXTERNAL_LIST_LIST *x) { struct EXTERNAL_LIST_LIST *start = x; while (start->prev != NULL) start = start->prev; if (x->next != NULL) x->next->prev = x->prev; if (x->prev != NULL) x->prev->next = x->next; else start = start->next; if (x->he != NULL) reg_free(x->he); if (x->fe != NULL) reg_free(x->fe); if (x->me != NULL) reg_free(x->me); FREE_AND_NULL(x->comment); FREE_AND_NULL(x->mime); FREE_AND_NULL(x->host); FREE_AND_NULL(x->file); FREE_AND_NULL(x->exec); FREE_AND_NULL(x->newmime); xfree(x); return start; } /* free memory used by EXTERNAL-type data structure */ void external_free(EXTERNAL * external) { if (!external) return; external_list_free(external->external_list); pthread_rwlock_destroy(&external->lock); xfree(external); } void external_list_free(struct EXTERNAL_LIST_LIST *el) { struct EXTERNAL_LIST_LIST *tmp; while (el != NULL) { tmp = el->next; if (el->me != NULL) reg_free(el->me); if (el->he != NULL) reg_free(el->he); if (el->fe != NULL) reg_free(el->fe); FREE_AND_NULL(el->comment); FREE_AND_NULL(el->mime); FREE_AND_NULL(el->host); FREE_AND_NULL(el->file); FREE_AND_NULL(el->exec); FREE_AND_NULL(el->newmime); xfree(el); el = tmp; } } /* return pointer to EXTERNAL list node matching the connection, if found */ struct EXTERNAL_LIST_LIST *external_find(EXTERNAL * external, CONNECTION * connection) { int ret; struct EXTERNAL_LIST_LIST *tmp_list; pthread_rwlock_rdlock(&external->lock); if (external->enabled == FALSE || connection->bypass & FEATURE_EXTERNAL) return NULL; tmp_list = external->external_list; for (; tmp_list != NULL; tmp_list = tmp_list->next) { if (tmp_list->enabled == FALSE) continue; if (tmp_list->me != NULL && connection->rheader->content_type != NULL) { ret = reg_exec(tmp_list->me, connection->rheader->content_type); if (ret) continue; } else if (tmp_list->me != NULL) { ret = reg_exec(tmp_list->me, ""); if (ret) continue; } if (tmp_list->he != NULL) { ret = reg_exec(tmp_list->he, connection->header->host); if (ret) continue; } if (tmp_list->fe != NULL) { ret = reg_exec(tmp_list->fe, connection->header->file); if (ret) continue; } return tmp_list; } return NULL; } /* execute an external program, feed stdin the contents of filebuf if available, and place program's output into a filebuf then return */ FILEBUF *external_exec(CONNECTION * connection, char *exe, FILEBUF * filebuf, int flags) { int i = 0, x, ret, pid, ipipe[2], opipe[2], fd; char buf[8096], *ptr, tmpfile[64], **args; FILEBUF *ret_filebuf; struct HTTP_HEADER_LIST *header_list = NULL; struct pollfd pfd[2]; putlog(MMLOG_DEBUG, "executing %s", exe); ret = pipe(ipipe); if (ret == -1) return NULL; ret = pipe(opipe); if (ret == -1) goto error1; if (flags == EXTERNAL_FILE) { s_strncpy(tmpfile, TEMPFILE, sizeof(tmpfile)); fd = mkstemp(tmpfile); if (fd == -1) goto error2; ret = 0; while (ret != filebuf->size) { x = write(fd, &filebuf->data[ret], (filebuf->size - ret < BLOCKSIZE) ? filebuf->size - ret : BLOCKSIZE); if (x == -1) break; ret += x; } close(fd); } pid = fork(); if (pid == -1) goto error2; if (pid != 0) { close(ipipe[1]); close(opipe[0]); ret_filebuf = filebuf_new(); pfd[0].fd = ipipe[0]; pfd[0].events = POLLIN; if (flags == EXTERNAL_PIPE && filebuf != NULL) { pfd[1].fd = opipe[1]; pfd[1].events = POLLOUT; } else { close(opipe[1]); pfd[1].events = 0; pfd[1].fd = -1; } fcntl(ipipe[0], F_SETFL, O_NONBLOCK); fcntl(opipe[1], F_SETFL, O_NONBLOCK); while (1) { ret = p_poll(pfd, 2, -1); if (ret > 0) { if (pfd[0].revents & POLLIN) { ret = read(ipipe[0], buf, sizeof(buf)); if (ret > 0) filebuf_add(ret_filebuf, buf, ret); else goto cleanup; } else if (pfd[0].revents & (POLLHUP | POLLERR)) goto cleanup; if (pfd[1].revents & POLLOUT) { ret = write(opipe[1], &filebuf->data[i], (filebuf->size - i < 8096) ? filebuf->size - i : 8096); if (ret > 0) i += ret; else { close(opipe[1]); goto cleanup; } if (i == filebuf->size) { close(opipe[1]); pfd[1].fd = -1; pfd[1].events = 0; } } else if (pfd[1].revents & (POLLHUP | POLLERR)) { close(opipe[1]); goto cleanup; } } } } else { close(opipe[1]); close(ipipe[0]); args = string_break(exe, ' '); if (flags == EXTERNAL_FILE) { for (i = 0; args[i]; i++); args = xrealloc(args, sizeof(char *) * (i + 2)); args[i] = tmpfile; args[i + 1] = NULL; } p_clearenv(); if (connection->header != NULL) { p_setenv("HTTP_FILE", connection->header->file, 1); if (connection->header->host != NULL) p_setenv("HTTP_HOST", connection->header->host, 1); p_setenv("HTTP_METHOD", connection->header->method, 1); snprintf(buf, sizeof(buf), "%d", connection->header->port); p_setenv("HTTP_PORT", buf, 1); if (connection->header != NULL) header_list = connection->header->header; while (header_list != NULL) { ptr = env_header_format("CLIENT_", header_list->type); p_setenv(ptr, header_list->value, 1); xfree(ptr); header_list = header_list->next; } if (connection->rheader != NULL) header_list = connection->rheader->header; while (header_list != NULL) { ptr = env_header_format("SERVER_", header_list->type); p_setenv(ptr, header_list->value, 1); xfree(ptr); header_list = header_list->next; } } p_setenv("IP", connection->ip, 1); dup2(opipe[0], STDIN_FILENO); dup2(ipipe[1], STDOUT_FILENO); execvp(args[0], args); close(opipe[0]); close(ipipe[1]); exit(EXIT_FAILURE); } error2: close(opipe[0]); close(opipe[1]); error1: close(ipipe[0]); close(ipipe[1]); return NULL; cleanup: close(ipipe[0]); waitpid(pid, &ret, 0); if (!WIFEXITED(ret) || WEXITSTATUS(ret)) { /* return original content if process exits with an error */ filebuf_free(ret_filebuf); ret_filebuf = NULL; } if (flags == EXTERNAL_FILE) unlink(tmpfile); return ret_filebuf; } /* extract Content-Type header from a filebuf and remove it */ char *external_getmime(FILEBUF * filebuf) { int len; char *ptr, *ret; if (filebuf->size < 14 || strncasecmp(filebuf->data, "Content-type: ", 14)) return NULL; ptr = memchr(filebuf->data, '\n', filebuf->size); if (ptr == NULL) return NULL; len = ptr - &filebuf->data[14] - ((*(ptr - 1) == '\r') ? 1 : 0); ret = xmalloc(len + 1); s_strncpy(ret, &filebuf->data[14], len); /* assume \r\n for second newline if first is \r\n */ len += 14 + ((filebuf->data[14 + len] == '\r') ? 4 : 2); /* len can be lower if nothing but the header was sent */ if (len <= filebuf->size) { memcpy(filebuf->data, &filebuf->data[len], filebuf->size - len); filebuf_resize(filebuf, filebuf->size - len); } else { FREE_AND_NULL(filebuf->data); filebuf->size = 0; } putlog(MMLOG_DEBUG, "external_getmime: %s", ret); return ret; } /* make an http header suitable as an environment variable */ char *env_header_format(char *prefix, char *type) { int j = 0, i, x; char *ret; i = strlen(type); if (prefix != NULL) j = strlen(prefix); ret = xmalloc(i + j + 1); x = i + j; ret[x] = '\0'; if (j > 0) memcpy(ret, prefix, j); for (; i >= 0; x--, i--) { if (type[i] == '-') ret[x] = '_'; else ret[x] = toupper(type[i]); } return ret; }