mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-22 18:12:10 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39d164f5a8 | ||
|
|
bb89714216 | ||
|
|
b9d9f9112f | ||
|
|
91736dbd17 | ||
|
|
bbc4f91f68 | ||
|
|
e20f2381c3 | ||
|
|
7afa284efc | ||
|
|
d51da940da | ||
|
|
e3020e8441 | ||
|
|
cb894df702 | ||
|
|
7cc9dc90be | ||
|
|
f1b4fa1744 | ||
|
|
ca5e7fe6f3 | ||
|
|
bebc109b94 | ||
|
|
1c98792a55 | ||
|
|
1e1cde863b | ||
|
|
224efd36c3 | ||
|
|
1df05999b7 | ||
|
|
0d696eb007 | ||
|
|
03067e9ab7 | ||
|
|
eb976475be | ||
|
|
7eabafc6e2 | ||
|
|
5f9ff68f1c | ||
|
|
4752c8647d | ||
|
|
a8352c137c | ||
|
|
6c0dc269c7 | ||
|
|
0bc0fb62b7 | ||
|
|
dc70cc0d3b | ||
|
|
d39388d1ed | ||
|
|
70a776fb9a | ||
|
|
9fadd60eed | ||
|
|
83c9465c37 | ||
|
|
1137779b9c | ||
|
|
4d3aa6c6e0 | ||
|
|
579baae363 | ||
|
|
e2d01a7440 | ||
|
|
ecc76071c1 | ||
|
|
e5d7cd0575 | ||
|
|
d288125ff3 | ||
|
|
2cc1e1f1d9 | ||
|
|
77dc76a0f2 | ||
|
|
86188cdd28 | ||
|
|
c29081f71d | ||
|
|
8d58164fdd | ||
|
|
894eacfb42 | ||
|
|
6310e6fc9c | ||
|
|
0dd580f45b |
No files matched your search
@@ -1,3 +1,44 @@
|
|||||||
|
vNEXT:
|
||||||
|
Silently ignore EAGAIN/EWOULDBLOCK to avoid
|
||||||
|
spurrious error logs upon UDP port scans.
|
||||||
|
|
||||||
|
verbose_packet now also dumps UDP packets.
|
||||||
|
|
||||||
|
print_message() restores errno, fix sslh-fork
|
||||||
|
exiting upong log errors.
|
||||||
|
|
||||||
|
Add incoming connection timeout management, which
|
||||||
|
was strangely missing.
|
||||||
|
|
||||||
|
v2.3.1:
|
||||||
|
Fix segmentation fault in sslh-fork.
|
||||||
|
|
||||||
|
Add file access when using Landlock and Libwrap
|
||||||
|
together.
|
||||||
|
|
||||||
|
Mixed IPv4/IPv6 proxyprotocol setups will move both
|
||||||
|
addresses to IPv6 format on the server side.
|
||||||
|
|
||||||
|
Add CAP_NET_RAW to systemd config so transparent
|
||||||
|
proxying can work. Removed sslh-select systemd file:
|
||||||
|
don't repeat the same configuration file!
|
||||||
|
|
||||||
|
Changed SNI matching log messages from
|
||||||
|
`msg_probe_error` to `msg_probe_info`: beware if you
|
||||||
|
happen to use them...
|
||||||
|
|
||||||
|
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:
|
v2.3.0:
|
||||||
Added `max_connections` setting to `listen` and
|
Added `max_connections` setting to `listen` and
|
||||||
`protocol` configuration; see the
|
`protocol` configuration; see the
|
||||||
|
|||||||
+25
-2
@@ -3,8 +3,7 @@ ARG TARGET_ARCH="library"
|
|||||||
|
|
||||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
|
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
|
||||||
|
|
||||||
WORKDIR /sslh
|
# Install build dependencies for sslh and libproxyprotocol
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
'gcc' \
|
'gcc' \
|
||||||
'libconfig-dev' \
|
'libconfig-dev' \
|
||||||
@@ -12,15 +11,39 @@ RUN apk add --no-cache \
|
|||||||
'musl-dev' \
|
'musl-dev' \
|
||||||
'pcre2-dev' \
|
'pcre2-dev' \
|
||||||
'perl' \
|
'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
|
COPY . /sslh
|
||||||
|
|
||||||
|
# Configure and build sslh
|
||||||
|
# The configure script should automatically detect libproxyprotocol
|
||||||
RUN ./configure && make sslh-select && strip sslh-select
|
RUN ./configure && make sslh-select && strip sslh-select
|
||||||
|
|
||||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
|
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"
|
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
'libconfig' \
|
'libconfig' \
|
||||||
'pcre2' \
|
'pcre2' \
|
||||||
|
|||||||
+2
-2
@@ -22,11 +22,11 @@ MAN=sslh.8.gz # man page name
|
|||||||
# itself
|
# itself
|
||||||
|
|
||||||
ifneq ($(strip $(ENABLE_SANITIZER)),)
|
ifneq ($(strip $(ENABLE_SANITIZER)),)
|
||||||
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
|
CFLAGS_SAN=-DENABLE_SANITIZER -fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(strip $(COV_TEST)),)
|
ifneq ($(strip $(COV_TEST)),)
|
||||||
CFLAGS_COV=-fprofile-arcs -ftest-coverage
|
CFLAGS_COV=-DCOV_TEST -fprofile-arcs -ftest-coverage
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ sslh -- A ssl/ssh multiplexer
|
|||||||
them further based on tests performed on the first data
|
them further based on tests performed on the first data
|
||||||
packet sent by the remote client.
|
packet sent by the remote client.
|
||||||
|
|
||||||
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH,
|
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, WireGuard,
|
||||||
OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other
|
OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other
|
||||||
protocol that can be tested using a regular expression, can
|
protocol that can be tested using a regular expression, can
|
||||||
be recognised. A typical use case is to allow serving
|
be recognised. A typical use case is to allow serving
|
||||||
@@ -112,8 +112,6 @@ docker run \
|
|||||||
docker-compose example
|
docker-compose example
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sslh:
|
sslh:
|
||||||
image: ghcr.io/yrutschle/sslh:latest
|
image: ghcr.io/yrutschle/sslh:latest
|
||||||
@@ -136,8 +134,6 @@ Transparent mode 1: using sslh container for networking
|
|||||||
|
|
||||||
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
|
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sslh:
|
sslh:
|
||||||
build: https://github.com/yrutschle/sslh.git
|
build: https://github.com/yrutschle/sslh.git
|
||||||
@@ -178,8 +174,6 @@ services:
|
|||||||
Transparent mode 2: using host networking
|
Transparent mode 2: using host networking
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sslh:
|
sslh:
|
||||||
build: https://github.com/yrutschle/sslh.git
|
build: https://github.com/yrutschle/sslh.git
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ shouldn't be construed to mean that any of this will happen.
|
|||||||
- have certain services available only from specified subnets
|
- have certain services available only from specified subnets
|
||||||
|
|
||||||
- some sort of "service knocking" allowing to activate a
|
- some sort of "service knocking" allowing to activate a
|
||||||
service upon some external even, similar to port knocking;
|
service upon some external event, similar to port knocking;
|
||||||
for example, go to a specific URL to enable sslh forwarding
|
for example, go to a specific URL to enable sslh forwarding
|
||||||
to sshd for a set period of time:
|
to sshd for a set period of time:
|
||||||
* sslh listens on 443 and only directs to httpd
|
* sslh listens on 443 and only directs to httpd
|
||||||
|
|||||||
+10
-2
@@ -28,6 +28,7 @@
|
|||||||
/* Info to keep track of all connections */
|
/* Info to keep track of all connections */
|
||||||
struct cnx_collection {
|
struct cnx_collection {
|
||||||
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
|
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
|
/* Allocates and initialises a new collection of connections with at least
|
||||||
@@ -42,6 +43,7 @@ cnx_collection* collection_init(int len)
|
|||||||
memset(collection, 0, sizeof(*collection));
|
memset(collection, 0, sizeof(*collection));
|
||||||
|
|
||||||
collection->fd2cnx = gap_init(len);
|
collection->fd2cnx = gap_init(len);
|
||||||
|
collection->max_fd = 0;
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
@@ -54,10 +56,17 @@ void collection_destroy(cnx_collection* collection)
|
|||||||
free(collection);
|
free(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int collection_max_fd(cnx_collection* collection)
|
||||||
|
{
|
||||||
|
return collection->max_fd;
|
||||||
|
}
|
||||||
|
|
||||||
/* Points the file descriptor to the specified connection index */
|
/* Points the file descriptor to the specified connection index */
|
||||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
|
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
|
||||||
{
|
{
|
||||||
gap_set(collection->fd2cnx, fd, cnx);
|
gap_set(collection->fd2cnx, fd, cnx);
|
||||||
|
if (fd > collection->max_fd)
|
||||||
|
collection->max_fd = fd;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +83,7 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio
|
|||||||
cnx->state = ST_PROBING;
|
cnx->state = ST_PROBING;
|
||||||
cnx->probe_timeout = time(NULL) + cfg.timeout;
|
cnx->probe_timeout = time(NULL) + cfg.timeout;
|
||||||
|
|
||||||
gap_set(collection->fd2cnx, fd, cnx);
|
collection_add_fd(collection, cnx, fd);
|
||||||
|
|
||||||
return cnx;
|
return cnx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ void collection_destroy(cnx_collection* collection);
|
|||||||
|
|
||||||
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
|
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_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
|
||||||
|
int collection_max_fd(cnx_collection* collection);
|
||||||
|
|
||||||
/* Remove a connection from the collection */
|
/* Remove a connection from the collection */
|
||||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
|
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
|
||||||
|
|||||||
@@ -25,12 +25,6 @@
|
|||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Added to make the code compilable under CYGWIN
|
|
||||||
* */
|
|
||||||
#ifndef SA_NOCLDWAIT
|
|
||||||
#define SA_NOCLDWAIT 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Make use of systemd socket activation
|
/* Make use of systemd socket activation
|
||||||
* */
|
* */
|
||||||
#ifdef SYSTEMD
|
#ifdef SYSTEMD
|
||||||
@@ -80,6 +74,13 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
|
|||||||
int sd = 0;
|
int sd = 0;
|
||||||
|
|
||||||
#ifdef SYSTEMD
|
#ifdef SYSTEMD
|
||||||
|
/* Sockets passed by systemd socket activation do not correspond to a
|
||||||
|
* `listen` entry in the configuration, but code dereferences
|
||||||
|
* endpoint->endpoint_cfg unconditionally (e.g. inc_listen_connections()
|
||||||
|
* and probe_client_protocol()), so give them a default, empty
|
||||||
|
* configuration: no max_connections, no proxyprotocol. */
|
||||||
|
static struct sslhcfg_listen_item default_endpoint_cfg;
|
||||||
|
|
||||||
sd = sd_listen_fds(0);
|
sd = sd_listen_fds(0);
|
||||||
if (sd < 0) {
|
if (sd < 0) {
|
||||||
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
|
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
|
||||||
@@ -92,6 +93,7 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
|
|||||||
for (i = 0; i < sd; i++) {
|
for (i = 0; i < sd; i++) {
|
||||||
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
|
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
|
||||||
(*sockfd)[i].type = SOCK_STREAM;
|
(*sockfd)[i].type = SOCK_STREAM;
|
||||||
|
(*sockfd)[i].endpoint_cfg = &default_endpoint_cfg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -209,6 +211,8 @@ static int start_listen_inet(struct listen_endpoint *sockfd[], int num_addr, str
|
|||||||
/* Same, but for UNIX sockets */
|
/* Same, but for UNIX sockets */
|
||||||
static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg)
|
static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg)
|
||||||
{
|
{
|
||||||
|
struct sockaddr_un sun;
|
||||||
|
|
||||||
int fd = socket(AF_UNIX, cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
int fd = socket(AF_UNIX, cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||||
CHECK_RES_DIE(fd, "socket(AF_UNIX)");
|
CHECK_RES_DIE(fd, "socket(AF_UNIX)");
|
||||||
|
|
||||||
@@ -218,10 +222,10 @@ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, str
|
|||||||
exit(4);
|
exit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_un sun;
|
memset(&sun, 0, sizeof(sun));
|
||||||
sun.sun_family = AF_UNIX;
|
sun.sun_family = AF_UNIX;
|
||||||
strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1);
|
strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1);
|
||||||
printf("binding [%s]\n", sun.sun_path);
|
print_message(msg_config, "binding [%s]\n", sun.sun_path);
|
||||||
res = bind(fd, (struct sockaddr*)&sun, sizeof(sun));
|
res = bind(fd, (struct sockaddr*)&sun, sizeof(sun));
|
||||||
CHECK_RES_DIE(res, "bind(AF_UNIX)");
|
CHECK_RES_DIE(res, "bind(AF_UNIX)");
|
||||||
|
|
||||||
@@ -232,7 +236,7 @@ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, str
|
|||||||
memset(&(*sockfd)[num_addr-1], 0, sizeof((*sockfd)[num_addr-1]));
|
memset(&(*sockfd)[num_addr-1], 0, sizeof((*sockfd)[num_addr-1]));
|
||||||
(*sockfd)[num_addr-1].socketfd = fd;
|
(*sockfd)[num_addr-1].socketfd = fd;
|
||||||
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
|
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||||
(*sockfd)[num_addr-1].family = AF_INET;
|
(*sockfd)[num_addr-1].family = AF_UNIX;
|
||||||
(*sockfd)[num_addr-1].endpoint_cfg = cfg;
|
(*sockfd)[num_addr-1].endpoint_cfg = cfg;
|
||||||
|
|
||||||
return num_addr;
|
return num_addr;
|
||||||
@@ -428,9 +432,9 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
|
|||||||
cnx->proto->port);
|
cnx->proto->port);
|
||||||
}
|
}
|
||||||
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
||||||
/* When transparent or using proxyprotocol, make sure both
|
/* When transparent, make sure both connections use
|
||||||
* connections use the same address family (e.g. IP4 on both sides) */
|
* 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))
|
if (transparent && (a->ai_family != from.ai_addr->sa_family))
|
||||||
continue;
|
continue;
|
||||||
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
||||||
sprintaddr(buf, sizeof(buf), a),
|
sprintaddr(buf, sizeof(buf), a),
|
||||||
@@ -481,10 +485,13 @@ static int connect_unix(struct connection *cnx, int fd_from, connect_blocking bl
|
|||||||
struct sockaddr_storage ss;
|
struct sockaddr_storage ss;
|
||||||
struct sockaddr_un* sun = (struct sockaddr_un*)&ss;
|
struct sockaddr_un* sun = (struct sockaddr_un*)&ss;
|
||||||
|
|
||||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
memset(&ss, 0, sizeof(ss));
|
||||||
sun->sun_family = AF_UNIX;
|
sun->sun_family = AF_UNIX;
|
||||||
strncpy(sun->sun_path, cnx->proto->host, sizeof(sun->sun_path)-1);
|
strncpy(sun->sun_path, cnx->proto->host, sizeof(sun->sun_path)-1);
|
||||||
|
|
||||||
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
CHECK_RES_RETURN(fd, "socket", -1);
|
||||||
|
|
||||||
int res = connect(fd, (struct sockaddr*)sun, sizeof(*sun));
|
int res = connect(fd, (struct sockaddr*)sun, sizeof(*sun));
|
||||||
CHECK_RES_RETURN(res, "connect", res);
|
CHECK_RES_RETURN(res, "connect", res);
|
||||||
|
|
||||||
@@ -905,7 +912,7 @@ int check_access_rights(int in_socket, const char* service)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) {
|
if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) {
|
||||||
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
|
print_message(msg_connections, "connection from %s(%s): access denied\n", host, addr_str);
|
||||||
close(in_socket);
|
close(in_socket);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
#define FD_SETSIZE 4096
|
#define FD_SETSIZE 4096
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Cygwin does not define some symbols
|
||||||
|
* */
|
||||||
|
#ifndef SA_NOCLDWAIT
|
||||||
|
#define SA_NOCLDWAIT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -193,11 +200,11 @@ extern struct addrinfo *addr_listen;
|
|||||||
extern const char* server_type;
|
extern const char* server_type;
|
||||||
|
|
||||||
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
|
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
|
||||||
extern int hosts_ctl();
|
extern int hosts_ctl(char*, char*, char*, char*);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* sslh-fork.c */
|
/* sslh-fork.c */
|
||||||
void start_shoveler(int);
|
void main_inetd(void);
|
||||||
|
|
||||||
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
||||||
|
|
||||||
|
|||||||
+10
-5
@@ -7,12 +7,17 @@ AC_INIT
|
|||||||
AC_CONFIG_HEADERS(config.h)
|
AC_CONFIG_HEADERS(config.h)
|
||||||
AC_CONFIG_FILES([Makefile])
|
AC_CONFIG_FILES([Makefile])
|
||||||
|
|
||||||
AC_CHECK_LIB([wrap], [hosts_ctl], [AC_DEFINE(HAVE_LIBWRAP) LIBS="$LIBS -lwrap" ], [])
|
AC_CHECK_LIB([wrap], [hosts_ctl],
|
||||||
AC_CHECK_LIB([cap], [cap_get_proc], [AC_DEFINE(HAVE_LIBCAP) LIBS="$LIBS -lcap" ], [])
|
[AC_DEFINE([HAVE_LIBWRAP], [1], [Libwrap, to support host_ctl, /etc/allow and /etc/deny]) LIBS="$LIBS -lwrap"])
|
||||||
AC_CHECK_LIB([bsd], [setproctitle], [AC_DEFINE(HAVE_LIBBSD) LIBS="$LIBS -lbsd" ], [])
|
AC_CHECK_LIB([cap], [cap_get_proc],
|
||||||
|
[AC_DEFINE([HAVE_LIBCAP], [1], [libcap support, to use Linux capabilities]) LIBS="$LIBS -lcap"])
|
||||||
|
AC_CHECK_LIB([bsd], [setproctitle],
|
||||||
|
[AC_DEFINE([HAVE_LIBBSD], [1], [libbsd, to change process name]) LIBS="$LIBS -lbsd"])
|
||||||
|
|
||||||
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
|
AC_CHECK_HEADERS(linux/landlock.h,
|
||||||
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
|
AC_DEFINE([HAVE_LANDLOCK], [1], [Landlock sandboxing Linux LSM]))
|
||||||
|
AC_CHECK_HEADERS(proxy_protocol.h,
|
||||||
|
[AC_DEFINE([HAVE_PROXYPROTOCOL], [1], [Support for Proxy-protocol using libproxyprotocol]) LIBS="$LIBS -lproxyprotocol"])
|
||||||
|
|
||||||
LIBS="$LIBS"
|
LIBS="$LIBS"
|
||||||
AC_SUBST([LIBS])
|
AC_SUBST([LIBS])
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ protocols:
|
|||||||
(
|
(
|
||||||
{ name: "ssh"; host: "localhost"; port: "2222"; },
|
{ name: "ssh"; host: "localhost"; port: "2222"; },
|
||||||
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
|
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
|
||||||
);
|
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,7 +76,6 @@ protocols:
|
|||||||
(
|
(
|
||||||
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
|
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
|
||||||
{ name: "tls"; host: "localhost"; port: "8080"; }
|
{ name: "tls"; host: "localhost"; port: "8080"; }
|
||||||
);
|
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -68,9 +68,13 @@ void start_echo(int fd)
|
|||||||
int first = 1;
|
int first = 1;
|
||||||
|
|
||||||
prefix_len = strlen(cfg.prefix);
|
prefix_len = strlen(cfg.prefix);
|
||||||
|
if (prefix_len >= sizeof(buffer)) {
|
||||||
|
fprintf(stderr, "prefix too long\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
strcpy(buffer, cfg.prefix);
|
memcpy(buffer, cfg.prefix, prefix_len);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
|
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
|
||||||
@@ -147,19 +151,24 @@ void udp_echo(struct listen_endpoint* listen_socket)
|
|||||||
memset(data, 0, sizeof(data));
|
memset(data, 0, sizeof(data));
|
||||||
|
|
||||||
size_t prefix_len = strlen(cfg.prefix);
|
size_t prefix_len = strlen(cfg.prefix);
|
||||||
|
if (prefix_len >= sizeof(data) - 1) {
|
||||||
|
fprintf(stderr, "prefix too long\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
memcpy(data, cfg.prefix, prefix_len);
|
memcpy(data, cfg.prefix, prefix_len);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
addrlen = sizeof(src_addr);
|
addrlen = sizeof(src_addr);
|
||||||
ssize_t len = recvfrom(listen_socket->socketfd,
|
ssize_t len = recvfrom(listen_socket->socketfd,
|
||||||
data + prefix_len,
|
data + prefix_len,
|
||||||
sizeof(data) - prefix_len,
|
sizeof(data) - prefix_len - 1,
|
||||||
0,
|
0,
|
||||||
&src_addr,
|
&src_addr,
|
||||||
&addrlen);
|
&addrlen);
|
||||||
|
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
perror("recvfrom");
|
perror("recvfrom");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
*(data + prefix_len + len) = 0;
|
*(data + prefix_len + len) = 0;
|
||||||
fprintf(stderr, "%zd %s\n", len, data + prefix_len);
|
fprintf(stderr, "%zd %s\n", len, data + prefix_len);
|
||||||
|
|||||||
-109
@@ -1,109 +0,0 @@
|
|||||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
|
||||||
* on Sat Nov 7 09:19:26 2020.
|
|
||||||
|
|
||||||
# conf2struct: generate libconf parsers that read to structs
|
|
||||||
# Copyright (C) 2018-2019 Yves Rutschle
|
|
||||||
# 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 C2S_SSLHCFG_H
|
|
||||||
#define C2S_SSLHCFG_H
|
|
||||||
#ifdef LIBCONFIG
|
|
||||||
# include <libconfig.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#include "probe.h"
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
|
|
||||||
struct sslhcfg_listen_item {
|
|
||||||
char* host;
|
|
||||||
char* port;
|
|
||||||
int keepalive;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sslhcfg_protocols_item {
|
|
||||||
char* name;
|
|
||||||
char* host;
|
|
||||||
char* port;
|
|
||||||
int service_is_present;
|
|
||||||
char* service;
|
|
||||||
int fork;
|
|
||||||
int tfo_ok;
|
|
||||||
int log_level;
|
|
||||||
int keepalive;
|
|
||||||
size_t sni_hostnames_len;
|
|
||||||
char** sni_hostnames;
|
|
||||||
size_t alpn_protocols_len;
|
|
||||||
char** alpn_protocols;
|
|
||||||
size_t regex_patterns_len;
|
|
||||||
char** regex_patterns;
|
|
||||||
int minlength_is_present;
|
|
||||||
int minlength;
|
|
||||||
T_PROBE* probe;
|
|
||||||
struct addrinfo* saddr;
|
|
||||||
void* data;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sslhcfg_item {
|
|
||||||
char* prefix;
|
|
||||||
int verbose;
|
|
||||||
int foreground;
|
|
||||||
int inetd;
|
|
||||||
int numeric;
|
|
||||||
int transparent;
|
|
||||||
int timeout;
|
|
||||||
int user_is_present;
|
|
||||||
char* user;
|
|
||||||
int pidfile_is_present;
|
|
||||||
char* pidfile;
|
|
||||||
int chroot_is_present;
|
|
||||||
char* chroot;
|
|
||||||
char* syslog_facility;
|
|
||||||
char* on_timeout;
|
|
||||||
size_t listen_len;
|
|
||||||
struct sslhcfg_listen_item* listen;
|
|
||||||
size_t protocols_len;
|
|
||||||
struct sslhcfg_protocols_item* protocols;
|
|
||||||
};
|
|
||||||
|
|
||||||
int sslhcfg_parse_file(
|
|
||||||
const char* filename,
|
|
||||||
struct sslhcfg_item* sslhcfg,
|
|
||||||
const char** errmsg);
|
|
||||||
|
|
||||||
void sslhcfg_fprint(
|
|
||||||
FILE* out,
|
|
||||||
struct sslhcfg_item *sslhcfg,
|
|
||||||
int depth);
|
|
||||||
|
|
||||||
int sslhcfg_cl_parse(
|
|
||||||
int argc,
|
|
||||||
char* argv[],
|
|
||||||
struct sslhcfg_item *sslhcfg);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -159,6 +159,9 @@ protocols:
|
|||||||
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
|
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
|
||||||
# Remember that the regex needs to be adjusted for every supported QUIC version.
|
# Remember that the regex needs to be adjusted for every supported QUIC version.
|
||||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
|
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
|
||||||
|
# More recent versions of QUIC migth work with this:
|
||||||
|
{ name: "regex"; host: "localhost"; is_udp: true; port: "4443"; regex_patterns: [ "^[\x40-\x7F\xC0-\xFF]" ]; },
|
||||||
|
# See https://github.com/yrutschle/sslh/issues/466 for discussions on QUIC
|
||||||
|
|
||||||
# Regex examples -- better use the built-in probes for real-world use!
|
# Regex examples -- better use the built-in probes for real-world use!
|
||||||
# OpenVPN
|
# OpenVPN
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,9 @@
|
|||||||
|
|
||||||
CFLAGS=-DHASH_DEBUG -O2 -Wall
|
CFLAGS=-DHASH_DEBUG -O2 -Wall
|
||||||
OBJ=../hash.o ../gap.o htest.o
|
OBJ=hash.o ../gap.o htest.o
|
||||||
|
|
||||||
|
hash.o: ../hash.c
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
htest: $(OBJ)
|
htest: $(OBJ)
|
||||||
$(CC) -o htest $(OBJ)
|
$(CC) -o htest $(OBJ)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ struct hash_item {
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef struct hash_item* hash_item;
|
typedef struct hash_item* hash_item;
|
||||||
|
typedef struct hash hash;
|
||||||
|
|
||||||
#include "../hash.h"
|
#include "../hash.h"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ static int add_resolv(int ruleset_fd)
|
|||||||
static int add_libwrap(int ruleset_fd)
|
static int add_libwrap(int ruleset_fd)
|
||||||
{
|
{
|
||||||
/* Files for libwrap */
|
/* Files for libwrap */
|
||||||
#ifdef LIBWRAP
|
#ifdef HAVE_LIBWRAP
|
||||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
|
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
|
||||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
|
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ msg_info msg_fd = {
|
|||||||
/* Internal errors: inconsistent states, impossible values, things that should never happen, and are therefore the sign of memory corruption: hence the LOG_CRIT */
|
/* Internal errors: inconsistent states, impossible values, things that should never happen, and are therefore the sign of memory corruption: hence the LOG_CRIT */
|
||||||
msg_info msg_int_error = {
|
msg_info msg_int_error = {
|
||||||
LOG_CRIT,
|
LOG_CRIT,
|
||||||
&cfg.verbose_system_error
|
&cfg.verbose_int_error
|
||||||
};
|
};
|
||||||
|
|
||||||
/* System errors: when the system around us fails us: memory allocation, fork, ... */
|
/* System errors: when the system around us fails us: memory allocation, fork, ... */
|
||||||
@@ -107,6 +107,12 @@ void print_message(msg_info info, const char* str, ...)
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
|
/* print_message() being used for logging, it is often used after system
|
||||||
|
* calls and before caller checks errno; as it itself does not do anything
|
||||||
|
* with errno (can't log an error if error logging fails), it just restores
|
||||||
|
* it on exit */
|
||||||
|
int saved_errno = errno;
|
||||||
|
|
||||||
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
|
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
|
||||||
va_start(ap, str);
|
va_start(ap, str);
|
||||||
vfprintf(stderr, str, ap);
|
vfprintf(stderr, str, ap);
|
||||||
@@ -125,6 +131,8 @@ void print_message(msg_info info, const char* str, ...)
|
|||||||
fflush(logfile_fp);
|
fflush(logfile_fp);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errno = saved_errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
|
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
|
||||||
|
|||||||
@@ -88,20 +88,20 @@ struct sslhcfg_protocols_item* timeout_protocol(void)
|
|||||||
void hexdump(msg_info msg_info, const char *mem, unsigned int len)
|
void hexdump(msg_info msg_info, const char *mem, unsigned int len)
|
||||||
{
|
{
|
||||||
unsigned int i, j;
|
unsigned int i, j;
|
||||||
char str[10 + HEXDUMP_COLS * 4 + 2];
|
char str[12 + HEXDUMP_COLS * 4 + 2]; /* offset <= 12, hex+ascii 64, '\n'+'\0' */
|
||||||
int c = 0; /* index in str */
|
int c = 0; /* index in str */
|
||||||
|
|
||||||
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
|
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
|
||||||
{
|
{
|
||||||
/* print offset */
|
/* print offset */
|
||||||
if(i % HEXDUMP_COLS == 0)
|
if(i % HEXDUMP_COLS == 0)
|
||||||
c += sprintf(&str[c], "0x%06x: ", i);
|
c += snprintf(&str[c], sizeof(str) - c, "0x%06x: ", i);
|
||||||
|
|
||||||
/* print hex data */
|
/* print hex data */
|
||||||
if(i < len)
|
if(i < len)
|
||||||
c += sprintf(&str[c], "%02x ", 0xFF & mem[i]);
|
c += snprintf(&str[c], sizeof(str) - c, "%02x ", 0xFF & mem[i]);
|
||||||
else /* end of block, just aligning for ASCII dump */
|
else /* end of block, just aligning for ASCII dump */
|
||||||
c+= sprintf(&str[c], " ");
|
c += snprintf(&str[c], sizeof(str) - c, " ");
|
||||||
|
|
||||||
/* print ASCII dump */
|
/* print ASCII dump */
|
||||||
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
|
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
|
||||||
@@ -156,7 +156,10 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
|
|||||||
if (len < 2)
|
if (len < 2)
|
||||||
return PROBE_AGAIN;
|
return PROBE_AGAIN;
|
||||||
|
|
||||||
packet_len = ntohs(*(uint16_t*)p);
|
/* p may be unaligned (e.g. on ARM), do not cast to uint16_t* */
|
||||||
|
uint16_t wire_len;
|
||||||
|
memcpy(&wire_len, p, sizeof(wire_len));
|
||||||
|
packet_len = ntohs(wire_len);
|
||||||
return packet_len == len - 2;
|
return packet_len == len - 2;
|
||||||
} else {
|
} else {
|
||||||
if (len < 1)
|
if (len < 1)
|
||||||
@@ -205,7 +208,9 @@ static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_prot
|
|||||||
return PROBE_NEXT;
|
return PROBE_NEXT;
|
||||||
|
|
||||||
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
|
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
|
||||||
if (ntohl(*(uint32_t*)p) != 0x01000000)
|
uint32_t type;
|
||||||
|
memcpy(&type, p, sizeof(type));
|
||||||
|
if (ntohl(type) != 0x01000000)
|
||||||
return PROBE_NEXT;
|
return PROBE_NEXT;
|
||||||
|
|
||||||
return PROBE_MATCH;
|
return PROBE_MATCH;
|
||||||
@@ -407,20 +412,25 @@ static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_prot
|
|||||||
if (len < 8)
|
if (len < 8)
|
||||||
return PROBE_NEXT;
|
return PROBE_NEXT;
|
||||||
|
|
||||||
return !strncmp(p, "TS3INIT1", len);
|
/* Compare a fixed-length prefix: strncmp(p, "TS3INIT1", len) would read
|
||||||
|
* len bytes of the 9-byte literal and never match a real packet, whose
|
||||||
|
* byte 8 is not NUL */
|
||||||
|
return !memcmp(p, "TS3INIT1", 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||||
{
|
{
|
||||||
char version;
|
char version;
|
||||||
char packet_len;
|
uint16_t packet_len;
|
||||||
if (len < 7)
|
if (len < 7)
|
||||||
return PROBE_NEXT;
|
return PROBE_NEXT;
|
||||||
version=*p;
|
version=*p;
|
||||||
if (version!=0x03)
|
if (version!=0x03)
|
||||||
return 0;
|
return 0;
|
||||||
packet_len = ntohs(*(uint16_t*)(p+2));
|
memcpy(&packet_len, p+2, sizeof(packet_len));
|
||||||
return packet_len == len;
|
/* The declared TPKT length must fit the entire received packet; a char
|
||||||
|
* would truncate e.g. 0x0107 to 7 and accept truncated packets */
|
||||||
|
return ntohs(packet_len) == len;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_REGEX
|
#ifdef ENABLE_REGEX
|
||||||
@@ -465,6 +475,9 @@ int probe_buffer(char* buf, int len,
|
|||||||
struct sslhcfg_protocols_item* p;
|
struct sslhcfg_protocols_item* p;
|
||||||
int i, res, again = 0;
|
int i, res, again = 0;
|
||||||
|
|
||||||
|
print_message(msg_packets, "hexdump of incoming packet:\n");
|
||||||
|
hexdump(msg_packets, buf, len);
|
||||||
|
|
||||||
*proto_out = NULL;
|
*proto_out = NULL;
|
||||||
for (i = 0; i < proto_len; i++) {
|
for (i = 0; i < proto_len; i++) {
|
||||||
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
||||||
|
|||||||
+6
-1
@@ -142,6 +142,10 @@ static int pid_cmp(hash_item item1, hash_item item2)
|
|||||||
void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
|
void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
|
||||||
struct pid2proto p2p = { .pid = pid };
|
struct pid2proto p2p = { .pid = pid };
|
||||||
struct pid2proto* found = hash_find(loop->pid2proto, &p2p);
|
struct pid2proto* found = hash_find(loop->pid2proto, &p2p);
|
||||||
|
if (!found) {
|
||||||
|
print_message(msg_int_error, "SIGCHLD for unknown pid %d\n", pid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
dec_proto_connections(found->proto);
|
dec_proto_connections(found->proto);
|
||||||
dec_listen_connections(found->endpoint);
|
dec_listen_connections(found->endpoint);
|
||||||
|
|
||||||
@@ -196,7 +200,7 @@ static int next_power_of_two(int n)
|
|||||||
return n + 1;
|
return n + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop_init(struct loop_info* loop, int num_addr_listen)
|
void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen)
|
||||||
{
|
{
|
||||||
memset(loop, 0, sizeof(*loop));
|
memset(loop, 0, sizeof(*loop));
|
||||||
loop->collection = collection_init(0);
|
loop->collection = collection_init(0);
|
||||||
@@ -206,6 +210,7 @@ void loop_init(struct loop_info* loop, int num_addr_listen)
|
|||||||
udp_init(loop);
|
udp_init(loop);
|
||||||
tcp_init();
|
tcp_init();
|
||||||
|
|
||||||
|
loop->listen_sockets = listen_sockets;
|
||||||
loop->num_addr_listen = num_addr_listen;
|
loop->num_addr_listen = num_addr_listen;
|
||||||
|
|
||||||
int max_forks = next_power_of_two(max_forking_connections());
|
int max_forks = next_power_of_two(max_forking_connections());
|
||||||
|
|||||||
+5
-1
@@ -23,6 +23,8 @@ struct loop_info {
|
|||||||
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
|
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
|
||||||
|
|
||||||
watchers* watchers;
|
watchers* watchers;
|
||||||
|
|
||||||
|
struct listen_endpoint* listen_sockets;
|
||||||
int num_addr_listen; /* How many listen endpoints do we have here */
|
int num_addr_listen; /* How many listen endpoints do we have here */
|
||||||
|
|
||||||
cnx_collection* collection; /* Collection of connections linked to this loop */
|
cnx_collection* collection; /* Collection of connections linked to this loop */
|
||||||
@@ -32,7 +34,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);
|
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);
|
int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
|
||||||
void loop_init(struct loop_info* loop, int num_addr_listen);
|
void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen);
|
||||||
|
|
||||||
void remember_child_data(struct loop_info* fd_info,
|
void remember_child_data(struct loop_info* fd_info,
|
||||||
struct connection* cnx, pid_t pid);
|
struct connection* cnx, pid_t pid);
|
||||||
@@ -47,4 +49,6 @@ void watchers_del_write(watchers* w, int fd);
|
|||||||
|
|
||||||
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
|
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
|
||||||
|
|
||||||
|
void close_listen_endpoints(struct loop_info* loop);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+2
-2
@@ -112,7 +112,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
|
|||||||
if (res == -1) return -1;
|
if (res == -1) return -1;
|
||||||
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
|
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
|
||||||
|
|
||||||
res = get_info(cnx->q[1].fd, SOCK,
|
res = get_info(cnx->q[0].fd, SOCK,
|
||||||
&addr,
|
&addr,
|
||||||
&pp_info_in_v1.dst_addr,
|
&pp_info_in_v1.dst_addr,
|
||||||
&pp_info_in_v1.dst_port
|
&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);
|
uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error);
|
||||||
|
|
||||||
if (!pp1_hdr) {
|
if (!pp1_hdr) {
|
||||||
print_message(msg_system_error, "pp_create_hrd:%d:%s\n", error, pp_strerror(error));
|
print_message(msg_system_error, "pp_create_hdr:%d:%s\n", error, pp_strerror(error));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
|
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ int pp_header_len(char* buffer, int len);
|
|||||||
#else /* HAVE_PROXYPROTOCOL */
|
#else /* HAVE_PROXYPROTOCOL */
|
||||||
|
|
||||||
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
|
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
|
||||||
static int pp_log_connection(char* buffer, ssize_t buffer_len) { return 0; }
|
static inline int pp_log_connection(char* buffer, ssize_t buffer_len) { return 0; }
|
||||||
static inline int pp_header_len(char*, int) { return 0; }
|
static inline int pp_header_len(char*, int) { return 0; }
|
||||||
|
|
||||||
#endif /* HAVE_PROXYPROTOCOL */
|
#endif /* HAVE_PROXYPROTOCOL */
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
[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]
|
[Unit]
|
||||||
Description=SSL/SSH multiplexer (fork mode) for %I
|
Description=SSL/SSH multiplexer for %I
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
@@ -8,8 +8,8 @@ ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
|||||||
KillMode=process
|
KillMode=process
|
||||||
#Hardening
|
#Hardening
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
CapabilityBoundingSet=CAP_SETGIT CAP_SETUID CAP_NET_BIND_SERVICE CAP_NET_RAW
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
|
||||||
SecureBits=noroot-locked
|
SecureBits=noroot-locked
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "udp-listener.h"
|
#include "udp-listener.h"
|
||||||
#include "tcp-listener.h"
|
#include "tcp-listener.h"
|
||||||
|
#include "sslh-conf.h"
|
||||||
|
|
||||||
|
|
||||||
const char* server_type = "sslh-ev";
|
const char* server_type = "sslh-ev";
|
||||||
@@ -167,6 +168,41 @@ static void temporary_stop_accept_watchers(ev_io* io)
|
|||||||
ev_timer_start(EV_A_ timer);
|
ev_timer_start(EV_A_ timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Timer to timeout incoming connections that send no data.
|
||||||
|
* It's set to cnx.timeout during initialisation, then started when at least
|
||||||
|
* one connection is probing. It's stopped when the last probing connection is
|
||||||
|
* forwarded. Whenever it fires, it processes the first probing connection
|
||||||
|
* (which is due to time out), then resets the timer to trigger when the next
|
||||||
|
* connection is due. This relies on the loop->probing_list */
|
||||||
|
static ev_timer probe_timer;
|
||||||
|
|
||||||
|
static void probe_timeout_cb(EV_P_ ev_timer* timer, int revents)
|
||||||
|
{
|
||||||
|
struct loop_info* info = ev_userdata(EV_A);
|
||||||
|
|
||||||
|
ev_timer_stop(EV_A_ &probe_timer);
|
||||||
|
|
||||||
|
/* Process all probing connections that have timed out */
|
||||||
|
if (info->num_probing > 0) {
|
||||||
|
struct connection* cnx = gap_get(info->probing_list, 0);
|
||||||
|
if (cnx) { /* impossible cnx is NULL? */
|
||||||
|
probing_read_process(cnx, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there is at least one left, restart the timer to fire depending on
|
||||||
|
* the next connection to time out */
|
||||||
|
if (info->num_probing > 0) {
|
||||||
|
struct connection* cnx = gap_get(info->probing_list, 0);
|
||||||
|
time_t duration = cnx->probe_timeout - time(NULL);
|
||||||
|
if (duration <= 0) duration = 1;
|
||||||
|
ev_timer_set(EV_A_ &probe_timer, duration, 0);
|
||||||
|
ev_timer_start(EV_A_ &probe_timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* The accept() callback, called when there is READ activity.
|
/* The accept() callback, called when there is READ activity.
|
||||||
*
|
*
|
||||||
* accept()ing when the process as reached its file descriptor limit (ulimit -n) is can of worms with no solution (see libev's manual "The special problem of accept()ing when you can't").
|
* accept()ing when the process as reached its file descriptor limit (ulimit -n) is can of worms with no solution (see libev's manual "The special problem of accept()ing when you can't").
|
||||||
@@ -176,7 +212,14 @@ static void temporary_stop_accept_watchers(ev_io* io)
|
|||||||
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
|
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
|
||||||
{
|
{
|
||||||
struct loop_info* info = ev_userdata(EV_A);
|
struct loop_info* info = ev_userdata(EV_A);
|
||||||
if (!cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd))) {
|
struct connection* cnx = cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd));
|
||||||
|
|
||||||
|
if (cnx) {
|
||||||
|
if (info->num_probing == 1) { /* This is the first connection, start the timer */
|
||||||
|
ev_timer_set(EV_A_ &probe_timer, cfg.timeout, 0);
|
||||||
|
ev_timer_start(EV_A_ &probe_timer);
|
||||||
|
}
|
||||||
|
} else { /* accept() failed, probably all file descriptors are used */
|
||||||
switch (errno) {
|
switch (errno) {
|
||||||
case EMFILE:
|
case EMFILE:
|
||||||
case ENFILE: /* Not sure ENFILE should be included here */
|
case ENFILE: /* Not sure ENFILE should be included here */
|
||||||
@@ -207,13 +250,14 @@ void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pi
|
|||||||
ev_child_start(EV_DEFAULT_ cw);
|
ev_child_start(EV_DEFAULT_ cw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||||
{
|
{
|
||||||
struct loop_info ev_info;
|
struct loop_info ev_info;
|
||||||
loop = EV_DEFAULT;
|
loop = EV_DEFAULT;
|
||||||
|
|
||||||
loop_init(&ev_info, num_addr_listen);
|
ev_timer_init(&probe_timer, &probe_timeout_cb, cfg.timeout, 0);
|
||||||
|
|
||||||
|
loop_init(&ev_info, listen_sockets, num_addr_listen);
|
||||||
|
|
||||||
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
|
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
|
||||||
ev_set_userdata(EV_A_ &ev_info);
|
ev_set_userdata(EV_A_ &ev_info);
|
||||||
@@ -221,7 +265,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
|||||||
ev_run(EV_A_ 0);
|
ev_run(EV_A_ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_shoveler(int listen_socket) {
|
void main_inetd(void) {
|
||||||
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
|
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-9
@@ -69,7 +69,7 @@ int shovel(struct connection *cnx)
|
|||||||
|
|
||||||
/* Child process that finds out what to connect to and proxies
|
/* Child process that finds out what to connect to and proxies
|
||||||
*/
|
*/
|
||||||
void start_shoveler(int in_socket)
|
static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
|
||||||
{
|
{
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
@@ -79,6 +79,7 @@ void start_shoveler(int in_socket)
|
|||||||
|
|
||||||
init_cnx(&cnx);
|
init_cnx(&cnx);
|
||||||
cnx.q[0].fd = in_socket;
|
cnx.q[0].fd = in_socket;
|
||||||
|
cnx.endpoint = endpoint;
|
||||||
|
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(in_socket, &fds);
|
FD_SET(in_socket, &fds);
|
||||||
@@ -130,6 +131,22 @@ void start_shoveler(int in_socket)
|
|||||||
exit(0);
|
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 pid_t *listener_pid;
|
||||||
static int listener_pid_number = 0;
|
static int listener_pid_number = 0;
|
||||||
|
|
||||||
@@ -207,9 +224,10 @@ static void process_signals(struct listen_endpoint* endpoint)
|
|||||||
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
|
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
|
||||||
{
|
{
|
||||||
int i, in_socket;
|
int i, in_socket;
|
||||||
|
struct listen_endpoint* self = &endpoint[active_endpoint];
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
in_socket = accept(self->socketfd, 0, 0);
|
||||||
if (in_socket == -1) {
|
if (in_socket == -1) {
|
||||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
|
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
|
||||||
__FILE__, __LINE__, "accept", errno, strerror(errno));
|
__FILE__, __LINE__, "accept", errno, strerror(errno));
|
||||||
@@ -226,33 +244,34 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
case EINTR:
|
case EINTR:
|
||||||
process_signals(endpoint);
|
process_signals(self);
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
default: /* Otherwise, it's something wrong in our parameters, we fail */
|
default: /* Otherwise, it's something wrong in our parameters, we fail */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
endpoint->num_connections++;
|
self->num_connections++;
|
||||||
print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, endpoint->num_connections);
|
print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, self->num_connections);
|
||||||
if ((endpoint->endpoint_cfg->max_connections_is_present)
|
if ((self->endpoint_cfg->max_connections_is_present)
|
||||||
&& (endpoint->num_connections > endpoint->endpoint_cfg->max_connections))
|
&& (self->num_connections > self->endpoint_cfg->max_connections))
|
||||||
{
|
{
|
||||||
print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket);
|
print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket);
|
||||||
endpoint->num_connections--;
|
self->num_connections--;
|
||||||
close(in_socket);
|
close(in_socket);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(fork()) {
|
switch(fork()) {
|
||||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||||
|
self->num_connections--; /* no child will reap it */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0: /* In child process */
|
case 0: /* In child process */
|
||||||
/* Shoveler processes don't need to hog file descriptors */
|
/* Shoveler processes don't need to hog file descriptors */
|
||||||
for (i = 0; i < num_endpoints; i++)
|
for (i = 0; i < num_endpoints; i++)
|
||||||
close(endpoint[i].socketfd);
|
close(endpoint[i].socketfd);
|
||||||
start_shoveler(in_socket);
|
start_shoveler(in_socket, self);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
default: /* In parent process */
|
default: /* In parent process */
|
||||||
|
|||||||
+125
-8
@@ -30,6 +30,8 @@
|
|||||||
#include <pcre2.h>
|
#include <pcre2.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -72,16 +74,16 @@ static void printsettings(void)
|
|||||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||||
p = &cfg.protocols[i];
|
p = &cfg.protocols[i];
|
||||||
if (p->is_unix) {
|
if (p->is_unix) {
|
||||||
sprintf(buf, "unix socket: %s", p->host);
|
snprintf(buf, sizeof(buf), "unix socket: %s", p->host);
|
||||||
} else {
|
} else {
|
||||||
if (p->resolve_on_forward) {
|
if (p->resolve_on_forward) {
|
||||||
strcpy(buf, "resolve on forward: ");
|
strcpy(buf, "resolve on forward: ");
|
||||||
size_t len = strlen(buf);
|
size_t len = strlen(buf);
|
||||||
sprintf(buf+len, "%s:%s", p->host, p->port);
|
snprintf(buf+len, sizeof(buf)-len, "%s:%s", p->host, p->port);
|
||||||
} else {
|
} else {
|
||||||
sprintaddr(buf, sizeof(buf), p->saddr);
|
sprintaddr(buf, sizeof(buf), p->saddr);
|
||||||
size_t len = strlen(buf);
|
size_t len = strlen(buf);
|
||||||
sprintf(buf+len, " family %d %d",
|
snprintf(buf+len, sizeof(buf)-len, " family %d %d",
|
||||||
p->saddr->ai_family,
|
p->saddr->ai_family,
|
||||||
p->saddr->ai_addr->sa_family);
|
p->saddr->ai_addr->sa_family);
|
||||||
}
|
}
|
||||||
@@ -220,6 +222,77 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sun_path has a fixed, compile-time size (typically 108 chars): a configured
|
||||||
|
* unix socket path that does not fit can never work, so reject it once at
|
||||||
|
* startup rather than at bind()/connect() time */
|
||||||
|
static void config_sanity_unix_path(const char* host)
|
||||||
|
{
|
||||||
|
size_t path_max = sizeof(((struct sockaddr_un*)0)->sun_path);
|
||||||
|
|
||||||
|
if (host && strlen(host) >= path_max) {
|
||||||
|
print_message(msg_config_error,
|
||||||
|
"unix socket path too long (%zd, max %zd): %s\n",
|
||||||
|
strlen(host), path_max - 1, host);
|
||||||
|
exit(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void config_sanity_check(struct sslhcfg_item* cfg)
|
void config_sanity_check(struct sslhcfg_item* cfg)
|
||||||
{
|
{
|
||||||
@@ -233,6 +306,12 @@ void config_sanity_check(struct sslhcfg_item* cfg)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
for (i = 0; i < cfg->listen_len; i++) {
|
||||||
|
config_sanity_listen_proxyprotocol(&cfg->listen[i]);
|
||||||
|
if (cfg->listen[i].is_unix)
|
||||||
|
config_sanity_unix_path(cfg->listen[i].host);
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < cfg->protocols_len; ++i) {
|
for (i = 0; i < cfg->protocols_len; ++i) {
|
||||||
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
|
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
|
||||||
if (cfg->protocols[i].sni_hostnames_len) {
|
if (cfg->protocols[i].sni_hostnames_len) {
|
||||||
@@ -249,6 +328,12 @@ 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_unix)
|
||||||
|
config_sanity_unix_path(cfg->protocols[i].host);
|
||||||
|
|
||||||
if (cfg->protocols[i].is_udp) {
|
if (cfg->protocols[i].is_udp) {
|
||||||
if (cfg->protocols[i].tfo_ok) {
|
if (cfg->protocols[i].tfo_ok) {
|
||||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||||
@@ -284,6 +369,41 @@ 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 main(int argc, char *argv[], char* envp[])
|
||||||
{
|
{
|
||||||
int res, num_addr_listen;
|
int res, num_addr_listen;
|
||||||
@@ -299,7 +419,7 @@ int main(int argc, char *argv[], char* envp[])
|
|||||||
config_finish(&cfg);
|
config_finish(&cfg);
|
||||||
|
|
||||||
if (cfg.version) {
|
if (cfg.version) {
|
||||||
printf("%s %s\n", server_type, VERSION);
|
print_version();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,10 +428,7 @@ int main(int argc, char *argv[], char* envp[])
|
|||||||
|
|
||||||
if (cfg.inetd)
|
if (cfg.inetd)
|
||||||
{
|
{
|
||||||
close(fileno(stderr)); /* Make sure no error will go to client */
|
main_inetd(); /* Does not return */
|
||||||
tcp_init();
|
|
||||||
start_shoveler(0);
|
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printsettings();
|
printsettings();
|
||||||
|
|||||||
+2
-2
@@ -204,7 +204,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
|||||||
int i, res;
|
int i, res;
|
||||||
|
|
||||||
setup_sigalrm();
|
setup_sigalrm();
|
||||||
loop_init(&fd_info, num_addr_listen);
|
loop_init(&fd_info, listen_sockets, num_addr_listen);
|
||||||
|
|
||||||
watchers_init(&fd_info.watchers, listen_sockets, 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 start_shoveler(int listen_socket) {
|
void main_inetd(void) {
|
||||||
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
|
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
=head1 SYNOPSIS
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [B<-v> I<n>] [-i] [-V] [-f] [-n]
|
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--wireguard> I<target address for WireGuard>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [B<-v> I<n>] [-i] [-V] [-f] [-n]
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ configuration file's settings.
|
|||||||
=item B<-t> I<num>, B<--timeout> I<num>
|
=item B<-t> I<num>, B<--timeout> I<num>
|
||||||
|
|
||||||
Timeout before forwarding the connection to the timeout
|
Timeout before forwarding the connection to the timeout
|
||||||
protocol (which should usually be SSH). Default is 2s.
|
protocol (which should usually be SSH). Default is 5s.
|
||||||
|
|
||||||
=item B<--on-timeout> I<protocol name>
|
=item B<--on-timeout> I<protocol name>
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ Makes B<sslh> behave as a transparent proxy, i.e. the
|
|||||||
receiving service sees the original client's IP address.
|
receiving service sees the original client's IP address.
|
||||||
This works on Linux only and involves B<iproute2> settings.
|
This works on Linux only and involves B<iproute2> settings.
|
||||||
In some cases also B<iptables/nftables> settings are needed.
|
In some cases also B<iptables/nftables> settings are needed.
|
||||||
Refer to the README or L<https://github.com/ftasnetamot/sslh/blob/2014-08-16--close-filehandles-with-detach/doc/simple_transparent_proxy.md>
|
Refer to the README or L<https://github.com/yrutschle/sslh/blob/master/doc/simple_transparent_proxy.md>
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
=item B<-p> I<listening address>, B<--listen> I<listening address>
|
=item B<-p> I<listening address>, B<--listen> I<listening address>
|
||||||
@@ -136,6 +136,11 @@ If you wish to accept SSLv2, use B<--anyprot> instead.
|
|||||||
Interface and port on which to forward SSH connections,
|
Interface and port on which to forward SSH connections,
|
||||||
typically I<localhost:22>.
|
typically I<localhost:22>.
|
||||||
|
|
||||||
|
=item B<--wireguard> I<target address>
|
||||||
|
|
||||||
|
Interface and port on which to forward WireGuard connections,
|
||||||
|
typically I<localhost:51820>.
|
||||||
|
|
||||||
=item B<--openvpn> I<target address>
|
=item B<--openvpn> I<target address>
|
||||||
|
|
||||||
Interface and port on which to forward OpenVPN connections,
|
Interface and port on which to forward OpenVPN connections,
|
||||||
|
|||||||
@@ -307,12 +307,12 @@ int main(int argc, char *argv[]) {
|
|||||||
err_log = stderr;
|
err_log = stderr;
|
||||||
}
|
}
|
||||||
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
|
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
|
||||||
if (systemd_invoked) {
|
|
||||||
fclose(err_log);
|
|
||||||
}
|
|
||||||
if (!r) {
|
if (!r) {
|
||||||
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
|
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
|
||||||
}
|
}
|
||||||
|
if (systemd_invoked) {
|
||||||
|
fclose(err_log);
|
||||||
|
}
|
||||||
return r < 0 ? -1 : 0;
|
return r < 0 ? -1 : 0;
|
||||||
} else {
|
} else {
|
||||||
printf("This program takes three or no arguments.\n");
|
printf("This program takes three or no arguments.\n");
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
# ./run # run all tests
|
# ./run # run all tests
|
||||||
# ./run -l # list all tests
|
# ./run -l # list all tests
|
||||||
# ./run 1 3 5 # run specified tests
|
# ./run 1 3 5 # run specified tests
|
||||||
|
# ./run --binary sslh-ev # run only one binary
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use IO::Socket::INET6;
|
use IO::Socket::INET6;
|
||||||
@@ -17,9 +18,13 @@ use Test::More qw/no_plan/;
|
|||||||
use Conf::Libconfig 1.0.3;
|
use Conf::Libconfig 1.0.3;
|
||||||
use Getopt::Long;
|
use Getopt::Long;
|
||||||
|
|
||||||
my ($coverage, $list_tests);
|
# --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);
|
||||||
GetOptions(
|
GetOptions(
|
||||||
'cover' => \$coverage,
|
'cover' => \$coverage,
|
||||||
|
'binary=s' => \$param_binary,
|
||||||
'list' => \$list_tests,
|
'list' => \$list_tests,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -192,7 +197,8 @@ sub run_ending_test
|
|||||||
|
|
||||||
# Runs one test for one probe. Start echosrv's if required.
|
# Runs one test for one probe. Start echosrv's if required.
|
||||||
# Connect to port specified in the test, otherwise to the
|
# Connect to port specified in the test, otherwise to the
|
||||||
# first port in the sslh configuration.
|
# first port in the sslh configuration; to host specified in
|
||||||
|
# the test, otherwise to localhost
|
||||||
# run sslh, connect, write the test pattern, read the result,
|
# run sslh, connect, write the test pattern, read the result,
|
||||||
# check it connected to the right echosrv, check the data
|
# check it connected to the right echosrv, check the data
|
||||||
# was transfered ok.
|
# was transfered ok.
|
||||||
@@ -206,9 +212,10 @@ sub run_test_probe
|
|||||||
|
|
||||||
my $expected = $test->{expected};
|
my $expected = $test->{expected};
|
||||||
|
|
||||||
|
my $sslh_host = $test->{host} // "localhost";
|
||||||
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
|
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
|
||||||
print "test_probe [$expected] $sslh_port\n";
|
print "test_probe [$expected] $sslh_host:$sslh_port\n";
|
||||||
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
my $cnx = new IO::Socket::INET(PeerHost => "$sslh_host:$sslh_port");
|
||||||
warn "t: $!\n" unless $cnx;
|
warn "t: $!\n" unless $cnx;
|
||||||
return unless $cnx;
|
return unless $cnx;
|
||||||
|
|
||||||
@@ -316,6 +323,7 @@ my @tests = (
|
|||||||
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
|
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
# check socks5 does not
|
# check socks5 does not
|
||||||
{
|
{
|
||||||
desc => "Server-side ProxyProtocol not active",
|
desc => "Server-side ProxyProtocol not active",
|
||||||
@@ -325,6 +333,55 @@ my @tests = (
|
|||||||
expected => "socks5",
|
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
|
# test_pp2.cfg has proxyprotocol on port 8080
|
||||||
{
|
{
|
||||||
desc => "Client-side ProxyProtocol active",
|
desc => "Client-side ProxyProtocol active",
|
||||||
@@ -388,10 +445,15 @@ if ($list_tests) {
|
|||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Run selected tests
|
# Run selected tests
|
||||||
foreach my $test (@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";
|
warn "Running test: $test->{desc}\n";
|
||||||
my $binary = 'sslh-ev';
|
|
||||||
my $code = ($test->{run})->($binary, $test);
|
my $code = ($test->{run})->($binary, $test);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_echosrv();
|
stop_echosrv();
|
||||||
|
|||||||
+4
-4
@@ -29,15 +29,15 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
|
|||||||
# Options:
|
# Options:
|
||||||
listen:
|
listen:
|
||||||
(
|
(
|
||||||
{ host: "localhost"; port: "8080"; keepalive: true; }
|
{ host: "ip4-localhost"; port: "8080"; keepalive: true; }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
protocols:
|
protocols:
|
||||||
(
|
(
|
||||||
{ name: "ssh"; host: "localhost"; port: "9000"; proxyprotocol: 1; },
|
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
|
||||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
{ name: "socks5"; host: "ip4-localhost"; port: "9001"; },
|
||||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
{ name: "anyprot"; host: "ip4-localhost"; port: "9099"; }
|
||||||
);
|
);
|
||||||
|
|
||||||
on_timeout: "ssh";
|
on_timeout: "ssh";
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# 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";
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# 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";
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# 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";
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# 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";
|
||||||
|
|
||||||
+37
-5
@@ -30,14 +30,16 @@
|
|||||||
/* Removes cnx from probing list */
|
/* Removes cnx from probing list */
|
||||||
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
||||||
{
|
{
|
||||||
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
|
if (!gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing)) {
|
||||||
fd_info->num_probing--;
|
fd_info->num_probing--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
static void add_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
||||||
{
|
{
|
||||||
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
|
if (!gap_set(fd_info->probing_list, fd_info->num_probing, cnx)) {
|
||||||
fd_info->num_probing++;
|
fd_info->num_probing++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* shovels data from active fd to the other
|
/* shovels data from active fd to the other
|
||||||
@@ -53,7 +55,6 @@ static void shovel(struct connection *cnx, int active_fd, struct loop_info* fd_i
|
|||||||
print_message(msg_fd, "activity on fd%d\n", read_q->fd);
|
print_message(msg_fd, "activity on fd%d\n", read_q->fd);
|
||||||
|
|
||||||
switch(fd2fd(write_q, read_q)) {
|
switch(fd2fd(write_q, read_q)) {
|
||||||
case -1:
|
|
||||||
case FD_CNXCLOSED:
|
case FD_CNXCLOSED:
|
||||||
tidy_connection(cnx, fd_info);
|
tidy_connection(cnx, fd_info);
|
||||||
break;
|
break;
|
||||||
@@ -296,6 +297,37 @@ 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 */
|
/* Forks a shoveler process for one protocol */
|
||||||
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
@@ -307,8 +339,8 @@ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
|||||||
|
|
||||||
switch (pid = fork()) {
|
switch (pid = fork()) {
|
||||||
case 0: /* child */
|
case 0: /* child */
|
||||||
/* TODO: close all file descriptors except 2 */
|
close_listen_endpoints(fd_info);
|
||||||
/* free(cnx); */
|
tidy_other_connections(fd_info, cnx);
|
||||||
connect_proxy(cnx);
|
connect_proxy(cnx);
|
||||||
exit(0);
|
exit(0);
|
||||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||||
|
|||||||
@@ -46,10 +46,6 @@ int probe_client_protocol(struct connection *cnx)
|
|||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
defer_write(&cnx->q[1], buffer, n);
|
defer_write(&cnx->q[1], buffer, n);
|
||||||
|
|
||||||
print_message(msg_packets, "hexdump of incoming packet:\n");
|
|
||||||
hexdump(msg_packets, cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size);
|
|
||||||
|
|
||||||
|
|
||||||
size_t pp_len = 0;
|
size_t pp_len = 0;
|
||||||
if (cnx->endpoint->endpoint_cfg->proxyprotocol) {
|
if (cnx->endpoint->endpoint_cfg->proxyprotocol) {
|
||||||
pp_len = pp_header_len(cnx->q[1].begin_deferred_data,
|
pp_len = pp_header_len(cnx->q[1].begin_deferred_data,
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ has_match(const char** list, size_t list_len, const char* name, size_t name_len)
|
|||||||
|
|
||||||
for (i = 0; i < list_len; i++) {
|
for (i = 0; i < list_len; i++) {
|
||||||
item = &list[i];
|
item = &list[i];
|
||||||
print_message(msg_probe_error, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
print_message(msg_probe_info, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
||||||
if(!fnmatch(*item, name_nullterminated, 0)) {
|
if(!fnmatch(*item, name_nullterminated, 0)) {
|
||||||
free(name_nullterminated);
|
free(name_nullterminated);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
+8
-10
@@ -239,12 +239,11 @@ static int nonblocking_socket(struct sslhcfg_protocols_item* proto)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
||||||
|
CHECK_RES_RETURN(out, "udp:socket:socket", -1);
|
||||||
|
|
||||||
res = set_nonblock(out);
|
res = set_nonblock(out);
|
||||||
if (res == -1) {
|
CHECK_RES_RETURN(out, "udp:socket:nonblock", -1);
|
||||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, "udp:socket:nonblock", errno, strerror(errno));
|
|
||||||
close(out);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,10 +272,9 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
|||||||
|
|
||||||
addrlen = sizeof(src_addr);
|
addrlen = sizeof(src_addr);
|
||||||
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
|
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
|
||||||
if (len < 0) {
|
if ((len == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return NULL;
|
||||||
perror("recvfrom");
|
CHECK_RES_DIE(len, "udp_listener/c2s/recvfrom");
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
target = known_source(fd_info->hash_sources, &src_addr, addrlen);
|
target = known_source(fd_info->hash_sources, &src_addr, addrlen);
|
||||||
addrinfo.ai_addr = (struct sockaddr*) &src_addr;
|
addrinfo.ai_addr = (struct sockaddr*) &src_addr;
|
||||||
addrinfo.ai_addrlen = addrlen;
|
addrinfo.ai_addrlen = addrlen;
|
||||||
@@ -337,7 +335,7 @@ void udp_s2c_forward(struct connection* cnx)
|
|||||||
|
|
||||||
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
||||||
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
|
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
|
||||||
CHECK_RES_DIE(res, "udp_listener/recvfrom");
|
CHECK_RES_DIE(res, "udp_listener/s2c/recvfrom");
|
||||||
res = sendto(cnx->local_endpoint, data, res, 0,
|
res = sendto(cnx->local_endpoint, data, res, 0,
|
||||||
(struct sockaddr*)&cnx->client_addr, cnx->addrlen);
|
(struct sockaddr*)&cnx->client_addr, cnx->addrlen);
|
||||||
mark_active(cnx);
|
mark_active(cnx);
|
||||||
|
|||||||
Reference in new issue
Block a user