diff --git a/.gitignore b/.gitignore index 5930ef53..c31cc9c9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ config.h tags redsocks .depend +http-parser* +gen/* diff --git a/Makefile b/Makefile index dd320777..8daa5b9f 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,36 @@ -OBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o gen/version.o +LIBHTTP_VERSION := 2.6.0 +LIBHTTP_NAME := http-parser-$(LIBHTTP_VERSION) +LIBHTTP_CFLAGS := -I./http-parser-$(LIBHTTP_VERSION) -L./http-parser-$(LIBHTTP_VERSION) + +OBJS := tls.o parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o gen/version.o SRCS := $(OBJS:.o=.c) CONF := config.h DEPS := .depend OUT := redsocks -VERSION := 0.4 +VERSION := 0.4.1-adallom -LIBS := -levent +LIBS := -levent -lhttp_parser +CFLAGS += $(LIBHTTP_CFLAGS) CFLAGS += -g -O2 override CFLAGS += -std=c99 -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_DEFAULT_SOURCE -Wall all: $(OUT) -.PHONY: all clean distclean +.PHONY: all clean distclean http-parser tags: *.c *.h ctags -R +$(LIBHTTP_NAME): + wget https://github.com/nodejs/http-parser/archive/v$(LIBHTTP_VERSION).tar.gz + tar -zxf v$(LIBHTTP_VERSION).tar.gz + rm -f v$(LIBHTTP_VERSION).tar.gz + +$(LIBHTTP_NAME)/libhttp_parser.o: + cd $(LIBHTTP_NAME) && make package + +http-parser: $(LIBHTTP_NAME) $(LIBHTTP_NAME)/libhttp_parser.o + $(CONF): @case `uname` in \ Linux*) \ @@ -78,8 +93,8 @@ $(DEPS): $(SRCS) -include $(DEPS) -$(OUT): $(OBJS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) +$(OUT): http-parser $(OBJS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) clean: $(RM) $(OUT) $(CONF) $(OBJS) @@ -87,3 +102,4 @@ clean: distclean: clean $(RM) tags $(DEPS) $(RM) -r gen + $(RM) -rf $(LIBHTTP_NAME) diff --git a/http-connect.c b/http-connect.c index e2435282..4994d72a 100644 --- a/http-connect.c +++ b/http-connect.c @@ -43,6 +43,10 @@ typedef enum httpc_state_t { #define HTTP_HEAD_WM_HIGH 4096 // that should be enough for one HTTP line. +#define MAX_SERVER_NAME (253) /* Max DNS is 253 characters */ +#define MAX_PORT_STR_LENGTH (6) /* Ports are 5 digits decimax max */ +#define MAX_CONNECT_HOST_LENGTH (MAX_SERVER_NAME + MAX_PORT_STR_LENGTH + 1) /* Add one byte for \0 */ + static void httpc_client_init(redsocks_client *client) { client->state = httpc_new; @@ -196,6 +200,14 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) const char *auth_scheme = NULL; char *auth_string = NULL; + char *hostname = NULL; + + if (client->hostname) { + hostname = client->hostname; + } else { + hostname = inet_ntoa(client->destaddr.sin_addr); + } + if (auth->last_auth_query != NULL) { /* find previous auth challange */ @@ -205,8 +217,9 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) auth_scheme = "Basic"; } else if (strncasecmp(auth->last_auth_query, "Digest", 6) == 0) { /* calculate uri */ - char uri[128]; - snprintf(uri, 128, "%s:%u", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port)); + char uri[MAX_CONNECT_HOST_LENGTH] = {0}; + + snprintf(uri, MAX_CONNECT_HOST_LENGTH, "%s:%u", hostname, ntohs(client->destaddr.sin_port)); /* prepare an random string for cnounce */ char cnounce[17]; @@ -223,13 +236,13 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) if (auth_string == NULL) { len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n\r\n", - inet_ntoa(client->destaddr.sin_addr), + hostname, ntohs(client->destaddr.sin_port) ); } else { len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n%s %s %s\r\n\r\n", - inet_ntoa(client->destaddr.sin_addr), + hostname, ntohs(client->destaddr.sin_port), auth_response_header, auth_scheme, diff --git a/redsocks.c b/redsocks.c index 878576ff..82772033 100644 --- a/redsocks.c +++ b/redsocks.c @@ -34,13 +34,27 @@ #include "redsocks.h" #include "utils.h" #include "libevent-compat.h" - +#include "tls.h" +#include "http_parser.h" #define REDSOCKS_RELAY_HALFBUFF 4096 +#define MINIMUM_HOST_READ (10) +#define MAXIMUM_HOST_READ (0) static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how); +struct http_parser_data { + const char *last_header_field; + const char *http_host; + size_t http_host_length; +}; + +typedef enum _redsocks_hostname_read_rc { + SUCCESS = 0, + DATA_MISSING = 1, + FATAL_ERROR = 2, +} redsocks_hostname_read_rc; extern relay_subsys http_connect_subsys; extern relay_subsys http_relay_subsys; @@ -68,6 +82,7 @@ static parser_entry redsocks_entries[] = { .key = "listenq", .type = pt_uint16 }, { .key = "min_accept_backoff", .type = pt_uint16 }, { .key = "max_accept_backoff", .type = pt_uint16 }, + { .key = "parse_host", .type = pt_pchar }, { } }; @@ -137,6 +152,7 @@ static int redsocks_onenter(parser_section *section) (strcmp(entry->key, "listenq") == 0) ? (void*)&instance->config.listenq : (strcmp(entry->key, "min_accept_backoff") == 0) ? (void*)&instance->config.min_backoff_ms : (strcmp(entry->key, "max_accept_backoff") == 0) ? (void*)&instance->config.max_backoff_ms : + (strcmp(entry->key, "parse_host") == 0) ? (void*)&instance->config.parse_host : NULL; section->data = instance; return 0; @@ -346,6 +362,7 @@ void redsocks_drop_client(redsocks_client *client) } list_del(&client->list); + free(client->hostname); free(client); } @@ -409,6 +426,198 @@ static int redsocks_socket_geterrno(redsocks_client *client, struct bufferevent return pseudo_errno; } +static int redsocks_http_parser_on_header_field(http_parser *parser, const char *at, size_t length) +{ + struct http_parser_data *parser_data = (struct http_parser_data *) parser->data; + + parser_data->last_header_field = at; + + return 0; +} + +static int redsocks_http_parser_on_header_value(http_parser *parser, const char *at, size_t length) +{ + struct http_parser_data *parser_data = (struct http_parser_data *) parser->data; + + if (0 != strncasecmp(parser_data->last_header_field, "host", sizeof("host") - 1)) { + return 0; + } + + parser_data->http_host = at; + parser_data->http_host_length = length; + + http_parser_pause(parser, 1); + + return 0; +} + +static int redsocks_peek_buffer(redsocks_client *client, struct bufferevent *buffev, char **peek_buffer, size_t *peek_size) +{ + int n = 0, i = 0; + char *read_buffer = NULL; + size_t read_buffer_size = 0; + size_t read_buffer_position = 0; + struct evbuffer_iovec *v = NULL; + + n = evbuffer_peek(buffev->input, -1, NULL, NULL, 0); + + v = malloc(sizeof(struct evbuffer_iovec) * n); + if (NULL == v) { + redsocks_log_error(client, LOG_ERR, "malloc() error"); + goto finish; + } + + n = evbuffer_peek(buffev->input, -1, NULL, v, n); + + read_buffer_size = 0; + for (i = 0; i < n; i++) { + read_buffer_size += v[i].iov_len; + } + + read_buffer = (char *) malloc(read_buffer_size); + if (NULL == read_buffer) { + redsocks_log_error(client, LOG_ERR, "malloc() error"); + goto fail; + } + + read_buffer_position = 0; + for (i = 0; i < n; i++) { + memcpy(&read_buffer[read_buffer_position], v[i].iov_base, v[i].iov_len); + read_buffer_position += v[i].iov_len; + } + + fail: + free(v); + + finish: + *peek_buffer = read_buffer; + *peek_size = read_buffer_size; + + return 0; +} + +static redsocks_hostname_read_rc redsocks_read_sni(redsocks_client *client, char *read_buffer, size_t read_buffer_size, char **hostname) +{ + int rc = FATAL_ERROR; + + rc = parse_tls_header(read_buffer, read_buffer_size, hostname); + + if (rc >= 0) { + return SUCCESS; + } + + /* rc < 0 */ + + if (rc == -1) { + return DATA_MISSING; + } + + /* rc < -1 */ + return FATAL_ERROR; +} + +static redsocks_hostname_read_rc redsocks_read_http_host(redsocks_client *client, char *read_buffer, size_t read_buffer_size, char **hostname) +{ + http_parser parser; + http_parser_settings parser_settings; + struct http_parser_data parser_data; + char *temp_hostname = NULL; + int rc = HPE_UNKNOWN; + + memset(&parser, 0, sizeof(parser)); + memset(&parser_settings, 0, sizeof(parser_settings)); + memset(&parser_data, 0, sizeof(parser_data)); + + http_parser_init(&parser, HTTP_REQUEST); + + parser.data = &parser_data; + parser_settings.on_header_field = redsocks_http_parser_on_header_field; + parser_settings.on_header_value = redsocks_http_parser_on_header_value; + + rc = http_parser_execute(&parser, &parser_settings, read_buffer, read_buffer_size); + + if (rc != read_buffer_size && + HTTP_PARSER_ERRNO(&parser) != HPE_PAUSED) { + redsocks_log_error(client, LOG_ERR, "error at http parser library: %s", + http_errno_description(HTTP_PARSER_ERRNO(&parser))); + return FATAL_ERROR; + } + + if (rc == read_buffer_size && NULL == parser_data.http_host) { + return DATA_MISSING; + } + + if (parser_data.http_host && parser_data.http_host_length) { + temp_hostname = (char *) malloc(parser_data.http_host_length + 1); + if (NULL == temp_hostname) { + return FATAL_ERROR; + } + + memset(temp_hostname, 0, parser_data.http_host_length + 1); + memcpy(temp_hostname, parser_data.http_host, parser_data.http_host_length); + temp_hostname[parser_data.http_host_length + 1] = '\0'; + *hostname = temp_hostname; + + return SUCCESS; + } + + /* should be unreachable */ + return FATAL_ERROR; +} + +static void redsocks_hostname_reader(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + size_t read_buffer_size = 0; + char *read_buffer = NULL; + char *hostname = NULL; + redsocks_hostname_read_rc rc = FATAL_ERROR; + + assert(client->instance->config.parse_host != NULL); + + if (client->instance->config.parse_host == NULL) { + return; + } + + if (0 != redsocks_peek_buffer(client, buffev, &read_buffer, &read_buffer_size)) { + redsocks_drop_client(client); + return; + } + + redsocks_log_error(client, LOG_INFO, "searching for hostname by %s", client->instance->config.parse_host); + + if (0 == strncmp(client->instance->config.parse_host, "sni", sizeof("sni"))) { + rc = redsocks_read_sni(client, read_buffer, read_buffer_size, &hostname); + } else if (strncmp(client->instance->config.parse_host, "http_host", sizeof("http_host")) == 0) { + rc = redsocks_read_http_host(client, read_buffer, read_buffer_size, &hostname); + } + + switch (rc) { + + case SUCCESS: + client->hostname = hostname; + redsocks_log_error(client, LOG_INFO, "found hostname %s, now connecting", client->hostname); + + if (client->instance->relay_ss->connect_relay) { + client->instance->relay_ss->connect_relay(client); + } else { + redsocks_connect_relay(client); + } + + break; + + case DATA_MISSING: + redsocks_log_error(client, LOG_INFO, "data is missing"); + break; + + case FATAL_ERROR: /* passthourgh */ + default: + redsocks_drop_client(client); + } + + free(read_buffer); +} + static void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg) { redsocks_client *client = _arg; @@ -685,7 +894,12 @@ static void redsocks_accept_client(int fd, short what, void *_arg) redsocks_touch_client(client); - client->client = bufferevent_new(client_fd, NULL, NULL, redsocks_event_error, client); + if (self->config.parse_host == NULL) { + client->client = bufferevent_new(client_fd, NULL, NULL, redsocks_event_error, client); + } else { + client->client = bufferevent_new(client_fd, redsocks_hostname_reader, NULL, redsocks_event_error, client); + } + if (!client->client) { log_errno(LOG_ERR, "bufferevent_new"); goto fail; @@ -695,6 +909,7 @@ static void redsocks_accept_client(int fd, short what, void *_arg) list_add(&client->list, &self->clients); // enable reading to handle EOF from client + if (bufferevent_enable(client->client, EV_READ) != 0) { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); goto fail; @@ -702,6 +917,13 @@ static void redsocks_accept_client(int fd, short what, void *_arg) redsocks_log_error(client, LOG_INFO, "accepted"); + if (self->config.parse_host) { + client->client->wm_read.low = MINIMUM_HOST_READ; + client->client->wm_read.high = MAXIMUM_HOST_READ; + /* We wait first for the client to give us the host */ + return; + } + if (self->relay_ss->connect_relay) self->relay_ss->connect_relay(client); else diff --git a/redsocks.h b/redsocks.h index 4f072d56..b7a7e849 100644 --- a/redsocks.h +++ b/redsocks.h @@ -29,6 +29,7 @@ typedef struct redsocks_config_t { char *type; char *login; char *password; + char *parse_host; uint16_t min_backoff_ms; uint16_t max_backoff_ms; // backoff capped by 65 seconds is enough :) uint16_t listenq; @@ -56,6 +57,7 @@ typedef struct redsocks_client_t { struct bufferevent *relay; struct sockaddr_in clientaddr; struct sockaddr_in destaddr; + char *hostname; int state; // it's used by bottom layer unsigned short client_evshut; unsigned short relay_evshut; diff --git a/tls.c b/tls.c new file mode 100644 index 00000000..be136ae1 --- /dev/null +++ b/tls.c @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2011 and 2012, Dustin Lundquist + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This is a minimal TLS implementation intended only to parse the server name + * extension. This was created based primarily on Wireshark dissection of a + * TLS handshake and RFC4366. + */ +#include +#include /* malloc() */ +#include /* strncpy() */ +#include +#include "tls.h" + +#define TLS_HEADER_LEN 5 +#define TLS_HANDSHAKE_CONTENT_TYPE 0x16 +#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01 + +#ifndef MIN +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif + +static const char tls_alert[] = { + 0x15, /* TLS Alert */ + 0x03, 0x01, /* TLS version */ + 0x00, 0x02, /* Payload length */ + 0x02, 0x28, /* Fatal, handshake failure */ +}; + +static int parse_extensions(const char *, size_t, char **); +static int parse_server_name_extension(const char *, size_t, char **); + +/* Parse a TLS packet for the Server Name Indication extension in the client + * hello handshake, returning the first servername found (pointer to static + * array) + * + * Returns: + * >=0 - length of the hostname and updates *hostname + * caller is responsible for freeing *hostname + * -1 - Incomplete request + * -2 - No Host header included in this request + * -3 - Invalid hostname pointer + * -4 - malloc failure + * < -4 - Invalid TLS client hello + */ +int +parse_tls_header(const char *data, size_t data_len, char **hostname) { + char tls_content_type; + char tls_version_major; + char tls_version_minor; + size_t pos = TLS_HEADER_LEN; + size_t len; + + if (hostname == NULL) + return -3; + + /* Check that our TCP payload is at least large enough for a TLS header */ + if (data_len < TLS_HEADER_LEN) + return -1; + + /* SSL 2.0 compatible Client Hello + * + * High bit of first byte (length) and content type is Client Hello + * + * See RFC5246 Appendix E.2 + */ + if (data[0] & 0x80 && data[2] == 1) { + return -2; + } + + tls_content_type = data[0]; + if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { + return -5; + } + + tls_version_major = data[1]; + tls_version_minor = data[2]; + if (tls_version_major < 3) { + + return -2; + } + + /* TLS record length */ + len = ((unsigned char)data[3] << 8) + + (unsigned char)data[4] + TLS_HEADER_LEN; + data_len = MIN(data_len, len); + + /* Check we received entire TLS record length */ + if (data_len < len) + return -1; + + /* + * Handshake + */ + if (pos + 1 > data_len) { + return -5; + } + if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { + + return -5; + } + + /* Skip past fixed length records: + 1 Handshake Type + 3 Length + 2 Version (again) + 32 Random + to Session ID Length + */ + pos += 38; + + /* Session ID */ + if (pos + 1 > data_len) + return -5; + len = (unsigned char)data[pos]; + pos += 1 + len; + + /* Cipher Suites */ + if (pos + 2 > data_len) + return -5; + len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; + pos += 2 + len; + + /* Compression Methods */ + if (pos + 1 > data_len) + return -5; + len = (unsigned char)data[pos]; + pos += 1 + len; + + if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { + return -2; + } + + /* Extensions */ + if (pos + 2 > data_len) + return -5; + len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; + pos += 2; + + if (pos + len > data_len) + return -5; + return parse_extensions(data + pos, len, hostname); +} + +static int +parse_extensions(const char *data, size_t data_len, char **hostname) { + size_t pos = 0; + size_t len; + + /* Parse each 4 bytes for the extension header */ + while (pos + 4 <= data_len) { + /* Extension Length */ + len = ((unsigned char)data[pos + 2] << 8) + + (unsigned char)data[pos + 3]; + + /* Check if it's a server name extension */ + if (data[pos] == 0x00 && data[pos + 1] == 0x00) { + /* There can be only one extension of each type, so we break + our state and move p to beinnging of the extension here */ + if (pos + 4 + len > data_len) + return -5; + return parse_server_name_extension(data + pos + 4, len, hostname); + } + pos += 4 + len; /* Advance to the next extension header */ + } + /* Check we ended where we expected to */ + if (pos != data_len) + return -5; + + return -2; +} + +static int +parse_server_name_extension(const char *data, size_t data_len, + char **hostname) { + size_t pos = 2; /* skip server name list length */ + size_t len; + + while (pos + 3 < data_len) { + len = ((unsigned char)data[pos + 1] << 8) + + (unsigned char)data[pos + 2]; + + if (pos + 3 + len > data_len) + return -5; + + switch (data[pos]) { /* name type */ + case 0x00: /* host_name */ + *hostname = malloc(len + 1); + if (*hostname == NULL) { + return -4; + } + + strncpy(*hostname, data + pos + 3, len); + + (*hostname)[len] = '\0'; + + return len; + default: + return -5; + } + pos += 3 + len; + } + /* Check we ended where we expected to */ + if (pos != data_len) + return -5; + + return -2; +} + diff --git a/tls.h b/tls.h new file mode 100644 index 00000000..599280cd --- /dev/null +++ b/tls.h @@ -0,0 +1,6 @@ +#ifndef __TLS_H__ +#define __TLS_H__ + +int parse_tls_header(const char *, size_t, char **); + +#endif