/* * Copyright (c) 2005 Niels Provos * All rights reserved. * * 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 * 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 #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include #ifdef HAVE_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "proxy.h" #include "proxy_messages.h" #include "smtp.h" #include "honeyd_overload.h" extern int debug; #define DFPRINTF(x, y) do { \ if (debug >= x) fprintf y; \ } while (0) /* globals */ FILE *flog_proxy = NULL; /* log the proxy transactions somewhere */ static pcre *re_connect; /* regular expression to match connect */ static pcre *re_hostport; /* extracts host and port */ static pcre *re_get; /* generic get request */ /* Generic PROXY related code */ char * proxy_logline(struct proxy_ta *ta) { static char line[1024]; char *srcipaddress = kv_find(&ta->dictionary, "$srcipaddress"); char *cmd = kv_find(&ta->dictionary, "$command"); char *host = kv_find(&ta->dictionary, "$host"); char *port = kv_find(&ta->dictionary, "$port"); char *uri = kv_find(&ta->dictionary, "$rawuri"); if (!strcasecmp("connect", cmd)) { snprintf(line, sizeof(line), "%d %s: CONNECT %s:%s", time(NULL), srcipaddress, host, port); } else { snprintf(line, sizeof(line), "%d %s: GET %s:%s%s", time(NULL), srcipaddress, host, port, uri); } return (line); } void proxy_clear_state(struct proxy_ta *ta) { /* XXX - something here */ } /* Callbacks for PROXY handling */ char * proxy_response(struct proxy_ta *ta, struct keyvalue data[]) { static char line[1024]; struct keyvalue *cur; for (cur = &data[0]; cur->key != NULL; cur++) { if (strcmp(ta->proxy_id, cur->key) == 0) break; } if (cur->key == NULL) return (NULL); strlcpy(line, cur->value, sizeof(line)); TAILQ_FOREACH(cur, &ta->dictionary, next) { strrpl(line, sizeof(line), cur->key, cur->value); } return (line); } int proxy_allowed_network(const char *host) { const char *error; int erroroffset; pcre *re_uri; int rc; int ovector[30]; char *unusednets[] = { "^127\\.[0-9]+\\.[0-9]+\\.[0-9]+$", /* local */ "^10\\.[0-9]+\\.[0-9]+\\.[0-9]+$", /* rfc-1918 */ "^172\\.(1[6-9]|2[0-9]|3[01])\\.[0-9]+\\.[0-9]+$", "^192\\.168\\.[0-9]+\\.[0-9]+$", /* rfc-1918 */ "^2(2[4-9]|3[0-9])\\.[0-9]+\\.[0-9]+\\.[0-9]+$",/* rfc-1112 */ "^2(4[0-9]|5[0-5])\\.[0-9]+\\.[0-9]+\\.[0-9]+$", "^0\\.[0-9]+\\.[0-9]+\\.[0-9]+$", "^255\\.[0-9]+\\.[0-9]+\\.[0-9]+$", NULL }; char **p; for (p = &unusednets[0]; *p; ++p) { re_uri = pcre_compile(*p, PCRE_CASELESS, &error, &erroroffset, NULL); if (re_uri == NULL) { /* Default to no match */ fprintf(stderr, "%s: %s: %s at %d", __func__, *p, error, erroroffset); return (0); } /* Match against the URI */ rc = pcre_exec(re_uri, NULL, host, strlen(host), 0, 0, ovector, 30); pcre_free(re_uri); if (rc >= 0) return (0); } return (1); } /* * Checks if we are allowed to retrieve a URL from here. */ int proxy_allowed_get(struct proxy_ta *ta, struct keyvalue data[]) { const char *error; int erroroffset; char *host, *uri; struct keyvalue *cur; pcre *re_uri; int rc; int ovector[30]; host = kv_find(&ta->dictionary, "$host"); uri = kv_find(&ta->dictionary, "$rawuri"); for (cur = &data[0]; cur->key != NULL; cur++) { if (strcmp(host, cur->key) == 0) break; } /* Host is not allowed if we do not find it */ if (cur->key == NULL) return (0); re_uri = pcre_compile(cur->value, PCRE_CASELESS, &error, &erroroffset, NULL); if (re_uri == NULL) { /* Default to no match */ fprintf(stderr, "%s: %s: %s at %d", __func__, cur->value, error, erroroffset); return (0); } /* Match against the URI */ rc = pcre_exec(re_uri, NULL, uri, strlen(uri), 0, 0, ovector, 30); pcre_free(re_uri); return (rc >= 0); } int proxy_bad_connection(struct proxy_ta *ta) { char *response = proxy_response(ta, badconnection); bufferevent_write(ta->bev, response, strlen(response)); ta->wantclose = 1; return (0); } void proxy_remote_readcb(struct bufferevent *bev, void *arg) { struct proxy_ta *ta = arg; struct evbuffer *buffer = EVBUFFER_INPUT(bev); unsigned char *data = EVBUFFER_DATA(buffer); size_t len = EVBUFFER_LENGTH(buffer); bufferevent_write(ta->bev, data, len); evbuffer_drain(buffer, len); } void proxy_remote_writecb(struct bufferevent *bev, void *arg) { } void proxy_remote_errorcb(struct bufferevent *bev, short what, void *arg) { struct proxy_ta *ta = arg; struct evbuffer *buffer = EVBUFFER_OUTPUT(ta->bev); fprintf(stderr, "%s: called with %p, freeing\n", __func__, arg); /* If we still have data to write; we just wait for the flush */ if (EVBUFFER_LENGTH(buffer)) { /* Shutdown this site at least - XXX: maybe call shutdown */ bufferevent_disable(bev, EV_READ|EV_WRITE); ta->wantclose = 1; } else { proxy_ta_free(ta); } } char * proxy_corrupt(char *data, size_t len) { static char buffer[4096]; int corruptions = len / CORRUPT_SPACE + 1; int i; if (len > sizeof(buffer) || len <= 1) return (data); memcpy(buffer, data, len); for (i = 0; i < corruptions; i++) { int off = rand() % (len - 1); buffer[off] = rand(); } return (buffer); } void proxy_connect_cb(int fd, short what, void *arg) { char line[1024], *data; struct proxy_ta *ta = arg; int error; socklen_t errsz = sizeof(error); char *uri; /* Check if the connection completed */ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errsz) == -1 || error) { char *response; fprintf(stderr, "%s: connection failed: %s\n", __func__, strerror(error)); close(fd); /* Give them a connect error message */ kv_replace(&ta->dictionary, "$reason", strerror(error)); response = proxy_response(ta, badconnect); bufferevent_write(ta->bev, response, strlen(response)); ta->wantclose = 1; return; } ta->remote_bev = bufferevent_new(ta->remote_fd, proxy_remote_readcb, proxy_remote_writecb, proxy_remote_errorcb, ta); if (ta->bev == NULL) { close(fd); proxy_ta_free(ta); return; } /* If this get is not allowed, we are going to corrupt the data */ if (!proxy_allowed_get(ta, allowedhosts)) ta->corrupt = 1; uri = kv_find(&ta->dictionary, "$rawuri"); snprintf(line, sizeof(line), "GET %s HTTP/1.0\r\n", ta->corrupt ? proxy_corrupt(uri, strlen(uri)) : uri); bufferevent_write(ta->remote_bev, line, strlen(line)); /* Forward all the headers */ while ((data = kv_find(&ta->dictionary, "data")) != NULL) { /* We do not propagate X-Forwarded-For headers */ if (strncasecmp(X_FORWARDED, data, strlen(X_FORWARDED))) { bufferevent_write(ta->remote_bev, ta->corrupt ? proxy_corrupt(data, strlen(data)) : data, strlen(data)); bufferevent_write(ta->remote_bev, "\r\n", 2); } /* Do not invalidate this data until we used it */ kv_remove(&ta->dictionary, "data"); } bufferevent_write(ta->remote_bev, "\r\n", 2); /* Allow the remote site to send us data */ bufferevent_enable(ta->remote_bev, EV_READ); ta->justforward = 1; } void proxy_connect(struct proxy_ta *ta, char *host, int port) { fprintf(stderr, "Connecting to %s port %d\n", host, port); ta->remote_fd = -1; if (proxy_allowed_network(host)) { char *local_ip = kv_find(&ta->dictionary, "$dstipaddress"); if (local_ip != NULL) { ta->remote_fd = make_bound_connect( SOCK_STREAM, host, port, local_ip); } else { ta->remote_fd = make_socket( connect, SOCK_STREAM, host, port); } } if (ta->remote_fd == -1) { char *response; fprintf(stderr, "%s: failed to connect: %s\n", __func__, strerror(errno)); kv_replace(&ta->dictionary, "$reason", strerror(errno)); response = proxy_response(ta, badconnect); bufferevent_write(ta->bev, response, strlen(response)); ta->wantclose = 1; return; } /* One handy event to get called back on this */ event_once(ta->remote_fd, EV_WRITE, proxy_connect_cb, ta, NULL); } void proxy_handle_get_cb(int result, char type, int count, int ttl, void *addresses, void *arg) { struct proxy_ta *ta = arg; struct addr addr; struct in_addr *in_addrs = addresses; int port = atoi(kv_find(&ta->dictionary, "$port")); char *response; if (ta->dns_canceled) { proxy_ta_free(ta); return; } ta->dns_pending = 0; if (result != DNS_ERR_NONE || type != DNS_IPv4_A || count == 0) { response = proxy_response(ta, baddomain); bufferevent_write(ta->bev, response, strlen(response)); ta->wantclose = 1; return; } /* Need to make a connection here */ bufferevent_disable(ta->bev, EV_READ); addr_pack(&addr, ADDR_TYPE_IP, IP_ADDR_BITS, &in_addrs[0], IP_ADDR_LEN); proxy_connect(ta, addr_ntoa(&addr), port); } int proxy_handle_get(struct proxy_ta *ta) { char *host = kv_find(&ta->dictionary, "$rawhost"); int rc; int ovector[30]; kv_replace(&ta->dictionary, "$command", "GET"); rc = pcre_exec(re_hostport, NULL, host, strlen(host), 0, 0, ovector, 30); if (rc >= 0) { char *strport = proxy_pcre_group(host, 2, ovector); char *real_host = proxy_pcre_group(host, 1, ovector); kv_add(&ta->dictionary, "$host", real_host); kv_add(&ta->dictionary, "$port", strport); free(real_host); free(strport); } else { kv_add(&ta->dictionary, "$host", host); kv_add(&ta->dictionary, "$port", "80"); } if (flog_proxy != NULL) { char *line = proxy_logline(ta); fprintf(flog_proxy, "%s\n", line); fflush(flog_proxy); } /* Try to resolve the domain name */ evdns_resolve_ipv4(kv_find(&ta->dictionary, "$host"), 0, proxy_handle_get_cb, ta); ta->dns_pending = 1; return (0); } void proxy_handle_connect_cb(int result, char type, int count, int ttl, void *addresses, void *arg) { struct proxy_ta *ta = arg; char *host = kv_find(&ta->dictionary, "$host"); int port = atoi(kv_find(&ta->dictionary, "$port")); char *response; fprintf(stderr, "Connecting to %s port %d\n", host, port); if (ta->dns_canceled) { proxy_ta_free(ta); return; } ta->dns_pending = 0; if (result != DNS_ERR_NONE) { response = proxy_response(ta, baddomain); bufferevent_write(ta->bev, response, strlen(response)); ta->wantclose = 1; return; } if (port != 25 || !proxy_allowed_network(host)) { response = proxy_response(ta, badport); bufferevent_write(ta->bev, response, strlen(response)); ta->wantclose = 1; } else { struct smtp_ta *smtp_ta = NULL; int fd = dup(ta->fd); if (fd != -1) smtp_ta = smtp_ta_new(fd, (struct sockaddr *)&ta->sa, ta->salen, NULL, 0, 0); if (smtp_ta != NULL) { response = proxy_response(ta, goodport); bufferevent_write(smtp_ta->bev, response, strlen(response)); smtp_greeting(smtp_ta); proxy_ta_free(ta); } else { kv_add(&ta->dictionary, "$host", host); response = proxy_response(ta, badport); bufferevent_write(ta->bev, response, strlen(response)); ta->wantclose = 1; } } } int proxy_handle_connect(struct proxy_ta *ta) { char *host = kv_find(&ta->dictionary, "$rawhost"); int rc; int ovector[30]; kv_replace(&ta->dictionary, "$command", "CONNECT"); rc = pcre_exec(re_hostport, NULL, host, strlen(host), 0, 0, ovector, 30); if (rc >= 0) { char *strport = proxy_pcre_group(host, 2, ovector); char *real_host = proxy_pcre_group(host, 1, ovector); kv_add(&ta->dictionary, "$host", real_host); kv_add(&ta->dictionary, "$port", strport); free(real_host); free(strport); } else { kv_add(&ta->dictionary, "$host", host); kv_add(&ta->dictionary, "$port", "80"); } if (flog_proxy != NULL) { char *line = proxy_logline(ta); fprintf(flog_proxy, "%s\n", line); fflush(flog_proxy); } /* Try to resolve the domain name */ evdns_resolve_ipv4(kv_find(&ta->dictionary, "$host"), 0, proxy_handle_connect_cb, ta); ta->dns_pending = 1; return (0); } char * proxy_pcre_group(char *line, int groupnr, int ovector[]) { int start = ovector[2*groupnr]; int end = ovector[2*groupnr + 1]; char *group = malloc(end - start + 1); if (group == NULL) err(1, "%s: malloc", __func__); memcpy(group, line + start, end - start); group[end-start] = '\0'; return (group); } int proxy_handle(struct proxy_ta *ta, char *line) { int rc; int ovector[30]; /* Execute regular expressions to match the command */ rc = pcre_exec(re_connect, NULL, line, strlen(line), 0, 0, ovector, 30); if (rc >= 0) { char *host = proxy_pcre_group(line, 1, ovector); kv_replace(&ta->dictionary, "$rawhost", host); free(host); ta->empty_cb = proxy_handle_connect; return (0); } rc = pcre_exec(re_get, NULL, line, strlen(line), 0, 0, ovector, 30); if (rc >= 0) { char *host = proxy_pcre_group(line, 1, ovector); char *uri = proxy_pcre_group(line, 2, ovector); kv_replace(&ta->dictionary, "$rawhost", host); kv_replace(&ta->dictionary, "$rawuri", uri); free(host); free(uri); ta->empty_cb = proxy_handle_get; return (0); } return proxy_bad_connection(ta); } char * proxy_readline(struct bufferevent *bev) { struct evbuffer *buffer = EVBUFFER_INPUT(bev); char *data = EVBUFFER_DATA(buffer); size_t len = EVBUFFER_LENGTH(buffer); char *line; int i; for (i = 0; i < len; i++) { if (data[i] == '\r' || data[i] == '\n') break; } if (i == len) return (NULL); if ((line = malloc(i + 1)) == NULL) { fprintf(stderr, "%s: out of memory\n", __func__); evbuffer_drain(buffer, i); return (NULL); } memcpy(line, data, i); line[i] = '\0'; if ( i < len - 1 ) { char fch = data[i], sch = data[i+1]; /* Drain one more character if needed */ if ( (sch == '\r' || sch == '\n') && sch != fch ) i += 1; } evbuffer_drain(buffer, i + 1); return (line); } void proxy_readcb(struct bufferevent *bev, void *arg) { struct proxy_ta *ta = arg; char *line; if (ta->justforward) { struct evbuffer *input = EVBUFFER_INPUT(bev); char *data = EVBUFFER_DATA(input); size_t len = EVBUFFER_LENGTH(input); if (ta->corrupt) { bufferevent_write(ta->remote_bev, proxy_corrupt(data, len), len); } else { bufferevent_write(ta->remote_bev, data, len); } evbuffer_drain(input, len); return; } while ((line = proxy_readline(bev)) != NULL) { int res = 0; /* If we are ready to close on the bugger, just eat it */ if (ta->wantclose) { free(line); continue; } if (ta->empty_cb) { /* eat the input until we get a return */ if (strlen(line)) { kv_add(&ta->dictionary, "data", line); free(line); continue; } else { res = (*ta->empty_cb)(ta); ta->empty_cb = NULL; } } else { res = proxy_handle(ta, line); } free(line); /* Destroy the state machine on error */ if (res == -1) { proxy_ta_free(ta); return; } } } void proxy_writecb(struct bufferevent *bev, void *arg) { struct proxy_ta *ta = arg; if (ta->wantclose) proxy_ta_free(ta); } void proxy_errorcb(struct bufferevent *bev, short what, void *arg) { fprintf(stderr, "%s: called with %p, freeing\n", __func__, arg); proxy_ta_free(arg); } /* Tear down a connection */ void proxy_ta_free(struct proxy_ta *ta) { struct keyvalue *entry; if (ta->dns_pending && !ta->dns_canceled) { /* if we have a pending dns lookup, tell it to cancel */ ta->dns_canceled = 1; return; } while ((entry = TAILQ_FIRST(&ta->dictionary)) != NULL) { TAILQ_REMOVE(&ta->dictionary, entry, next); free(entry->key); free(entry->value); free(entry); } bufferevent_free(ta->bev); close(ta->fd); if (ta->remote_bev) { bufferevent_free(ta->remote_bev); close(ta->remote_fd); } free(ta); } /* Create a new PROXY transaction */ struct proxy_ta * proxy_ta_new(int fd, struct sockaddr *sa, socklen_t salen, struct sockaddr *lsa, socklen_t lsalen) { struct proxy_ta *ta = calloc(1, sizeof(struct proxy_ta)); char *srcipname, *srcportname; char *dstipname, *dstportname; if (ta == NULL) goto error; ta->proxy_id = "junkbuster"; TAILQ_INIT(&ta->dictionary); memcpy(&ta->sa, sa, salen); ta->salen = salen; ta->fd = fd; ta->bev = bufferevent_new(fd, proxy_readcb, proxy_writecb, proxy_errorcb, ta); if (ta->bev == NULL) goto error; /* Create our tiny dictionary */ if (lsa != NULL) { name_from_addr(lsa, lsalen, &dstipname, &dstportname); kv_add(&ta->dictionary, "$dstipaddress", dstipname); } name_from_addr(sa, salen, &srcipname, &srcportname); kv_add(&ta->dictionary, "$srcipaddress", srcipname); bufferevent_enable(ta->bev, EV_READ); fprintf(stderr, "%s: new proxy instance to %s complete.\n", __func__, srcipname); return (ta); error: if (ta != NULL) free(ta); fprintf(stderr, "%s: out of memory\n", __func__); close(fd); return (NULL); } static void accept_socket(int fd, short what, void *arg) { struct sockaddr_storage ss, lss; socklen_t addrlen = sizeof(ss), laddrlen = sizeof(lss); int nfd, res; if ((nfd = accept(fd, (struct sockaddr *)&ss, &addrlen)) == -1) { fprintf(stderr, "%s: bad accept\n", __func__); return; } /* Test our special subsystem magic */ res = fcntl(fd, F_XXX_GETSOCK, &lss, &laddrlen); if (res != -1) { /* * We are running under honeyd and could figure out * who we are. That's great. */ proxy_ta_new(nfd, (struct sockaddr *)&ss, addrlen, (struct sockaddr *)&lss, laddrlen); } else { proxy_ta_new(nfd, (struct sockaddr *)&ss, addrlen, NULL, 0); } } void proxy_bind_socket(struct event *ev, u_short port) { int fd; if ((fd = make_socket(bind, SOCK_STREAM, "0.0.0.0", port)) == -1) err(1, "%s: cannot bind socket: %d", __func__, port); if (listen(fd, 10) == -1) err(1, "%s: listen failed: %d", __func__, port); /* Schedule the socket for accepting */ event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, NULL); event_add(ev, NULL); fprintf(stderr, "Bound to port %d\n" "Awaiting connections ... \n", port); } void proxy_init(void) { const char *error; int erroroffset; const char *exp_connect = "^connect\\s+(.*)\\s+http"; const char *exp_hostport = "^(.*):([0-9]+)$"; const char *exp_get = "^GET\\s+http://([^/ ]*)(/?[^ ]*)\\s+HTTP"; /* Compile regular expressions for command parsing */ re_connect = pcre_compile(exp_connect, PCRE_CASELESS, &error, &erroroffset, NULL); if (re_connect == NULL) err(1, "%s: %s at %d", __func__, error, erroroffset); re_hostport = pcre_compile(exp_hostport, PCRE_CASELESS, &error, &erroroffset, NULL); if (re_connect == NULL) err(1, "%s: %s at %d", __func__, error, erroroffset); re_get = pcre_compile(exp_get, PCRE_CASELESS, &error, &erroroffset, NULL); if (re_connect == NULL) err(1, "%s: %s at %d", __func__, error, erroroffset); }