From 03023337f6c14ad49da4ed906cddadd7c64ba50e Mon Sep 17 00:00:00 2001 From: andronoob Date: Sun, 25 Apr 2021 22:13:21 +0800 Subject: [PATCH] renew and port PR # 71 originally authored by @aviramc --- .gitmodules | 3 + Makefile | 26 ++++- http-connect.c | 16 ++- http-parser | 1 + protocol.h | 39 +++++++ redsocks.c | 237 ++++++++++++++++++++++++++++++++++++++- redsocks.conf.example | 12 ++ redsocks.h | 3 + tls.c | 251 ++++++++++++++++++++++++++++++++++++++++++ tls.h | 37 +++++++ 10 files changed, 617 insertions(+), 8 deletions(-) create mode 100644 .gitmodules create mode 160000 http-parser create mode 100644 protocol.h create mode 100644 tls.c create mode 100644 tls.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..1aa7912c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "http-parser"] + path = http-parser + url = https://github.com/nodejs/http-parser/ diff --git a/Makefile b/Makefile index 70237a24..86ad572e 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,18 @@ ifdef DISABLE_SHADOWSOCKS OBJS := parser.o main.o redsocks.o log.o direct.o ipcache.o autoproxy.o http-connect.o \ socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o socks5-udp.o \ - tcpdns.o gen/version.o + tcpdns.o tls.o gen/version.o CFLAGS +=-fPIC -O3 -DDISABLE_SHADOWSOCKS FEATURES += DISABLE_SHADOWSOCKS else OBJS := parser.o main.o redsocks.o log.o direct.o ipcache.o autoproxy.o encrypt.o shadowsocks.o http-connect.o \ socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o socks5-udp.o shadowsocks-udp.o \ - tcpdns.o gen/version.o + tcpdns.o tls.o gen/version.o CFLAGS +=-fPIC -O3 endif + +LIBHTTP_CFLAGS := -I./http-parser -L./http-parser + SRCS := $(OBJS:.o=.c) CONF := config.h DEPS := .depend @@ -18,6 +21,10 @@ VERSION := 0.68 OS := $(shell uname) LIBS := -levent + +LIBS += -lhttp_parser +CFLAGS += $(LIBHTTP_CFLAGS) + override CFLAGS += -D_BSD_SOURCE -D_DEFAULT_SOURCE -Wall ifeq ($(OS), Linux) override CFLAGS += -std=c99 -D_XOPEN_SOURCE=600 @@ -63,11 +70,19 @@ endif all: $(OUT) -.PHONY: all clean distclean +.PHONY: all clean distclean http-parser tags: *.c *.h ctags -R +http-parser-download: + git submodule update --init + +http-parser-build: + cd http-parser && make package + +http-parser: http-parser-download http-parser-build + $(CONF): @case $(OS) in \ Linux*) \ @@ -149,8 +164,8 @@ $(DEPS): $(OSX_HEADERS) $(SRCS) -include $(DEPS) -$(OUT): $(OBJS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) +$(OUT): http-parser $(OBJS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) clean: $(RM) $(CONF) $(OBJS) @@ -160,3 +175,4 @@ distclean: clean $(RM) tags $(DEPS) $(RM) -r gen $(RM) -r $(OSX_ROOT_PATH) + cd http-parser && make clean diff --git a/http-connect.c b/http-connect.c index 245b3e69..535e5adb 100644 --- a/http-connect.c +++ b/http-connect.c @@ -42,6 +42,10 @@ typedef enum httpc_state_t { #define HTTP_HEAD_WM_HIGH 8192 // 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) { @@ -174,6 +178,14 @@ struct evbuffer *httpc_mkconnect(redsocks_client *client) { struct evbuffer *buff = NULL, *retval = NULL; int len; + char *hostname = NULL; + + + if (client->hostname) { + hostname = client->hostname; + } else { + hostname = inet_ntoa(client->destaddr.sin_addr); + } buff = evbuffer_new(); if (!buff) { @@ -188,8 +200,8 @@ struct evbuffer *httpc_mkconnect(redsocks_client *client) char *auth_string = NULL; /* calculate uri */ - char uri[RED_INET_ADDRSTRLEN]; - red_inet_ntop(&client->destaddr, uri, sizeof(uri)); + char uri[MAX_CONNECT_HOST_LENGTH] = {0}; + snprintf(uri, MAX_CONNECT_HOST_LENGTH, "%s:%u", hostname, ntohs(client->destaddr.sin_port)); if (auth->last_auth_query != NULL) { /* find previous auth challange */ diff --git a/http-parser b/http-parser new file mode 160000 index 00000000..2343fd6b --- /dev/null +++ b/http-parser @@ -0,0 +1 @@ +Subproject commit 2343fd6b5214b2ded2cdcf76de2bf60903bb90cd diff --git a/protocol.h b/protocol.h new file mode 100644 index 00000000..454c5fc0 --- /dev/null +++ b/protocol.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014, 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. + */ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include + +struct Protocol { + const char *const name; + const uint16_t default_port; + int (*const parse_packet)(const char*, size_t, char **); + const char *const abort_message; + const size_t abort_message_len; +}; + +#endif diff --git a/redsocks.c b/redsocks.c index 4c1b58d6..91b88118 100644 --- a/redsocks.c +++ b/redsocks.c @@ -40,7 +40,23 @@ #include "base.h" #include "redsocks.h" #include "utils.h" +#include "tls.h" +#include "http_parser.h" + +#define MINIMUM_HOST_READ (10) +#define MAXIMUM_HOST_READ (0) + +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; #define REDSOCKS_RELAY_HALFBUFF 1024*16 #define REDSOCKS_AUDIT_INTERVAL 60*2 @@ -86,6 +102,8 @@ static parser_entry redsocks_entries[] = { .key = "login", .type = pt_pchar }, { .key = "password", .type = pt_pchar }, { .key = "listenq", .type = pt_uint16 }, + { .key = "parse_sni_host", .type = pt_bool }, + { .key = "parse_http_host", .type = pt_bool }, { .key = "min_accept_backoff", .type = pt_uint16 }, { .key = "max_accept_backoff", .type = pt_uint16 }, { .key = "autoproxy", .type = pt_uint16 }, @@ -173,6 +191,8 @@ static int redsocks_onenter(parser_section *section) (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : (strcmp(entry->key, "listenq") == 0) ? (void*)&instance->config.listenq : + (strcmp(entry->key, "parse_sni_host") == 0) ? (void*)&instance->config.parse_sni_host : + (strcmp(entry->key, "parse_http_host") == 0) ? (void*)&instance->config.parse_http_host : (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, "autoproxy") == 0) ? (void*)&instance->config.autoproxy : @@ -441,6 +461,8 @@ void redsocks_drop_client(redsocks_client *client) } list_del(&client->list); + if (client->hostname != NULL) + free(client->hostname); free(client); } @@ -566,6 +588,208 @@ int redsocks_read_expected(redsocks_client *client, struct evbuffer *input, void } } +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 = parse_tls_header(read_buffer, read_buffer_size, hostname); + + if (rc >= 0) { + return SUCCESS; + } + + /* rc < 0 */ + + if (rc != -4) { + return DATA_MISSING; + } + + /* rc == -4, malloc failure */ + 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; + return DATA_MISSING; /* Something like "invalid HTTP method" should not be fatal */ + } + + 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] = '\0'; //this should be redundant. (should already be covered by memset) + + /* handle the http://host:port/ situation. + this is not yet handled by http-parser currently: + https://github.com/nodejs/http-parser/issues/501 */ + char *colon_ptr = strchr(temp_hostname, ':'); + if (colon_ptr != NULL) memset(colon_ptr, 0, parser_data.http_host_length + 1 - (colon_ptr - temp_hostname)); + + *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_sni_host || client->instance->config.parse_http_host); + + if (!client->instance->config.parse_sni_host && !client->instance->config.parse_http_host) { + return; + } + + if (client->relay != NULL) { + return; + } + + if (0 != redsocks_peek_buffer(client, buffev, &read_buffer, &read_buffer_size)) { + redsocks_drop_client(client); + return; + } + + if (client->instance->config.parse_sni_host) { + redsocks_log_error(client, LOG_INFO, "searching for hostname by TLS SNI"); + rc = redsocks_read_sni(client, read_buffer, read_buffer_size, &hostname); + } + if (rc != SUCCESS && rc != FATAL_ERROR && client->instance->config.parse_http_host) { + redsocks_log_error(client, LOG_INFO, "searching for hostname by HTTP Host header"); + rc = redsocks_read_http_host(client, read_buffer, read_buffer_size, &hostname); + } + + client->hostname = NULL; + + switch (rc) { + + case SUCCESS: + client->hostname = hostname; + redsocks_log_error(client, LOG_INFO, "found hostname %s,", client->hostname); + case DATA_MISSING: + redsocks_log_error(client, LOG_INFO, "now connecting..."); + if (client->instance->relay_ss->connect_relay) { + client->instance->relay_ss->connect_relay(client); + } else { + redsocks_connect_relay(client); + } + + break; + + case FATAL_ERROR: /* passthourgh */ + default: + redsocks_drop_client(client); + } + + free(read_buffer); +} + struct evbuffer *mkevbuffer(void *data, size_t len) { struct evbuffer *buff = NULL, *retval = NULL; @@ -822,7 +1046,11 @@ static void redsocks_accept_client(int fd, short what, void *_arg) log_errno(LOG_ERR, "bufferevent_socket_new"); goto fail; } - bufferevent_setcb(client->client, NULL, NULL, redsocks_event_error, client); + if (!self->config.parse_sni_host && !self->config.parse_http_host) { + bufferevent_setcb(client->client, NULL, NULL, redsocks_event_error, client); + } else { + bufferevent_setcb(client->client, redsocks_hostname_reader, NULL, redsocks_event_error, client); + } client_fd = -1; @@ -836,6 +1064,13 @@ static void redsocks_accept_client(int fd, short what, void *_arg) redsocks_log_error(client, LOG_DEBUG, "accepted"); + if (self->config.parse_sni_host || self->config.parse_http_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->config.autoproxy && autoproxy_subsys.connect_relay) autoproxy_subsys.connect_relay(client); else if (self->relay_ss->connect_relay) diff --git a/redsocks.conf.example b/redsocks.conf.example index 82d5c373..ec59c85c 100644 --- a/redsocks.conf.example +++ b/redsocks.conf.example @@ -113,6 +113,18 @@ redsocks { // login = "foobar";// field 'login' is reused as encryption // method of shadowsocks // password = "baz"; + + // parse hostname from TLS SNI extension, + // so that HTTP CONNECT can use hostname rather than IP address. + // in this case, the hostname is supposed to be resolved remotely. + // (in other words - remote-DNS) + // parse_sni_host = false; + + // parse hostname from plaintext HTTP Host header, + // so that HTTP CONNECT can use hostname rather than IP address. + // in this case, the hostname is supposed to be resolved remotely. + // (in other words - remote-DNS) + // parse_http_host = false; } redudp { diff --git a/redsocks.h b/redsocks.h index 1437dfb1..3e5df848 100644 --- a/redsocks.h +++ b/redsocks.h @@ -36,6 +36,8 @@ typedef struct redsocks_config_t { char *type; char *login; char *password; + bool parse_sni_host; + bool parse_http_host; uint16_t min_backoff_ms; uint16_t max_backoff_ms; // backoff capped by 65 seconds is enough :) uint16_t listenq; @@ -66,6 +68,7 @@ typedef struct redsocks_client_t { struct bufferevent *relay; struct sockaddr_storage clientaddr; struct sockaddr_storage destaddr; + char *hostname; int state; // it's used by bottom layer short relay_connected; unsigned short client_evshut; diff --git a/tls.c b/tls.c new file mode 100644 index 00000000..b2deb50c --- /dev/null +++ b/tls.c @@ -0,0 +1,251 @@ +/* + * 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 +#include /* strncpy() */ +#include +#include +#include "tls.h" +#include "protocol.h" +#include "log.h" + +#define SERVER_NAME_LEN 256 +#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 */ +}; + +const struct Protocol *const tls_protocol = &(struct Protocol){ + .name = "tls", + .default_port = 443, + .parse_packet = (int (*const)(const char *, size_t, char **))&parse_tls_header, + .abort_message = tls_alert, + .abort_message_len = sizeof(tls_alert) +}; + + +/* 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 uint8_t *data, size_t data_len, char **hostname) { + uint8_t tls_content_type; + uint8_t tls_version_major; + uint8_t 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) { + log_error(LOG_DEBUG, "Received SSL 2.0 Client Hello which can not support SNI."); + return -2; + } + + tls_content_type = data[0]; + if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { + log_error(LOG_DEBUG, "Request did not begin with TLS handshake."); + return -5; + } + + tls_version_major = data[1]; + tls_version_minor = data[2]; + if (tls_version_major < 3) { + log_error(LOG_DEBUG, "Received SSL %" PRIu8 ".%" PRIu8 " handshake which can not support SNI.", + tls_version_major, tls_version_minor); + + return -2; + } + + /* TLS record length */ + len = ((size_t)data[3] << 8) + + (size_t)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) { + log_error(LOG_DEBUG, "Not a 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 = (size_t)data[pos]; + pos += 1 + len; + + /* Cipher Suites */ + if (pos + 2 > data_len) + return -5; + len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1]; + pos += 2 + len; + + /* Compression Methods */ + if (pos + 1 > data_len) + return -5; + len = (size_t)data[pos]; + pos += 1 + len; + + if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { + log_error(LOG_DEBUG, "Received SSL 3.0 handshake without extensions"); + return -2; + } + + /* Extensions */ + if (pos + 2 > data_len) + return -5; + len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1]; + pos += 2; + + if (pos + len > data_len) + return -5; + return parse_extensions(data + pos, len, hostname); +} + +int +parse_extensions(const uint8_t *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 = ((size_t)data[pos + 2] << 8) + + (size_t)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; +} + +int +parse_server_name_extension(const uint8_t *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 = ((size_t)data[pos + 1] << 8) + + (size_t)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) { + log_error(LOG_ERR, "malloc() failure"); + return -4; + } + + strncpy(*hostname, (const char *)(data + pos + 3), len); + + (*hostname)[len] = '\0'; + + return len; + default: + log_error(LOG_DEBUG, "Unknown server name extension name type: %" PRIu8, + data[pos]); + } + 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..52fdddff --- /dev/null +++ b/tls.h @@ -0,0 +1,37 @@ +/* + * 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. + */ +#ifndef TLS_H +#define TLS_H + +#include "protocol.h" + +extern const struct Protocol *const tls_protocol; + +int parse_tls_header(const uint8_t*, size_t, char **); +int parse_extensions(const uint8_t*, size_t, char **); +int parse_server_name_extension(const uint8_t*, size_t, char **); + +#endif