Skip to content

Commit

Permalink
support for matching by http hostnames added
Browse files Browse the repository at this point in the history
  • Loading branch information
falahati committed Nov 17, 2022
1 parent 555717e commit b928216
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 35 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ AR ?= ar
CFLAGS ?=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)

LIBS=-lm -lpcre2-8
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o http.o tls.o argtable3.o collection.o gap.o tcp-probe.o
OBJS_A=libsslh.a
FORK_OBJS=sslh-fork.o $(OBJS_A)
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
Expand Down Expand Up @@ -85,7 +85,7 @@ version.h:

sslh: sslh-fork sslh-select sslh-ev

$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h http.h tls.h udp-listener.h version.h


sslh-conf.c sslh-conf.h: sslhconf.cfg
Expand Down
4 changes: 3 additions & 1 deletion echoѕrv-conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ struct sslhcfg_protocols_item {
int fork;
int tfo_ok;
int log_level;
int keepalive;
int keepalive;
size_t hostnames_len;
char **hostnames;
size_t sni_hostnames_len;
char** sni_hostnames;
size_t alpn_protocols_len;
Expand Down
172 changes: 172 additions & 0 deletions http.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <[email protected]>
* 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 HTTP implementation intended only to parse the host name of a
* request
*/
#include <stdio.h>
#include <stdlib.h> /* malloc() */
#include <fnmatch.h> /* fnmatch() */
#include "http.h"
#include "sslh-conf.h"
#include "log.h"

#define HTTP_HEADER_LEN 5

typedef struct {
int http_match_hostname : 1;
} TLS_MATCHMODE;

struct HTTPProtocol {
TLS_MATCHMODE match_mode;
int hostname_list_len;
const char** hostname_list;
};

static int has_match(const char **, size_t, const char *, size_t);
static int parse_hostname(const struct HTTPProtocol *tls_data, const char *data, size_t data_len);
static int probe_http_method(const char *p, int len, const char *opt);

/* Parse a HTTP header for request hostname, returning a status code
*
* Returns:
* 0: no match
* 1: match
* < 0: error code (see http.h)
*/
int
parse_http_header(const struct HTTPProtocol *http_data, const char *data, size_t data_len) {

/* If it does not have HTTP in the request (HTTP/1.1) then lets check for the method */
if (memmem(data, data_len, "HTTP", 4) == NULL) {
int res;
#define PROBE_HTTP_METHOD(opt) if ((res = probe_http_method(data, data_len, opt)) != HTTP_NOMATCH) return res

/* it could be HTTP/1.0 without version: check if it's got an
* HTTP method (RFC2616 5.1.1) */
PROBE_HTTP_METHOD("OPTIONS");
PROBE_HTTP_METHOD("GET");
PROBE_HTTP_METHOD("HEAD");
PROBE_HTTP_METHOD("POST");
PROBE_HTTP_METHOD("PUT");
PROBE_HTTP_METHOD("DELETE");
PROBE_HTTP_METHOD("TRACE");
PROBE_HTTP_METHOD("CONNECT");

#undef PROBE_HTTP_METHOD

// if neither match, this isnt a HTTP request
return HTTP_NOMATCH;
}

/* By now we know it's HTTP. if hostname is set, parse request to see if
* they match. Otherwise, it's a match already */
if (http_data &&
(http_data->match_mode.http_match_hostname || http_data->match_mode.http_match_hostname)) {
return parse_hostname(http_data, data, data_len);
} else {
return HTTP_MATCH;
}
}

static int
parse_hostname(const struct HTTPProtocol *http_data, const char *data, size_t data_len)
{
if (http_data == NULL)
return HTTP_EINVAL;

// see if already have the hostname
const char *start = memmem(data, data_len, "Host: ", 6);
if (start != NULL)
{
start += 6;

const char *end = memchr(start, '\r', data_len - (start - data));
// if we have the end, we are ready to parse it
if (end != NULL)
{
return has_match(http_data->hostname_list, http_data->hostname_list_len, start, end - start);
}
}
else if (
// or if we have already reached the end of the request
memmem(data, data_len, "\r\n\r\n", 4)
)
{
// no host informaiton available for this request
return HTTP_ENOHOST;
}

return HTTP_ELENGTH;
}

static int
probe_http_method(const char *p, int len, const char *opt)
{
if (len < strlen(opt))
return HTTP_ELENGTH;

return !strncmp(p, opt, strlen(opt));
}

static int
has_match(const char** list, size_t list_len, const char* name, size_t name_len) {
const char **item;
int i;
char *name_nullterminated = malloc(name_len+1);
CHECK_ALLOC(name_nullterminated, "malloc");
memcpy(name_nullterminated, name, name_len);
name_nullterminated[name_len]='\0';

for (i = 0; i < list_len; i++) {
item = &list[i];
print_message(msg_probe_error, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
if(!fnmatch(*item, name_nullterminated, 0)) {
free(name_nullterminated);
return 1;
}
}
free(name_nullterminated);
return 0;
}

struct HTTPProtocol *
new_http_data() {
struct HTTPProtocol *http_data = malloc(sizeof(struct HTTPProtocol));
CHECK_ALLOC(http_data, "malloc");

memset(http_data, 0, sizeof(*http_data));

return http_data;
}

struct HTTPProtocol *
http_data_set_list(struct HTTPProtocol *http_data, const char** list, size_t list_len) {
http_data->hostname_list = list;
http_data->hostname_list_len = list_len;
http_data->match_mode.http_match_hostname = 1;
return http_data;
}
44 changes: 44 additions & 0 deletions http.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <[email protected]>
* 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 HTTP_H
#define HTTP_H

#include "common.h"

struct HTTPProtocol;

int parse_http_header(const struct HTTPProtocol *http_data, const char *data, size_t data_len);

struct HTTPProtocol *new_http_data();
struct HTTPProtocol *http_data_set_list(struct HTTPProtocol *, const char **, size_t);

#define HTTP_MATCH 1
#define HTTP_NOMATCH 0
#define HTTP_EINVAL -1 /* invalid */
#define HTTP_ELENGTH -2 /* Incomplete request */
#define HTTP_ENOHOST -3 /* No host information found */

#endif
39 changes: 8 additions & 31 deletions probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,38 +235,15 @@ static int is_xmpp_protocol( const char *p, ssize_t len, struct sslhcfg_protocol
return PROBE_NEXT;
}

static int probe_http_method(const char *p, int len, const char *opt)
{
if (len < strlen(opt))
return PROBE_AGAIN;

return !strncmp(p, opt, strlen(opt));
}

/* Is the buffer the beginning of an HTTP connection? */
/* Says if it's HTTP, optionally with hostname list in proto->data */
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
int res;
/* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */
if (memmem(p, len, "HTTP", 4))
return PROBE_MATCH;

#define PROBE_HTTP_METHOD(opt) if ((res = probe_http_method(p, len, opt)) != PROBE_NEXT) return res

/* Otherwise it could be HTTP/1.0 without version: check if it's got an
* HTTP method (RFC2616 5.1.1) */
PROBE_HTTP_METHOD("OPTIONS");
PROBE_HTTP_METHOD("GET");
PROBE_HTTP_METHOD("HEAD");
PROBE_HTTP_METHOD("POST");
PROBE_HTTP_METHOD("PUT");
PROBE_HTTP_METHOD("DELETE");
PROBE_HTTP_METHOD("TRACE");
PROBE_HTTP_METHOD("CONNECT");

#undef PROBE_HTTP_METHOD

return PROBE_NEXT;
{
switch (parse_http_header(proto->data, p, len)) {
case HTTP_MATCH: return PROBE_MATCH;
case HTTP_NOMATCH: return PROBE_NEXT;
case HTTP_ELENGTH: return PROBE_AGAIN;
default: return PROBE_NEXT;
}
}

/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
Expand Down
1 change: 1 addition & 0 deletions probe.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "common.h"
#include "tls.h"
#include "http.h"
#include "log.h"

typedef enum {
Expand Down
22 changes: 22 additions & 0 deletions sslh-conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,22 @@ static struct config_desc table_sslhcfg_protocols[] = {
/* default_val*/ .default_val.def_bool = 0
},

{
/* name */ "hostnames",
/* type */ CFG_ARRAY,
/* sub_group*/ NULL,
/* arg_cl */ NULL,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_protocols_item, hostnames),
/* offset_len */ offsetof(struct sslhcfg_protocols_item, hostnames_len),
/* offset_present */ 0,
/* size */ sizeof(char*),
/* array_type */ CFG_STRING,
/* mandatory */ 1,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 0
},

{
/* name */ "sni_hostnames",
/* type */ CFG_ARRAY,
Expand Down Expand Up @@ -2306,6 +2322,12 @@ static void sslhcfg_protocols_fprint(
fprintf(out, "keepalive: %d", sslhcfg_protocols->keepalive);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "hostnames [%zu]:\n", sslhcfg_protocols->hostnames_len);
for (i = 0; i < sslhcfg_protocols->hostnames_len; i++) {
indent(out, depth+1);
fprintf(out, "%d:\t%s\n", i, sslhcfg_protocols->hostnames[i]);
}
indent(out, depth);
fprintf(out, "sni_hostnames [%zu]:\n", sslhcfg_protocols->sni_hostnames_len);
for (i = 0; i < sslhcfg_protocols->sni_hostnames_len; i++) {
indent(out, depth+1);
Expand Down
4 changes: 3 additions & 1 deletion sslh-conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ struct sslhcfg_protocols_item {
int transparent;
int resolve_on_forward;
int log_level;
int keepalive;
int keepalive;
size_t hostnames_len;
char **hostnames;
size_t sni_hostnames_len;
char** sni_hostnames;
size_t alpn_protocols_len;
Expand Down
17 changes: 17 additions & 0 deletions sslh-main.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ static void config_protocols()
setup_regex_probe(&cfg.protocols[i]);
}

if (!strcmp(cfg.protocols[i].name, "http")) {
cfg.protocols[i].data = (void*)new_http_data();
if (cfg.protocols[i].hostnames_len)
http_data_set_list(cfg.protocols[i].data,
(const char**) cfg.protocols[i].hostnames,
cfg.protocols[i].hostnames_len);
}

if (!strcmp(cfg.protocols[i].name, "tls")) {
cfg.protocols[i].data = (void*)new_tls_data();
if (cfg.protocols[i].sni_hostnames_len)
Expand Down Expand Up @@ -180,6 +188,15 @@ void config_sanity_check(struct sslhcfg_item* cfg)
#endif

for (i = 0; i < cfg->protocols_len; ++i) {
if (strcmp(cfg->protocols[i].name, "http")) {
if (cfg->protocols[i].hostnames_len) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option hostnames is only applicable for http\n",
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
exit(1);
}
}

if (strcmp(cfg->protocols[i].name, "tls")) {
if (cfg->protocols[i].sni_hostnames_len) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
Expand Down

0 comments on commit b928216

Please sign in to comment.