mirror of
https://github.com/moparisthebest/sslh
synced 2024-11-23 01:22:19 -05:00
Add SNI hostname based probe
This commit is contained in:
parent
3aefaf3004
commit
b988540105
4
Makefile
4
Makefile
@ -23,7 +23,7 @@ CC ?= gcc
|
|||||||
CFLAGS ?=-Wall -g $(CFLAGS_COV)
|
CFLAGS ?=-Wall -g $(CFLAGS_COV)
|
||||||
|
|
||||||
LIBS=
|
LIBS=
|
||||||
OBJS=common.o sslh-main.o probe.o
|
OBJS=common.o sslh-main.o probe.o tls.o
|
||||||
|
|
||||||
ifneq ($(strip $(USELIBWRAP)),)
|
ifneq ($(strip $(USELIBWRAP)),)
|
||||||
LIBS:=$(LIBS) -lwrap
|
LIBS:=$(LIBS) -lwrap
|
||||||
@ -63,7 +63,7 @@ sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
|
|||||||
#strip sslh-select
|
#strip sslh-select
|
||||||
|
|
||||||
echosrv: $(OBJS) echosrv.o
|
echosrv: $(OBJS) echosrv.o
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o $(LIBS)
|
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
|
||||||
|
|
||||||
$(MAN): sslh.pod Makefile
|
$(MAN): sslh.pod Makefile
|
||||||
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
|
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
|
||||||
|
@ -36,6 +36,7 @@ listen:
|
|||||||
protocols:
|
protocols:
|
||||||
(
|
(
|
||||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
|
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
|
||||||
|
{ name: "sni"; host: "localhost"; port: "993"; probe: "builtin"; sni_hostnames: [ "imap.example.org", "imap.example.com" ]; },
|
||||||
{ name: "openvpn"; host: "localhost"; port: "1194"; probe: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
|
{ name: "openvpn"; host: "localhost"; port: "1194"; probe: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
|
||||||
{ name: "xmpp"; host: "localhost"; port: "5222"; probe: [ "jabber" ]; },
|
{ name: "xmpp"; host: "localhost"; port: "5222"; probe: [ "jabber" ]; },
|
||||||
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
|
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
|
||||||
|
31
probe.c
31
probe.c
@ -216,6 +216,33 @@ static int is_http_protocol(const char *p, int len, struct proto *proto)
|
|||||||
return PROBE_NEXT;
|
return PROBE_NEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int is_sni_protocol(const char *p, int len, struct proto *proto)
|
||||||
|
{
|
||||||
|
int valid_tls;
|
||||||
|
char *hostname;
|
||||||
|
|
||||||
|
valid_tls = parse_tls_header(p, len, &hostname);
|
||||||
|
|
||||||
|
if(valid_tls < 0)
|
||||||
|
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
|
||||||
|
|
||||||
|
if (verbose) fprintf(stderr, "sni hostname: %s\n", hostname);
|
||||||
|
|
||||||
|
/* Assume does not match */
|
||||||
|
valid_tls = PROBE_NEXT;
|
||||||
|
|
||||||
|
char **sni_hostname = proto->data;
|
||||||
|
|
||||||
|
for (; *sni_hostname; sni_hostname++)
|
||||||
|
if(!strcmp(hostname, *sni_hostname)) {
|
||||||
|
valid_tls = PROBE_MATCH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(hostname);
|
||||||
|
return valid_tls;
|
||||||
|
}
|
||||||
|
|
||||||
static int is_tls_protocol(const char *p, int len, struct proto *proto)
|
static int is_tls_protocol(const char *p, int len, struct proto *proto)
|
||||||
{
|
{
|
||||||
if (len < 3)
|
if (len < 3)
|
||||||
@ -334,6 +361,10 @@ T_PROBE* get_probe(const char* description) {
|
|||||||
if (!strcmp(description, "regex"))
|
if (!strcmp(description, "regex"))
|
||||||
return regex_probe;
|
return regex_probe;
|
||||||
|
|
||||||
|
/* Special case of "sni" probe for same reason as above*/
|
||||||
|
if (!strcmp(description, "sni"))
|
||||||
|
return is_sni_protocol;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
probe.h
3
probe.h
@ -4,6 +4,7 @@
|
|||||||
#define __PROBE_H_
|
#define __PROBE_H_
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "tls.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
|
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
|
||||||
@ -23,7 +24,7 @@ struct proto {
|
|||||||
/* function to probe that protocol; parameters are buffer and length
|
/* function to probe that protocol; parameters are buffer and length
|
||||||
* containing the data to probe, and a pointer to the protocol structure */
|
* containing the data to probe, and a pointer to the protocol structure */
|
||||||
T_PROBE* probe;
|
T_PROBE* probe;
|
||||||
void* data; /* opaque pointer ; used to pass list of regex to regex probe */
|
void* data; /* opaque pointer ; used to pass list of regex to regex probe, or sni hostnames to sni probe */
|
||||||
struct proto *next; /* pointer to next protocol in list, NULL if last */
|
struct proto *next; /* pointer to next protocol in list, NULL if last */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
39
sslh-main.c
39
sslh-main.c
@ -211,13 +211,45 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LIBCONFIG
|
||||||
|
static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames)
|
||||||
|
{
|
||||||
|
int num_probes, i, max_server_name_len, server_name_len;
|
||||||
|
const char * sni_hostname;
|
||||||
|
char** sni_hostname_list;
|
||||||
|
|
||||||
|
num_probes = config_setting_length(sni_hostnames);
|
||||||
|
if (!num_probes) {
|
||||||
|
fprintf(stderr, "%s: no sni_hostnames specified\n", p->description);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
max_server_name_len = 0;
|
||||||
|
for (i = 0; i < num_probes; i++) {
|
||||||
|
server_name_len = strlen(config_setting_get_string_elem(sni_hostnames, i));
|
||||||
|
if(server_name_len > max_server_name_len)
|
||||||
|
max_server_name_len = server_name_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len);
|
||||||
|
p->data = (void*)sni_hostname_list;
|
||||||
|
|
||||||
|
for (i = 0; i < num_probes; i++) {
|
||||||
|
sni_hostname = config_setting_get_string_elem(sni_hostnames, i);
|
||||||
|
sni_hostname_list[i] = malloc(max_server_name_len);
|
||||||
|
strcpy (sni_hostname_list[i], sni_hostname);
|
||||||
|
if(verbose) fprintf(stderr, "sni_hostnames[%d]: %s\n", i, sni_hostname_list[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Extract configuration for protocols to connect to.
|
/* Extract configuration for protocols to connect to.
|
||||||
* out: newly-allocated list of protocols
|
* out: newly-allocated list of protocols
|
||||||
*/
|
*/
|
||||||
#ifdef LIBCONFIG
|
#ifdef LIBCONFIG
|
||||||
static int config_protocols(config_t *config, struct proto **prots)
|
static int config_protocols(config_t *config, struct proto **prots)
|
||||||
{
|
{
|
||||||
config_setting_t *setting, *prot, *probes;
|
config_setting_t *setting, *prot, *probes, *sni_hostnames;
|
||||||
const char *hostname, *port, *name;
|
const char *hostname, *port, *name;
|
||||||
int i, num_prots;
|
int i, num_prots;
|
||||||
struct proto *p, *prev = NULL;
|
struct proto *p, *prev = NULL;
|
||||||
@ -265,6 +297,11 @@ static int config_protocols(config_t *config, struct proto **prots)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sni_hostnames = config_setting_get_member(prot, "sni_hostnames");
|
||||||
|
if (sni_hostnames && config_setting_is_array(sni_hostnames)) {
|
||||||
|
setup_sni_hostnames(p, sni_hostnames);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
238
tls.c
Normal file
238
tls.c
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <stdlib.h> /* malloc() */
|
||||||
|
#include <string.h> /* strncpy() */
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#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 int parse_extensions(const char *, size_t, char **);
|
||||||
|
static int parse_server_name_extension(const char *, size_t, char **);
|
||||||
|
|
||||||
|
const char tls_alert[] = {
|
||||||
|
0x15, /* TLS Alert */
|
||||||
|
0x03, 0x01, /* TLS version */
|
||||||
|
0x00, 0x02, /* Payload length */
|
||||||
|
0x02, 0x28, /* Fatal, handshake failure */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
if (verbose) fprintf(stderr, "Received SSL 2.0 Client Hello which can not support SNI.\n");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
tls_content_type = data[0];
|
||||||
|
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
||||||
|
if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
|
||||||
|
return -5;
|
||||||
|
}
|
||||||
|
|
||||||
|
tls_version_major = data[1];
|
||||||
|
tls_version_minor = data[2];
|
||||||
|
if (tls_version_major < 3) {
|
||||||
|
if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which which can not support SNI.\n",
|
||||||
|
tls_version_major, tls_version_minor);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (verbose) fprintf(stderr, "Not a client hello\n");
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (verbose) fprintf(stderr, "malloc() failure\n");
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(*hostname, data + pos + 3, len);
|
||||||
|
|
||||||
|
(*hostname)[len] = '\0';
|
||||||
|
|
||||||
|
return len;
|
||||||
|
default:
|
||||||
|
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
|
||||||
|
data[pos]);
|
||||||
|
}
|
||||||
|
pos += 3 + len;
|
||||||
|
}
|
||||||
|
/* Check we ended where we expected to */
|
||||||
|
if (pos != data_len)
|
||||||
|
return -5;
|
||||||
|
|
||||||
|
return -2;
|
||||||
|
}
|
33
tls.h
Normal file
33
tls.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
|
||||||
|
* 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 "common.h"
|
||||||
|
|
||||||
|
int parse_tls_header(const char *data, size_t data_len, char **hostname);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user