mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-25 01:06:20 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a0425f4f | ||
|
|
34645a62b2 | ||
|
|
6d50082993 | ||
|
|
6219527ce4 |
No files matched your search
@@ -1,31 +1,39 @@
|
||||
v2.3.1:
|
||||
Fix segmentation fault in sslh-fork.
|
||||
ech branch:
|
||||
This is an experimental version and does not work.
|
||||
Do not use it unless you are experimenting with ECH
|
||||
and an experimental wolfSSL.
|
||||
|
||||
Add file access when using Landlock and Libwrap
|
||||
together.
|
||||
A lot of context is discussed here:
|
||||
https://github.com/wolfSSL/wolfssl/issues/10485
|
||||
|
||||
Mixed IPv4/IPv6 proxyprotocol setups will move both
|
||||
addresses to IPv6 format on the server side.
|
||||
What it does:
|
||||
- It creates a public ECH configuration for the
|
||||
first public name (configure in the `listen`
|
||||
section of the configuration file). This gets
|
||||
printed in a file configured in `ech_conf_sink`
|
||||
(see ech_test.cfg example).
|
||||
- Upon incoming connections, it will call an
|
||||
imaginary wolfSSL_GetEchClientHelloInner().
|
||||
The resulting packet is hexdumped.
|
||||
- It will also use wolfSSL to decode the SNI and
|
||||
print it (but does not yet use it to forward).
|
||||
|
||||
Add CAP_NET_RAW to systemd config so transparent
|
||||
proxying can work. Removed sslh-select systemd file:
|
||||
don't repeat the same configuration file!
|
||||
Most things happen in tls_ech.c, in particular there
|
||||
is a stub of GetEchClientHelloInner() that needs to
|
||||
be removed when the function becomes available in
|
||||
wolfSSL.
|
||||
|
||||
Changed SNI matching log messages from
|
||||
`msg_probe_error` to `msg_probe_info`: beware if you
|
||||
happen to use them...
|
||||
TODO:
|
||||
Change configure script to check for appropriate
|
||||
wolfSSL version.
|
||||
Create ECH configurations for each public name
|
||||
Link decoded InnerHello to SNI extraction code
|
||||
....
|
||||
Error cases still to be worked out (see
|
||||
conversations on github)
|
||||
Profit!
|
||||
|
||||
Close unused file descriptors upon forking a worker
|
||||
for a connection (in sslh-ev and sslh-select). In
|
||||
particular, this prevents forked processes to
|
||||
inherit the listen sockets and block a new main
|
||||
process from starting.
|
||||
|
||||
More configuration sanity checks (e.g. using regex
|
||||
or proxyprotocol options if libraries are not compiled in)
|
||||
|
||||
-V now outputs compile-time options so it is
|
||||
possible to know what is compiled in.
|
||||
|
||||
v2.3.0:
|
||||
Added `max_connections` setting to `listen` and
|
||||
|
||||
+3
-26
@@ -3,7 +3,8 @@ ARG TARGET_ARCH="library"
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
|
||||
|
||||
# Install build dependencies for sslh and libproxyprotocol
|
||||
WORKDIR /sslh
|
||||
|
||||
RUN apk add --no-cache \
|
||||
'gcc' \
|
||||
'libconfig-dev' \
|
||||
@@ -11,39 +12,15 @@ RUN apk add --no-cache \
|
||||
'musl-dev' \
|
||||
'pcre2-dev' \
|
||||
'perl' \
|
||||
'git' \
|
||||
'autoconf' \
|
||||
'automake' \
|
||||
'libtool' \
|
||||
;
|
||||
|
||||
# Build libproxyprotocol
|
||||
WORKDIR /tmp
|
||||
RUN git clone https://github.com/kosmas-valianos/libproxyprotocol.git && \
|
||||
cd libproxyprotocol && \
|
||||
make && \
|
||||
mkdir -p /usr/local/include/ && \
|
||||
mv libs/libproxyprotocol.so /usr/local/lib/ && \
|
||||
mv src/* /usr/local/include/
|
||||
|
||||
# Set environment variables for sslh to find libproxyprotocol
|
||||
ENV C_INCLUDE_PATH=/usr/local/include
|
||||
ENV LIBRARY_PATH=/usr/local/lib
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib
|
||||
|
||||
WORKDIR /sslh
|
||||
COPY . /sslh
|
||||
|
||||
# Configure and build sslh
|
||||
# The configure script should automatically detect libproxyprotocol
|
||||
RUN ./configure && make sslh-select && strip sslh-select
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
|
||||
|
||||
# Copy libproxyprotocol.so from the build stage
|
||||
COPY --from=build /usr/local/lib/libproxyprotocol.so* /usr/local/lib/
|
||||
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
|
||||
|
||||
RUN apk add --no-cache \
|
||||
'libconfig' \
|
||||
'pcre2' \
|
||||
@@ -58,4 +35,4 @@ COPY "./container-entrypoint.sh" "/init"
|
||||
ENTRYPOINT [ "/init" ]
|
||||
|
||||
# required for updating iptables
|
||||
USER root:root
|
||||
USER root:root
|
||||
+3
-3
@@ -22,11 +22,11 @@ MAN=sslh.8.gz # man page name
|
||||
# itself
|
||||
|
||||
ifneq ($(strip $(ENABLE_SANITIZER)),)
|
||||
CFLAGS_SAN=-DENABLE_SANITIZER -fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
|
||||
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(COV_TEST)),)
|
||||
CFLAGS_COV=-DCOV_TEST -fprofile-arcs -ftest-coverage
|
||||
CFLAGS_COV=-fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
|
||||
CC ?= gcc
|
||||
@@ -35,7 +35,7 @@ CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
|
||||
LIBS=-lm -lpcre2-8 @LIBS@
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o tls_ech.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.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)
|
||||
|
||||
+2
-10
@@ -28,7 +28,6 @@
|
||||
/* Info to keep track of all connections */
|
||||
struct cnx_collection {
|
||||
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
|
||||
int max_fd;
|
||||
};
|
||||
|
||||
/* Allocates and initialises a new collection of connections with at least
|
||||
@@ -43,7 +42,6 @@ cnx_collection* collection_init(int len)
|
||||
memset(collection, 0, sizeof(*collection));
|
||||
|
||||
collection->fd2cnx = gap_init(len);
|
||||
collection->max_fd = 0;
|
||||
|
||||
return collection;
|
||||
}
|
||||
@@ -56,17 +54,10 @@ void collection_destroy(cnx_collection* collection)
|
||||
free(collection);
|
||||
}
|
||||
|
||||
int collection_max_fd(cnx_collection* collection)
|
||||
{
|
||||
return collection->max_fd;
|
||||
}
|
||||
|
||||
/* Points the file descriptor to the specified connection index */
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
|
||||
{
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
if (fd > collection->max_fd)
|
||||
collection->max_fd = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -83,7 +74,8 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio
|
||||
cnx->state = ST_PROBING;
|
||||
cnx->probe_timeout = time(NULL) + cfg.timeout;
|
||||
|
||||
collection_add_fd(collection, cnx, fd);
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
|
||||
return cnx;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ void collection_destroy(cnx_collection* collection);
|
||||
|
||||
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
|
||||
int collection_max_fd(cnx_collection* collection);
|
||||
|
||||
/* Remove a connection from the collection */
|
||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
|
||||
|
||||
@@ -422,9 +422,9 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
|
||||
cnx->proto->port);
|
||||
}
|
||||
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
||||
/* When transparent, make sure both connections use
|
||||
* the same address family (e.g. IP4 on both sides) */
|
||||
if (transparent && (a->ai_family != from.ai_addr->sa_family))
|
||||
/* When transparent or using proxyprotocol, make sure both
|
||||
* connections use the same address family (e.g. IP4 on both sides) */
|
||||
if ((transparent || cnx->proto->proxyprotocol_is_present) && (a->ai_family != from.ai_addr->sa_family))
|
||||
continue;
|
||||
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
||||
sprintaddr(buf, sizeof(buf), a),
|
||||
@@ -899,7 +899,7 @@ int check_access_rights(int in_socket, const char* service)
|
||||
}
|
||||
|
||||
if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) {
|
||||
print_message(msg_connections, "connection from %s(%s): access denied\n", host, addr_str);
|
||||
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "config.h"
|
||||
#include "version.h"
|
||||
|
||||
#undef MAX
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
@@ -204,7 +205,7 @@ extern int hosts_ctl();
|
||||
#endif
|
||||
|
||||
/* sslh-fork.c */
|
||||
void main_inetd(void);
|
||||
void start_shoveler(int);
|
||||
|
||||
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
||||
|
||||
|
||||
@@ -18,4 +18,7 @@
|
||||
/* libbsd, to change process name */
|
||||
#undef HAVE_LIBBSD
|
||||
|
||||
/* wolfssl, to support Encrypted Client Hello (ECH) */
|
||||
#undef HAVE_WOLFSSL
|
||||
|
||||
#endif
|
||||
@@ -3329,6 +3329,47 @@ then :
|
||||
LIBS="$LIBS -lbsd"
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wolfSSL_Init in -lwolfssl" >&5
|
||||
printf %s "checking for wolfSSL_Init in -lwolfssl... " >&6; }
|
||||
if test ${ac_cv_lib_wolfssl_wolfSSL_Init+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lwolfssl $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
char wolfSSL_Init ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return wolfSSL_Init ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_wolfssl_wolfSSL_Init=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_wolfssl_wolfSSL_Init=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.beam \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wolfssl_wolfSSL_Init" >&5
|
||||
printf "%s\n" "$ac_cv_lib_wolfssl_wolfSSL_Init" >&6; }
|
||||
if test "x$ac_cv_lib_wolfssl_wolfSSL_Init" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_WOLFSSL 1" >>confdefs.h
|
||||
LIBS="$LIBS -lwolfssl"
|
||||
fi
|
||||
|
||||
|
||||
ac_header= ac_cache=
|
||||
for ac_item in $ac_header_c_list
|
||||
|
||||
@@ -11,6 +11,9 @@ AC_CHECK_LIB([wrap], [hosts_ctl], [AC_DEFINE(HAVE_LIBWRAP) LIBS="$LIBS -lwrap" ]
|
||||
AC_CHECK_LIB([cap], [cap_get_proc], [AC_DEFINE(HAVE_LIBCAP) LIBS="$LIBS -lcap" ], [])
|
||||
AC_CHECK_LIB([bsd], [setproctitle], [AC_DEFINE(HAVE_LIBBSD) LIBS="$LIBS -lbsd" ], [])
|
||||
|
||||
dnl TODO: check that wolfssl contains wolfSSL_GetEchClientHelloInner instead of just existing
|
||||
AC_CHECK_LIB([wolfssl], [wolfSSL_Init], [AC_DEFINE(HAVE_WOLFSSL) LIBS="$LIBS -lwolfssl" ], [])
|
||||
|
||||
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
|
||||
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "2222"; },
|
||||
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
|
||||
);
|
||||
);
|
||||
```
|
||||
|
||||
@@ -76,6 +77,7 @@ protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
|
||||
{ name: "tls"; host: "localhost"; port: "8080"; }
|
||||
);
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# sslh configuration for saaz:
|
||||
# ssh to localhost
|
||||
# https to nextcloud
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
user: "yves";
|
||||
pidfile: "/tmp/sslh.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Where to write the ECH configurations
|
||||
ech_conf_sink: "/tmp/ech";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
|
||||
|
||||
|
||||
verbose: 1;
|
||||
|
||||
|
||||
# Change hostname with your external address name.
|
||||
listen:
|
||||
(
|
||||
{ host: "inet"; port: "4443"; },
|
||||
{ host: "chaton"; port: "4445"; }
|
||||
);
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||
{ name: "tls"; sni_hostnames: [
|
||||
"inet.rutschle.net", "lists.rutschle.net"
|
||||
];
|
||||
host: "localhost"; port: "4453"; },
|
||||
{ name: "tls"; sni_hostnames: [
|
||||
"chaton.rutschle.net"
|
||||
];
|
||||
host: "localhost"; port: "4455"; }
|
||||
);
|
||||
|
||||
|
||||
on-timeout: "ssh";
|
||||
+1
-1
@@ -118,7 +118,7 @@ static int add_resolv(int ruleset_fd)
|
||||
static int add_libwrap(int ruleset_fd)
|
||||
{
|
||||
/* Files for libwrap */
|
||||
#ifdef HAVE_LIBWRAP
|
||||
#ifdef LIBWRAP
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
|
||||
#endif
|
||||
|
||||
+1
-2
@@ -196,7 +196,7 @@ static int next_power_of_two(int n)
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen)
|
||||
void loop_init(struct loop_info* loop, int num_addr_listen)
|
||||
{
|
||||
memset(loop, 0, sizeof(*loop));
|
||||
loop->collection = collection_init(0);
|
||||
@@ -206,7 +206,6 @@ void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, i
|
||||
udp_init(loop);
|
||||
tcp_init();
|
||||
|
||||
loop->listen_sockets = listen_sockets;
|
||||
loop->num_addr_listen = num_addr_listen;
|
||||
|
||||
int max_forks = next_power_of_two(max_forking_connections());
|
||||
|
||||
+1
-5
@@ -23,8 +23,6 @@ struct loop_info {
|
||||
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
|
||||
|
||||
watchers* watchers;
|
||||
|
||||
struct listen_endpoint* listen_sockets;
|
||||
int num_addr_listen; /* How many listen endpoints do we have here */
|
||||
|
||||
cnx_collection* collection; /* Collection of connections linked to this loop */
|
||||
@@ -34,7 +32,7 @@ void cnx_read_process(struct loop_info* fd_info, int fd);
|
||||
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket);
|
||||
|
||||
int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
|
||||
void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen);
|
||||
void loop_init(struct loop_info* loop, int num_addr_listen);
|
||||
|
||||
void remember_child_data(struct loop_info* fd_info,
|
||||
struct connection* cnx, pid_t pid);
|
||||
@@ -49,6 +47,4 @@ void watchers_del_write(watchers* w, int fd);
|
||||
|
||||
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
|
||||
|
||||
void close_listen_endpoints(struct loop_info* loop);
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -112,7 +112,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
|
||||
if (res == -1) return -1;
|
||||
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
|
||||
|
||||
res = get_info(cnx->q[0].fd, SOCK,
|
||||
res = get_info(cnx->q[1].fd, SOCK,
|
||||
&addr,
|
||||
&pp_info_in_v1.dst_addr,
|
||||
&pp_info_in_v1.dst_port
|
||||
@@ -122,7 +122,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
|
||||
uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error);
|
||||
|
||||
if (!pp1_hdr) {
|
||||
print_message(msg_system_error, "pp_create_hdr:%d:%s\n", error, pp_strerror(error));
|
||||
print_message(msg_system_error, "pp_create_hrd:%d:%s\n", error, pp_strerror(error));
|
||||
return -1;
|
||||
}
|
||||
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer (select mode) for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/default/sslh
|
||||
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
SecureBits=noroot-locked
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
MountFlags=private
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
MemoryDenyWriteExecute=true
|
||||
DynamicUser=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,5 +1,5 @@
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer for %I
|
||||
Description=SSL/SSH multiplexer (fork mode) for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
@@ -8,8 +8,8 @@ ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
CapabilityBoundingSet=CAP_SETGIT CAP_SETUID CAP_NET_BIND_SERVICE CAP_NET_RAW
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
SecureBits=noroot-locked
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
|
||||
+62
-16
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Wed Sep 10 15:49:49 2025.
|
||||
* on Sat Jul 18 17:34:37 2026.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
@@ -482,6 +482,8 @@ struct arg_file* sslhcfg_conffile;
|
||||
struct arg_str* sslhcfg_chroot;
|
||||
struct arg_str* sslhcfg_syslog_facility;
|
||||
struct arg_str* sslhcfg_logfile;
|
||||
struct arg_str* sslhcfg_ech_conf_sink;
|
||||
struct arg_str* sslhcfg_ech_public_name;
|
||||
struct arg_str* sslhcfg_on_timeout;
|
||||
struct arg_str* sslhcfg_prefix;
|
||||
struct arg_str* sslhcfg_listen;
|
||||
@@ -926,7 +928,7 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg[] = {
|
||||
|
||||
|
||||
@@ -1314,6 +1316,38 @@ static struct config_desc table_sslhcfg[] = {
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "ech_conf_sink",
|
||||
/* type */ CFG_STRING,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_ech_conf_sink,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, ech_conf_sink),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_item, ech_conf_sink_is_present),
|
||||
/* size */ sizeof(char*),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "ech_public_name",
|
||||
/* type */ CFG_STRING,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_ech_public_name,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, ech_public_name),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_item, ech_public_name_is_present),
|
||||
/* size */ sizeof(char*),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "on_timeout",
|
||||
/* type */ CFG_STRING,
|
||||
@@ -1500,7 +1534,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: listen */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_listen,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [28],
|
||||
.targets = sslhcfg_listen_targets,
|
||||
|
||||
|
||||
@@ -1512,7 +1546,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssh */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssh,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_ssh_targets,
|
||||
|
||||
|
||||
@@ -1524,7 +1558,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tls */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tls,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_tls_targets,
|
||||
|
||||
|
||||
@@ -1536,7 +1570,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssl */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssl,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_ssl_targets,
|
||||
|
||||
|
||||
@@ -1548,7 +1582,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: openvpn */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_openvpn,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_openvpn_targets,
|
||||
|
||||
|
||||
@@ -1560,7 +1594,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tinc */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tinc,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_tinc_targets,
|
||||
|
||||
|
||||
@@ -1572,7 +1606,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: wireguard */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_wireguard,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_wireguard_targets,
|
||||
|
||||
|
||||
@@ -1584,7 +1618,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: xmpp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_xmpp,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_xmpp_targets,
|
||||
|
||||
|
||||
@@ -1596,7 +1630,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: http */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_http,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_http_targets,
|
||||
|
||||
|
||||
@@ -1608,7 +1642,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: adb */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_adb,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_adb_targets,
|
||||
|
||||
|
||||
@@ -1620,7 +1654,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: socks5 */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_socks5,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_socks5_targets,
|
||||
|
||||
|
||||
@@ -1632,7 +1666,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: syslog */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_syslog,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_syslog_targets,
|
||||
|
||||
|
||||
@@ -1644,7 +1678,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: msrdp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_msrdp,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_msrdp_targets,
|
||||
|
||||
|
||||
@@ -1656,7 +1690,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: anyprot */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_anyprot,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_anyprot_targets,
|
||||
|
||||
|
||||
@@ -2345,6 +2379,8 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
|
||||
sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
|
||||
sslhcfg_logfile = arg_strn(NULL, "logfile", "<str>", 0, 1, "Log messages to a file"),
|
||||
sslhcfg_ech_conf_sink = arg_strn(NULL, "ech-conf-sink", "<str>", 0, 1, "File where to write ECH configurations"),
|
||||
sslhcfg_ech_public_name = arg_strn(NULL, "ech-public-name", "<str>", 0, 1, "Public name for ECH client-facing proxy (usually, sslh's address)"),
|
||||
sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
|
||||
sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
|
||||
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
|
||||
@@ -2621,6 +2657,16 @@ void sslhcfg_fprint(
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "ech_conf_sink: %s", sslhcfg->ech_conf_sink);
|
||||
if (! sslhcfg->ech_conf_sink_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "ech_public_name: %s", sslhcfg->ech_public_name);
|
||||
if (! sslhcfg->ech_public_name_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Wed Sep 10 15:49:49 2025.
|
||||
* on Sat Jul 18 17:34:37 2026.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "probe.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <netdb.h>
|
||||
|
||||
struct sslhcfg_listen_item {
|
||||
@@ -114,6 +115,10 @@ struct sslhcfg_item {
|
||||
char* syslog_facility;
|
||||
int logfile_is_present;
|
||||
char* logfile;
|
||||
int ech_conf_sink_is_present;
|
||||
char* ech_conf_sink;
|
||||
int ech_public_name_is_present;
|
||||
char* ech_public_name;
|
||||
char* on_timeout;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
|
||||
@@ -207,12 +207,13 @@ void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pi
|
||||
ev_child_start(EV_DEFAULT_ cw);
|
||||
}
|
||||
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
struct loop_info ev_info;
|
||||
loop = EV_DEFAULT;
|
||||
|
||||
loop_init(&ev_info, listen_sockets, num_addr_listen);
|
||||
loop_init(&ev_info, num_addr_listen);
|
||||
|
||||
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
|
||||
ev_set_userdata(EV_A_ &ev_info);
|
||||
@@ -220,7 +221,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
ev_run(EV_A_ 0);
|
||||
}
|
||||
|
||||
void main_inetd(void) {
|
||||
void start_shoveler(int listen_socket) {
|
||||
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
+2
-19
@@ -69,7 +69,7 @@ int shovel(struct connection *cnx)
|
||||
|
||||
/* Child process that finds out what to connect to and proxies
|
||||
*/
|
||||
static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
|
||||
void start_shoveler(int in_socket)
|
||||
{
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
@@ -79,7 +79,6 @@ static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
|
||||
|
||||
init_cnx(&cnx);
|
||||
cnx.q[0].fd = in_socket;
|
||||
cnx.endpoint = endpoint;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(in_socket, &fds);
|
||||
@@ -131,22 +130,6 @@ static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
void main_inetd(void)
|
||||
{
|
||||
struct listen_endpoint endpoint = {0};
|
||||
struct sslhcfg_listen_item endpoint_cfg = {0};
|
||||
|
||||
/* Empty configuration: no connection limits, not proxyprotocol... */
|
||||
endpoint.endpoint_cfg = &endpoint_cfg;
|
||||
|
||||
close(fileno(stderr)); /* Make sure no error will go to client */
|
||||
tcp_init();
|
||||
start_shoveler(0, &endpoint);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
static pid_t *listener_pid;
|
||||
static int listener_pid_number = 0;
|
||||
|
||||
@@ -269,7 +252,7 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
||||
/* Shoveler processes don't need to hog file descriptors */
|
||||
for (i = 0; i < num_endpoints; i++)
|
||||
close(endpoint[i].socketfd);
|
||||
start_shoveler(in_socket, endpoint);
|
||||
start_shoveler(in_socket);
|
||||
exit(0);
|
||||
|
||||
default: /* In parent process */
|
||||
|
||||
+7
-101
@@ -220,62 +220,6 @@ static void config_protocols()
|
||||
}
|
||||
}
|
||||
|
||||
static void config_sanity_regex(const struct sslhcfg_protocols_item* prot)
|
||||
{
|
||||
if (prot->regex_patterns_len > 0) {
|
||||
if (strcmp(prot->name, "regex") != 0) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"regex_patterns setting is only applicable to `regex' probe. Ignoring setting.\n",
|
||||
prot->name, prot->host, prot->port);
|
||||
}
|
||||
#ifndef ENABLE_REGEX
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"Uses regex_patterns, but libpcre2 support was not compiled in. Ignoring setting.\n",
|
||||
prot->name, prot->host, prot->port);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* If user specified proxyprotocol but it is not compiled in, it should not be
|
||||
* ignored as the resulting configuration will most likely not work. In that
|
||||
* case, die. */
|
||||
static void config_sanity_proxyprotocol(const struct sslhcfg_protocols_item* prot)
|
||||
{
|
||||
#ifndef HAVE_PROXYPROTOCOL
|
||||
if (prot->proxyprotocol_is_present) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"Uses proxyprotocol, but libproxyprotocol support was not compiled in.\n",
|
||||
prot->name, prot->host, prot->port);
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void config_sanity_listen_proxyprotocol(const struct sslhcfg_listen_item* endpoint)
|
||||
{
|
||||
#ifndef HAVE_PROXYPROTOCOL
|
||||
if (endpoint->proxyprotocol) {
|
||||
print_message(msg_config_error, "listen on host: \"%s\"; port: \"%s\": "
|
||||
"Uses proxyprotocol, but libproxyprotocol support was not compiled in.\n",
|
||||
endpoint->host, endpoint->port);
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* If user specifies libwrap setting but it is not compiled in, it should
|
||||
* result in a working configuration, but not what they expected, so warn */
|
||||
static void config_sanity_libwrap(const struct sslhcfg_protocols_item* prot)
|
||||
{
|
||||
#ifndef HAVE_LIBWRAP
|
||||
if (prot->service_is_present) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"WARNING: `service' specified but libwrap not compiled in. Ignoring setting.\n",
|
||||
prot->name, prot->host, prot->port);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
{
|
||||
@@ -289,10 +233,6 @@ void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 0; i < cfg->listen_len; i++) {
|
||||
config_sanity_listen_proxyprotocol(&cfg->listen[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < cfg->protocols_len; ++i) {
|
||||
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
|
||||
if (cfg->protocols[i].sni_hostnames_len) {
|
||||
@@ -309,10 +249,6 @@ void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
}
|
||||
}
|
||||
|
||||
config_sanity_regex(&cfg->protocols[i]);
|
||||
config_sanity_proxyprotocol(&cfg->protocols[i]);
|
||||
config_sanity_libwrap(&cfg->protocols[i]);
|
||||
|
||||
if (cfg->protocols[i].is_udp) {
|
||||
if (cfg->protocols[i].tfo_ok) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
@@ -348,41 +284,6 @@ void close_std(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void print_version(void)
|
||||
{
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
#ifdef ENABLE_SANITIZER
|
||||
printf("ENABLE_SANITIZER\n");
|
||||
#endif
|
||||
#ifdef ENABLE_REGEX
|
||||
printf("ENABLE_REGEX\n");
|
||||
#endif
|
||||
#ifdef LIBCONFIG
|
||||
printf("LIBCONFIG\n");
|
||||
#endif
|
||||
#ifdef SYSTEMD
|
||||
printf("SYSTEMD\n");
|
||||
#endif
|
||||
#ifdef COV_TEST
|
||||
printf("COV_TEST\n");
|
||||
#endif
|
||||
#ifdef HAVE_LIBWRAP
|
||||
printf("HAVE_LIBWRAP\n");
|
||||
#endif
|
||||
#ifdef HAVE_LANDLOCK
|
||||
printf("HAVE_LANDLOCK\n");
|
||||
#endif
|
||||
#ifdef HAVE_PROXYPROTOCOL
|
||||
printf("HAVE_PROXYPROTOCOL\n");
|
||||
#endif
|
||||
#ifdef HAVE_LIBCAP
|
||||
printf("HAVE_LIBCAP\n");
|
||||
#endif
|
||||
#ifdef HAVE_LIBBSD
|
||||
printf("HAVE_LIBBSD\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[], char* envp[])
|
||||
{
|
||||
int res, num_addr_listen;
|
||||
@@ -398,7 +299,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
config_finish(&cfg);
|
||||
|
||||
if (cfg.version) {
|
||||
print_version();
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -407,7 +308,10 @@ int main(int argc, char *argv[], char* envp[])
|
||||
|
||||
if (cfg.inetd)
|
||||
{
|
||||
main_inetd(); /* Does not return */
|
||||
close(fileno(stderr)); /* Make sure no error will go to client */
|
||||
tcp_init();
|
||||
start_shoveler(0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
printsettings();
|
||||
@@ -443,6 +347,8 @@ int main(int argc, char *argv[], char* envp[])
|
||||
/* Open log file for writing */
|
||||
setup_logfile();
|
||||
|
||||
ech_init(listen_sockets, num_addr_listen);
|
||||
|
||||
if (cfg.user || cfg.chroot)
|
||||
drop_privileges(cfg.user, cfg.chroot);
|
||||
setup_landlock();
|
||||
|
||||
+2
-2
@@ -204,7 +204,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
int i, res;
|
||||
|
||||
setup_sigalrm();
|
||||
loop_init(&fd_info, listen_sockets, num_addr_listen);
|
||||
loop_init(&fd_info, num_addr_listen);
|
||||
|
||||
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
|
||||
|
||||
@@ -292,7 +292,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
}
|
||||
|
||||
|
||||
void main_inetd(void) {
|
||||
void start_shoveler(int listen_socket) {
|
||||
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ includes: (
|
||||
"probe.h",
|
||||
" <sys/types.h>",
|
||||
" <sys/socket.h>",
|
||||
" <wolfssl/ssl.h>",
|
||||
" <netdb.h>"
|
||||
);
|
||||
|
||||
@@ -85,6 +86,8 @@ config: {
|
||||
description: "Facility to syslog to"; },
|
||||
{ name: "logfile"; type: "string"; optional: true;
|
||||
description: "Log messages to a file" },
|
||||
{ name: "ech_conf_sink"; type: "string"; optional: true;
|
||||
description: "File where to write ECH configurations" },
|
||||
|
||||
{ name: "on-timeout"; type: "string"; default: "ssh";
|
||||
description: "Target to connect to when timing out"; },
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
# ./run # run all tests
|
||||
# ./run -l # list all tests
|
||||
# ./run 1 3 5 # run specified tests
|
||||
# ./run --binary sslh-ev # run only one binary
|
||||
|
||||
use strict;
|
||||
use IO::Socket::INET6;
|
||||
@@ -18,13 +17,9 @@ use Test::More qw/no_plan/;
|
||||
use Conf::Libconfig 1.0.3;
|
||||
use Getopt::Long;
|
||||
|
||||
# --cover: request test coverage analysis (not implemented)
|
||||
# --binary: specify which binary to run (default: all)
|
||||
# --list: list all available tests (do not run any)
|
||||
my ($coverage, $list_tests, $param_binary);
|
||||
my ($coverage, $list_tests);
|
||||
GetOptions(
|
||||
'cover' => \$coverage,
|
||||
'binary=s' => \$param_binary,
|
||||
'list' => \$list_tests,
|
||||
);
|
||||
|
||||
@@ -197,8 +192,7 @@ sub run_ending_test
|
||||
|
||||
# Runs one test for one probe. Start echosrv's if required.
|
||||
# Connect to port specified in the test, otherwise to the
|
||||
# first port in the sslh configuration; to host specified in
|
||||
# the test, otherwise to localhost
|
||||
# first port in the sslh configuration.
|
||||
# run sslh, connect, write the test pattern, read the result,
|
||||
# check it connected to the right echosrv, check the data
|
||||
# was transfered ok.
|
||||
@@ -212,10 +206,9 @@ sub run_test_probe
|
||||
|
||||
my $expected = $test->{expected};
|
||||
|
||||
my $sslh_host = $test->{host} // "localhost";
|
||||
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
|
||||
print "test_probe [$expected] $sslh_host:$sslh_port\n";
|
||||
my $cnx = new IO::Socket::INET(PeerHost => "$sslh_host:$sslh_port");
|
||||
print "test_probe [$expected] $sslh_port\n";
|
||||
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "t: $!\n" unless $cnx;
|
||||
return unless $cnx;
|
||||
|
||||
@@ -323,7 +316,6 @@ my @tests = (
|
||||
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
|
||||
},
|
||||
|
||||
|
||||
# check socks5 does not
|
||||
{
|
||||
desc => "Server-side ProxyProtocol not active",
|
||||
@@ -333,55 +325,6 @@ my @tests = (
|
||||
expected => "socks5",
|
||||
},
|
||||
|
||||
|
||||
# Check proxyprotocol from IPv4 to IPv4 works.
|
||||
{
|
||||
desc => "Server-side IPv4 to IPv4 ProxyProtocol active",
|
||||
host => "ip4-localhost",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp3.cfg",
|
||||
data => "SSH-2.0 hello",
|
||||
expected => "ssh",
|
||||
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
|
||||
},
|
||||
|
||||
|
||||
# Check pp with IPv6 to IPv6 works.
|
||||
{
|
||||
desc => "Server-side IPv6 to IPv6 ProxyProtocol active",
|
||||
host => "ip6-localhost",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp4.cfg",
|
||||
data => "SSH-2.0 hello",
|
||||
expected => "ssh",
|
||||
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
|
||||
},
|
||||
|
||||
|
||||
# Check pp with IPv4 to IPv6 works.
|
||||
{
|
||||
desc => "Server-side IPv4 to IPv6 ProxyProtocol active",
|
||||
host => "ip4-localhost",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp5.cfg",
|
||||
data => "SSH-2.0 hello",
|
||||
expected => "ssh",
|
||||
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
|
||||
},
|
||||
|
||||
|
||||
# Check pp with IPv6 to IPv4 works.
|
||||
{
|
||||
desc => "Server-side IPv6 to IPv4 ProxyProtocol active",
|
||||
host => "ip6-localhost",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp5.cfg",
|
||||
data => "SSH-2.0 hello",
|
||||
expected => "ssh",
|
||||
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
|
||||
},
|
||||
|
||||
|
||||
# test_pp2.cfg has proxyprotocol on port 8080
|
||||
{
|
||||
desc => "Client-side ProxyProtocol active",
|
||||
@@ -445,15 +388,10 @@ if ($list_tests) {
|
||||
|
||||
################################################################################
|
||||
# Run selected tests
|
||||
|
||||
my @binaries = qw/sslh-fork sslh-select sslh-ev/;
|
||||
@binaries = $param_binary if defined $param_binary;
|
||||
|
||||
foreach my $binary (@binaries) {
|
||||
foreach my $test (@tests) {
|
||||
warn "Running test: $test->{desc}\n";
|
||||
my $code = ($test->{run})->($binary, $test);
|
||||
}
|
||||
foreach my $test (@tests) {
|
||||
warn "Running test: $test->{desc}\n";
|
||||
my $binary = 'sslh-ev';
|
||||
my $code = ($test->{run})->($binary, $test);
|
||||
}
|
||||
|
||||
stop_echosrv();
|
||||
|
||||
+4
-4
@@ -29,15 +29,15 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "ip4-localhost"; port: "8080"; keepalive: true; }
|
||||
{ host: "localhost"; port: "8080"; keepalive: true; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
|
||||
{ name: "socks5"; host: "ip4-localhost"; port: "9001"; },
|
||||
{ name: "anyprot"; host: "ip4-localhost"; port: "9099"; }
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; proxyprotocol: 1; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Testing for server-side ProxyProtocol, IPv4 to IPv4
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: true;
|
||||
transparent: false;
|
||||
timeout: 10; # Probe test writes slowly
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: both
|
||||
# Defaults should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
verbose-config: 1; # print configuration at startup
|
||||
verbose-config-error: 1; # print configuration errors
|
||||
verbose-connections: 1; # trace established incoming address to forward address
|
||||
verbose-connections-error: 1; # connection errors
|
||||
verbose-connections-try: 1; # connection attempts towards targets
|
||||
verbose-fd: 1; # file descriptor activity, open/close/whatnot
|
||||
verbose-packets: 1; # hexdump packets on which probing is done
|
||||
verbose-probe-info: 1; # what's happening during the probe process
|
||||
verbose-probe-error: 1; # failures and problems during probing
|
||||
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "ip4-localhost"; port: "8080"; keepalive: true; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Testing for server-side ProxyProtocol, IPv6 to IPv6
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: true;
|
||||
transparent: false;
|
||||
timeout: 10; # Probe test writes slowly
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: both
|
||||
# Defaults should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
verbose-config: 1; # print configuration at startup
|
||||
verbose-config-error: 1; # print configuration errors
|
||||
verbose-connections: 1; # trace established incoming address to forward address
|
||||
verbose-connections-error: 1; # connection errors
|
||||
verbose-connections-try: 1; # connection attempts towards targets
|
||||
verbose-fd: 1; # file descriptor activity, open/close/whatnot
|
||||
verbose-packets: 1; # hexdump packets on which probing is done
|
||||
verbose-probe-info: 1; # what's happening during the probe process
|
||||
verbose-probe-error: 1; # failures and problems during probing
|
||||
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "ip6-localhost"; port: "8080"; keepalive: true; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "ip6-localhost"; port: "9000"; proxyprotocol: 1; },
|
||||
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
|
||||
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Testing for server-side ProxyProtocol, IPv4 to IPv6
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: true;
|
||||
transparent: false;
|
||||
timeout: 10; # Probe test writes slowly
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: both
|
||||
# Defaults should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
verbose-config: 1; # print configuration at startup
|
||||
verbose-config-error: 1; # print configuration errors
|
||||
verbose-connections: 1; # trace established incoming address to forward address
|
||||
verbose-connections-error: 1; # connection errors
|
||||
verbose-connections-try: 1; # connection attempts towards targets
|
||||
verbose-fd: 1; # file descriptor activity, open/close/whatnot
|
||||
verbose-packets: 1; # hexdump packets on which probing is done
|
||||
verbose-probe-info: 1; # what's happening during the probe process
|
||||
verbose-probe-error: 1; # failures and problems during probing
|
||||
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "ip4-localhost"; port: "8080"; keepalive: true; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "ip6-localhost"; port: "9000"; proxyprotocol: 1; },
|
||||
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
|
||||
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Testing for server-side ProxyProtocol, IPv6 to IPv4
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: true;
|
||||
transparent: false;
|
||||
timeout: 10; # Probe test writes slowly
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: both
|
||||
# Defaults should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
verbose-config: 1; # print configuration at startup
|
||||
verbose-config-error: 1; # print configuration errors
|
||||
verbose-connections: 1; # trace established incoming address to forward address
|
||||
verbose-connections-error: 1; # connection errors
|
||||
verbose-connections-try: 1; # connection attempts towards targets
|
||||
verbose-fd: 1; # file descriptor activity, open/close/whatnot
|
||||
verbose-packets: 1; # hexdump packets on which probing is done
|
||||
verbose-probe-info: 1; # what's happening during the probe process
|
||||
verbose-probe-error: 1; # failures and problems during probing
|
||||
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "ip6-localhost"; port: "8080"; keepalive: true; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
|
||||
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
|
||||
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
+2
-33
@@ -296,37 +296,6 @@ static void connect_proxy(struct connection *cnx)
|
||||
}
|
||||
|
||||
|
||||
/* This ensures the kernel will close the listening socket when the
|
||||
* parent process closes; otherwise, the listening socket is kept
|
||||
* around by the inactive file descriptor in the child */
|
||||
void close_listen_endpoints(struct loop_info* loop)
|
||||
{
|
||||
struct listen_endpoint* listen_sockets = loop->listen_sockets;
|
||||
watchers* w = loop->watchers;
|
||||
|
||||
for (int i = 0; i < loop->num_addr_listen; i++) {
|
||||
int fd = listen_sockets[i].socketfd;
|
||||
watchers_del_read(w, fd);
|
||||
watchers_del_write(w, fd);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Close all connections except specified.
|
||||
* This saves file descriptors and allocated memory when forking */
|
||||
void tidy_other_connections(struct loop_info* fd_info, struct connection* keep_cnx)
|
||||
{
|
||||
struct connection* cnx;
|
||||
cnx_collection* cnx_coll = fd_info->collection;
|
||||
|
||||
for (int i = 0; i < collection_max_fd(cnx_coll); i++) {
|
||||
cnx = collection_get_cnx_from_fd(cnx_coll, i);
|
||||
if (cnx && (cnx != keep_cnx))
|
||||
tidy_connection(cnx, fd_info);
|
||||
}
|
||||
}
|
||||
|
||||
/* Forks a shoveler process for one protocol */
|
||||
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
||||
pid_t pid;
|
||||
@@ -338,8 +307,8 @@ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
||||
|
||||
switch (pid = fork()) {
|
||||
case 0: /* child */
|
||||
close_listen_endpoints(fd_info);
|
||||
tidy_other_connections(fd_info, cnx);
|
||||
/* TODO: close all file descriptors except 2 */
|
||||
/* free(cnx); */
|
||||
connect_proxy(cnx);
|
||||
exit(0);
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
|
||||
@@ -61,6 +61,7 @@ static int parse_server_name_extension(const struct TLSProtocol *, const char *,
|
||||
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
|
||||
static int has_match(const char**, size_t, const char*, size_t);
|
||||
|
||||
|
||||
/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client
|
||||
* hello handshake, returning a status code
|
||||
*
|
||||
@@ -81,6 +82,11 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
if (data_len < TLS_HEADER_LEN)
|
||||
return TLS_ELENGTH;
|
||||
|
||||
/* If if it is an ECH, decrypt and match */
|
||||
ech_match_sni(data, data_len);
|
||||
|
||||
|
||||
|
||||
tls_content_type = data[0];
|
||||
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
||||
print_message(msg_probe_info, "Request did not begin with TLS handshake.\n");
|
||||
@@ -277,7 +283,7 @@ has_match(const char** list, size_t list_len, const char* name, size_t name_len)
|
||||
|
||||
for (i = 0; i < list_len; i++) {
|
||||
item = &list[i];
|
||||
print_message(msg_probe_info, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
||||
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;
|
||||
|
||||
@@ -44,5 +44,6 @@ struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, const char**, s
|
||||
#define TLS_ENOEXT -4 /* No ALPN or SNI extension found */
|
||||
#define TLS_EPROTOCOL -5 /* Protocol error */
|
||||
|
||||
void ech_match_sni(const char* buffer_in, size_t buffer_len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
# tls_ech.c: support for RFC9849 TLS Encrypted Client Hello (ECH)
|
||||
#
|
||||
# Copyright (C) 2026 Yves Rutschle
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#define WOLFSSL_USE_OPTION_H
|
||||
#include <wolfssl/options.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <wolfssl/wolfcrypt/random.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "tls.h"
|
||||
|
||||
WOLFSSL_CTX* wolfssl_ctx;
|
||||
|
||||
|
||||
// Hard-coded server certificate (PEM format)
|
||||
// from snakeoil -- these serves no purpose beyond initialising the context
|
||||
static const char* SERVER_CERT_PEM =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICyjCCAbKgAwIBAgIUZrCzD4tEIu1r9EhkOn96raiupe4wDQYJKoZIhvcNAQEL\n"
|
||||
"BQAwDzENMAsGA1UEAwwEc2FhejAeFw0yMTA1MjgyMDM0MjRaFw0zMTA1MjYyMDM0\n"
|
||||
"MjRaMA8xDTALBgNVBAMMBHNhYXowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
|
||||
"AoIBAQC5s1UhjcmsrRnSdJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6J\n"
|
||||
"MAL1MbPtX0pfR+yBILWUdn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoe\n"
|
||||
"gD9FE3lE895GIhEv7Jd9bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bK\n"
|
||||
"fJzuOxi7oIeINLEiR0Z4hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2\n"
|
||||
"QFMByfkEmAGp9Br+2tquRUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd\n"
|
||||
"6G6TLenPbQPEaac5UgGZ7rJvL9idAgMBAAGjHjAcMAkGA1UdEwQCMAAwDwYDVR0R\n"
|
||||
"BAgwBoIEc2FhejANBgkqhkiG9w0BAQsFAAOCAQEAZPasi1Hu7q4S9wJsbIzcGj7m\n"
|
||||
"ZCAxNdm3AINeV3mxfhToeqtXSoa2sujCEgwCuiYi0OE8wmseLO8e1xIsRc/NEpOl\n"
|
||||
"h9u5OupV2Xbf7nuKzPl31YQ7kv+90mnixizf6CFpHXAdU3WEZ33+fMtS+lV/ZBYo\n"
|
||||
"Nh9GGNJ7Grv5KIKu/b2cR403pRF1hGrjWABrIDZO3ZCtNgRp/My1Qr+/04rTs8VF\n"
|
||||
"WyDshOe0u6Xo//M3sCMbx5+62BopCMmAu2TxiPew0OTM8eDa3IBbc2kLGxp8c4J+\n"
|
||||
"EYpAKTZbkhs/6SleNMC4BOEmZQ1tk6D0N0u4Y0b1IKEjgx7HqwZ6mIdmbU1lZQ==\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static const char* SERVER_KEY_PEM =
|
||||
"-----BEGIN PRIVATE KEY-----\n"
|
||||
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5s1UhjcmsrRnS\n"
|
||||
"dJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6JMAL1MbPtX0pfR+yBILWU\n"
|
||||
"dn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoegD9FE3lE895GIhEv7Jd9\n"
|
||||
"bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bKfJzuOxi7oIeINLEiR0Z4\n"
|
||||
"hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2QFMByfkEmAGp9Br+2tqu\n"
|
||||
"RUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd6G6TLenPbQPEaac5UgGZ\n"
|
||||
"7rJvL9idAgMBAAECggEAbXNQctViD596/7rryhze+KztsI/UNsVU6uH3T+LN1XxU\n"
|
||||
"jurnRVNFV4JU3DDrHV9tIDQw3pXCxJzlyRpKHDWGNgTZU2fI6sZGuX+4w1Apd8ss\n"
|
||||
"3Y5mEkGYyaslF/MY13rR2B0wXf/IR4nYA/ssiaBjLcGqHHnQOVseHNgtdXFPXW9n\n"
|
||||
"5IXJ2PSeaFW0Z13vbTQm9Cx2kCBKL4J3TymhkuSW3DUHxmlDfWUK14lkyXCZIZ3i\n"
|
||||
"TbXQAXhwTrz3wqbG++iEP4MZM0Ka7/+WXqrsxA/yCirb+HnMewxwBKzGvkym2I2H\n"
|
||||
"3BkjDn9exDgTePD7i0w22UYCF5uezDB9YOiPVZ634QKBgQDyKIgxD7XMMJezeg0Y\n"
|
||||
"PaG+Eig/hE27mm2pyTQSd6MMZ1HpbDUpLQ/GO3Npy+bKS0/5eefNT4PWVcmpRL4W\n"
|
||||
"blNIfixkcZq1a6wax9Nhk89jXZdhBinO8gfXFOiANFDS7B4fEXeTrfhmuryqUw9m\n"
|
||||
"L6GJ0jk/H6fs6ads15y+O7A6tQKBgQDEUKlZg4qXxNMNKq7l+v7R6IH8VrNLt0I/\n"
|
||||
"f4fhpW3IhmPJCRuOFOS0UpDPYjTeu14i3EFcpe0D+DVoQiggBTAh3I/qMHvRko5g\n"
|
||||
"DT83YHp2/ec0AY0ocwnLwye224t6GF7S8R8vhG1FkIBk6DTJgWFZLDtY+dekZugz\n"
|
||||
"IHvgtxSPSQKBgHWD7fPBJ/xbaIMUq94jqqZOsXGBhyePncBTgA2mOV3/leStOm8t\n"
|
||||
"CwasOyoQZYOuYLU0z6T2/Ye6Qg7+6TCBgbEgafKknuuDwRWN+6rSzEXwWVIgZ5Gi\n"
|
||||
"KuPZparxuHdjVorFMz5borxXys7tV//DBaWYe2eCuT9jdHiBefNni/IxAoGAWHTn\n"
|
||||
"+deVqVEcsAAdkq8IBtk3SNZgL6vBhA8Y0QnTb9luOPWLnve3HbFeYrOjkwDmJ9sK\n"
|
||||
"8I4rP/ClT/cPUW5FA9z4U9PI1uOsl4cghvlH9Tnu5bYVPranIVMsH+7I6Bj3ESFo\n"
|
||||
"peaLvh6gW9dtgaZ0kNOnYi4hhoh/9Bmc9+JGt1ECgYAbGSQLa21L4Hx3NvMparVQ\n"
|
||||
"bGjN26igvqKmhBaKrH8YolOlhEw/mCwTlg9zI7jrhdO0VClPrB8IVBoL+6HvrT21\n"
|
||||
"LJM31J3ddqME9/5fQgKMQsD60kRDD6iV/iK2S6TOUjxJn1f6MuLmGSOHZYcbdBPZ\n"
|
||||
"DyBKgbz/xGV6katjXvg74A==\n"
|
||||
"-----END PRIVATE KEY-----\n";
|
||||
|
||||
|
||||
|
||||
#if 1
|
||||
/* Will be provided by wolfSSL */
|
||||
int wolfSSL_GetEchClientHelloInner(
|
||||
WOLFSSL* ssl, /* Connected to an existing context with the ECH configs. Stores hpke state */
|
||||
const byte* buffer_in, /* The full ClientHelloOuter */
|
||||
word32 buffer_len, /* How many bytes in the buffer */
|
||||
byte* buffer_out,
|
||||
word32* buffer_out_len
|
||||
)
|
||||
{
|
||||
char* inner = "some clienthelloinner";
|
||||
|
||||
*buffer_out_len = strlen(inner);
|
||||
memcpy(buffer_out, inner, *buffer_out_len);
|
||||
print_message(msg_fd, "GetEchClientHelloInner set\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void ech_get_client_hello_inner(WOLFSSL_CTX* ctx, const byte* data, size_t datalen)
|
||||
{
|
||||
WOLFSSL* ssl = NULL;
|
||||
|
||||
printf("wolfSSL_new(%p)\n", ctx);
|
||||
ssl = wolfSSL_new(ctx);
|
||||
if (ssl == NULL) {
|
||||
fprintf(stderr, "Failed to create WOLFSSL object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable certificate verification on this connection
|
||||
wolfSSL_set_verify(ssl, WOLFSSL_VERIFY_NONE, NULL);
|
||||
|
||||
byte client_hello_inner[1024];
|
||||
word32 ch_len = sizeof(client_hello_inner);;
|
||||
|
||||
int res = wolfSSL_GetEchClientHelloInner(
|
||||
ssl,
|
||||
data, /* The full ClientHelloOuter */
|
||||
datalen, /* How many bytes in the buffer */
|
||||
(byte*)&client_hello_inner,
|
||||
&ch_len
|
||||
);
|
||||
client_hello_inner[ch_len] = 0;
|
||||
if (!res) {
|
||||
print_message(msg_fd, "GetEchClientHelloInner: Innerlen: %d\n", ch_len);
|
||||
hexdump(msg_fd, (char*)client_hello_inner, ch_len);
|
||||
print_message(msg_fd, "/GetEchClientHelloInner\n");
|
||||
}
|
||||
|
||||
|
||||
byte sni_data[1024];
|
||||
unsigned int sni_len = sizeof(sni_data);
|
||||
memset(sni_data, 'A', sni_len);
|
||||
|
||||
printf("SNI_GetFromBuffer\n");
|
||||
wolfSSL_SNI_GetFromBuffer(data, datalen, 0, (byte*)&sni_data, &sni_len);
|
||||
sni_data[sni_len] = 0;
|
||||
printf("sni_len = %d\n", sni_len);
|
||||
printf("sni: %s\n", sni_data);
|
||||
}
|
||||
|
||||
/* See if the context matches */
|
||||
void ech_match_sni(const char* buffer_in, size_t buffer_len)
|
||||
{
|
||||
ech_get_client_hello_inner(wolfssl_ctx, (byte*)buffer_in, buffer_len);
|
||||
}
|
||||
|
||||
|
||||
/* Appends a configuration to the DNS configuration file */
|
||||
static void append_conf(const char* sni, const char* ech)
|
||||
{
|
||||
FILE* f = fopen(cfg.ech_conf_sink, "a");
|
||||
|
||||
if (!f) {
|
||||
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
int res = fprintf(f, "%s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\"\n", sni, ech);
|
||||
printf("append: %s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\" : %d\n", sni, ech, res);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
|
||||
/* Erases the configuration file */
|
||||
static void reset_conf(void)
|
||||
{
|
||||
FILE* f = fopen(cfg.ech_conf_sink, "w");
|
||||
|
||||
if (!f) {
|
||||
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
printf("erased and opend %s\n", cfg.ech_conf_sink);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Generate a keypair for a config; later this will be moved to a separate
|
||||
* process, and sslh will just read it */
|
||||
static void ech_genkey(WOLFSSL_CTX* ctx, const char* sni)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printf("ech_genkey for ctx %p\n", ctx);
|
||||
|
||||
print_message(msg_config, "Generating ECH config for outer SNI: %s\n", sni);
|
||||
ret = wolfSSL_CTX_GenerateEchConfig(ctx, sni, 0, 0, 0);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
print_message(msg_system_error, "Failed to generate ECH config: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
byte echConfig[512];
|
||||
word32 echConfigLen = sizeof(echConfig);
|
||||
ret = wolfSSL_CTX_GetEchConfigs(ctx, echConfig, &echConfigLen);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
print_message(msg_system_error, "Failed to get ECH config: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char out[512];
|
||||
word32 outLen = sizeof(out);
|
||||
if (Base64_Encode_NoNl(echConfig, echConfigLen, (byte*)out, &outLen) != 0) {
|
||||
print_message(msg_system_error, "Failed to encode to base64: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
append_conf(sni, out);
|
||||
}
|
||||
|
||||
/* For each public names, create a keypair and configuration.
|
||||
* (Later, this will load the configurations from some place else */
|
||||
static void ech_create_configs(WOLFSSL_CTX** ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
reset_conf();
|
||||
|
||||
*ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
|
||||
if (!*ctx) {
|
||||
print_message(msg_system_error, "Failed to create WOLFSSL_CTX\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Load certificate from memory
|
||||
ret = wolfSSL_CTX_use_certificate_buffer(*ctx,
|
||||
(const unsigned char*)SERVER_CERT_PEM,
|
||||
strlen(SERVER_CERT_PEM),
|
||||
WOLFSSL_FILETYPE_PEM);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "Failed to load certificate: %d\n", ret);
|
||||
fprintf(stderr, "Please replace SERVER_CERT_PEM with a valid certificate\n");
|
||||
wolfSSL_CTX_free(*ctx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Load private key from memory
|
||||
ret = wolfSSL_CTX_use_PrivateKey_buffer(*ctx,
|
||||
(const unsigned char*)SERVER_KEY_PEM,
|
||||
strlen(SERVER_KEY_PEM),
|
||||
WOLFSSL_FILETYPE_PEM);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "Failed to load private key: %d\n", ret);
|
||||
fprintf(stderr, "Please replace SERVER_KEY_PEM with a valid private key\n");
|
||||
wolfSSL_CTX_free(*ctx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Certificate and key loaded successfully\n");
|
||||
|
||||
ech_genkey(*ctx, cfg.listen[0].host); /* For now this only works on the first host */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Initialises the ECH subsystem: create wolfSSL context, create ECH configs,
|
||||
* etc */
|
||||
|
||||
void ech_init()
|
||||
{
|
||||
wolfSSL_Init();
|
||||
|
||||
ech_create_configs(&wolfssl_ctx);
|
||||
}
|
||||
Reference in new issue
Block a user