mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-25 01:06:20 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f462ba166 | ||
|
|
0e7885bc9f | ||
|
|
5ad1ea1e25 | ||
|
|
4978641271 | ||
|
|
ef6f698d86 | ||
|
|
416a82fcc6 | ||
|
|
2f111b6b8d | ||
|
|
24c3bb07a0 | ||
|
|
951b708f61 | ||
|
|
5a0897c5cb | ||
|
|
7a6673a877 | ||
|
|
3ebdca5e96 | ||
|
|
22a8ba9ef5 | ||
|
|
dcfa3fa2db | ||
|
|
fabf0a121c | ||
|
|
3a1c31d8cb | ||
|
|
e527b8e588 | ||
|
|
a2b4da8483 | ||
|
|
710807fd3b | ||
|
|
2e9f23a2f4 | ||
|
|
bf082292c2 | ||
|
|
59d89e34f0 | ||
|
|
cac7f48fa7 | ||
|
|
9e6b4fae29 | ||
|
|
16ef412663 | ||
|
|
5f55f7d76a | ||
|
|
9243a6e369 | ||
|
|
686d1f7cb6 | ||
|
|
e7a9a37624 | ||
|
|
7d41760f9a | ||
|
|
4def95865c | ||
|
|
109052fdc7 | ||
|
|
d6bb000115 | ||
|
|
18a9a882f5 | ||
|
|
acdbb79d43 | ||
|
|
1fd072837b | ||
|
|
a34f34917a | ||
|
|
d6041c93c4 | ||
|
|
2e2701de55 | ||
|
|
70a9b97f81 | ||
|
|
72a4376248 | ||
|
|
316e9a1863 | ||
|
|
c892fc4b34 | ||
|
|
b619f5bf36 | ||
|
|
06b7d7ed14 |
No files matched your search
@@ -10,7 +10,6 @@ sslh-ev
|
||||
systemd-sslh-generator
|
||||
sslh.8.gz
|
||||
tags
|
||||
version.h
|
||||
/config.status
|
||||
/config.log
|
||||
/config.h
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
v2.2.1:
|
||||
Added a boolean setting "is_unix" for listen and
|
||||
protocol entries. This will use the 'host' setting
|
||||
as a path name to a socket file, and connections
|
||||
(listening or connecting) will be performed on Unix
|
||||
socket instead of Internet sockets.
|
||||
|
||||
Support HAProxy's proxyprotocol on the backend
|
||||
server side.
|
||||
|
||||
Lots of documentation about a new, simpler way to
|
||||
perform transparent proxying.
|
||||
|
||||
New "verbose" option that overrides all other
|
||||
verbose settings.
|
||||
|
||||
v2.1.3:
|
||||
Fix Landlock access to /etc/hosts.deny and
|
||||
/etc/hosts.allow.
|
||||
|
||||
+9
-23
@@ -3,15 +3,14 @@ VERSION=$(shell ./genver.sh -r)
|
||||
|
||||
# Configuration -- you probably need to `make clean` if you
|
||||
# change any of these
|
||||
# landlock is checked by `configure` and set in config.h
|
||||
|
||||
# uncomment the following line to disable landlock
|
||||
# override undefine HAVE_LANDLOCK
|
||||
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBEV=1 # Use libev?
|
||||
USELIBWRAP?= # Use libwrap?
|
||||
USELIBCAP= # Use libcap?
|
||||
USESYSTEMD= # Make use of systemd socket activation
|
||||
USELIBBSD?= # Use libbsd (needed to update process name in `ps`)
|
||||
COV_TEST= # Perform test coverage?
|
||||
PREFIX?=/usr
|
||||
BINDIR?=$(PREFIX)/sbin
|
||||
@@ -35,8 +34,8 @@ AR ?= ar
|
||||
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
|
||||
LIBS=-lm -lpcre2-8
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o
|
||||
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_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)
|
||||
@@ -44,11 +43,6 @@ EV_OBJS=processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o $(OBJS_A)
|
||||
|
||||
CONDITIONAL_TARGETS=
|
||||
|
||||
ifneq ($(strip $(USELIBWRAP)),)
|
||||
LIBS:=$(LIBS) -lwrap
|
||||
CPPFLAGS+=-DLIBWRAP
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(ENABLE_REGEX)),)
|
||||
CPPFLAGS+=-DENABLE_REGEX
|
||||
endif
|
||||
@@ -58,22 +52,12 @@ ifneq ($(strip $(USELIBCONFIG)),)
|
||||
CPPFLAGS+=-DLIBCONFIG
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBCAP)),)
|
||||
LIBS:=$(LIBS) -lcap
|
||||
CPPFLAGS+=-DLIBCAP
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USESYSTEMD)),)
|
||||
LIBS:=$(LIBS) -lsystemd
|
||||
CPPFLAGS+=-DSYSTEMD
|
||||
CONDITIONAL_TARGETS+=systemd-sslh-generator
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBBSD)),)
|
||||
LIBS:=$(LIBS) -lbsd
|
||||
CPPFLAGS+=-DLIBBSD
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBEV)),)
|
||||
CONDITIONAL_TARGETS+=sslh-ev
|
||||
endif
|
||||
@@ -86,8 +70,9 @@ all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
||||
$(OBJS_A): $(OBJS)
|
||||
$(AR) rcs $(OBJS_A) $(OBJS)
|
||||
|
||||
version.h:
|
||||
version.h: .FORCE
|
||||
./genver.sh >version.h
|
||||
.FORCE:
|
||||
|
||||
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
|
||||
|
||||
@@ -126,7 +111,7 @@ $(MAN): sslh.pod Makefile
|
||||
# Create release: export clean tree and tag current
|
||||
# configuration
|
||||
release:
|
||||
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
|
||||
git archive $(VERSION) --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
|
||||
gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz
|
||||
|
||||
# Build docker image
|
||||
@@ -160,6 +145,7 @@ distclean: clean
|
||||
|
||||
clean:
|
||||
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
echo "// this is a placeholder for version.h, to make code-checking editors happy" > version.h
|
||||
|
||||
tags:
|
||||
ctags --globals -T *.[ch]
|
||||
|
||||
@@ -20,10 +20,10 @@ address.
|
||||
|
||||
`sslh` has the bells and whistles expected from a mature
|
||||
daemon: privilege and capabilities dropping, inetd support,
|
||||
systemd support, transparent proxying, chroot, logging,
|
||||
IPv4 and IPv6, TCP and UDP, a fork-based, a select-based
|
||||
model, and yet another based on libev for larger
|
||||
installations.
|
||||
systemd support, transparent proxying, support for HAProxy's
|
||||
proxyprotocol, chroot, logging, IPv4 and IPv6, TCP and UDP,
|
||||
a fork-based, a select-based model, and yet another based on
|
||||
libev for larger installations.
|
||||
|
||||
Install
|
||||
=======
|
||||
@@ -42,6 +42,12 @@ Transparent proxying
|
||||
Transparent proxying allows the target server to see the
|
||||
original client IP address, i.e. `sslh` becomes invisible.
|
||||
|
||||
The same result can be achieved more easily by using
|
||||
`proxyprotocol` if the backend server supports it. This is a
|
||||
simple setting to add to the `sslh` protocol configuration,
|
||||
usually with an equivalently simple setting to add in
|
||||
the backend server configuration, so try that first.
|
||||
|
||||
This means services behind `sslh` (Apache, `sshd` and so on)
|
||||
will see the external IP and ports as if the external world
|
||||
connected directly to them. This simplifies IP-based access
|
||||
|
||||
@@ -12,11 +12,18 @@
|
||||
#include <sys/types.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "proxyprotocol.h"
|
||||
|
||||
#if HAVE_LIBCAP
|
||||
#include <sys/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
* */
|
||||
@@ -30,7 +37,7 @@
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
#ifdef HAVE_LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -42,7 +49,7 @@ struct sslhcfg_item cfg;
|
||||
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
|
||||
|
||||
|
||||
#ifdef LIBWRAP
|
||||
#ifdef HAVE_LIBWRAP
|
||||
#include <tcpd.h>
|
||||
int allow_severity =0, deny_severity = 0;
|
||||
#endif
|
||||
@@ -155,17 +162,76 @@ int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
|
||||
/* Start listening internet sockets for configuration entry 'index'
|
||||
* OUT: *sockfd[]: pointer to array of listen_endpoint object; we append new
|
||||
* endpoints to that array
|
||||
* IN: num_addr: how many entries are in sockfd[]
|
||||
* *cfg: configuration data for the endpoint we are adding
|
||||
* Return: new value of num_addr
|
||||
* */
|
||||
static int start_listen_inet(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg)
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int res;
|
||||
|
||||
res = resolve_split_name(&start_addr, cfg->host, cfg->port);
|
||||
if (res) exit(4);
|
||||
|
||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, cfg->keepalive, cfg->is_udp);
|
||||
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
(*sockfd)[num_addr-1].family = AF_INET;
|
||||
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
|
||||
cfg->keepalive ? "keepalive" : "",
|
||||
cfg->is_udp ? "udp" : "");
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
/* Same, but for UNIX sockets */
|
||||
static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg)
|
||||
{
|
||||
int fd = socket(AF_UNIX, cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
CHECK_RES_DIE(fd, "socket(AF_UNIX)");
|
||||
|
||||
int res = unlink(cfg->host);
|
||||
if ((res == -1) && (errno != ENOENT)) {
|
||||
print_message(msg_config_error, "unlink unix socket `%s':%d:%s\n", cfg->host, errno, strerror(errno));
|
||||
exit(4);
|
||||
}
|
||||
|
||||
struct sockaddr_un sun;
|
||||
sun.sun_family = AF_UNIX;
|
||||
strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1);
|
||||
printf("binding [%s]\n", sun.sun_path);
|
||||
res = bind(fd, (struct sockaddr*)&sun, sizeof(sun));
|
||||
CHECK_RES_DIE(res, "bind(AF_UNIX)");
|
||||
|
||||
res = listen(fd, 50);
|
||||
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
(*sockfd)[num_addr-1].socketfd = fd;
|
||||
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
(*sockfd)[num_addr-1].family = AF_INET;
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
|
||||
/* Starts listening sockets on specified addresses.
|
||||
* OUT: *sockfd[] pointer to newly-allocated array of listen_endpoint objects
|
||||
* Returns number of addresses bound
|
||||
*/
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int i, res;
|
||||
int num_addr = 0, keepalive = 0, udp = 0;
|
||||
int sd_socks = 0;
|
||||
int i;
|
||||
int num_addr = 0, sd_socks = 0;
|
||||
|
||||
sd_socks = get_fd_sockets(sockfd);
|
||||
|
||||
@@ -178,22 +244,11 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
print_message(msg_config, "Listening to:\n");
|
||||
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
keepalive = cfg.listen[i].keepalive;
|
||||
udp = cfg.listen[i].is_udp;
|
||||
|
||||
res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port);
|
||||
if (res) exit(4);
|
||||
|
||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
|
||||
cfg.listen[i].keepalive ? "keepalive" : "",
|
||||
cfg.listen[i].is_udp ? "udp" : "");
|
||||
if (cfg.listen[i].is_unix) {
|
||||
num_addr = start_listen_unix(sockfd, num_addr, &cfg.listen[i]);
|
||||
} else {
|
||||
num_addr = start_listen_inet(sockfd, num_addr, &cfg.listen[i]);
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
@@ -243,12 +298,17 @@ int is_same_machine(struct addrinfo* from)
|
||||
|
||||
/* Transparent proxying: bind the peer address of fd to the peer address of
|
||||
* fd_from */
|
||||
#define IP_TRANSPARENT 19
|
||||
#ifndef IP_TRANSPARENT
|
||||
#define IP_TRANSPARENT 19
|
||||
#endif
|
||||
#ifndef IP_BIND_ADDRESS_NO_PORT
|
||||
#define IP_BIND_ADDRESS_NO_PORT 24
|
||||
#endif
|
||||
int bind_peer(int fd, int fd_from)
|
||||
{
|
||||
struct addrinfo from;
|
||||
struct sockaddr_storage ss;
|
||||
int res, trans = 1;
|
||||
int res, enable = 1, disable = 0;
|
||||
|
||||
memset(&from, 0, sizeof(from));
|
||||
from.ai_addr = (struct sockaddr*)&ss;
|
||||
@@ -264,15 +324,17 @@ int bind_peer(int fd, int fd_from)
|
||||
return 0;
|
||||
|
||||
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &enable, sizeof(enable));
|
||||
CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT");
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
|
||||
CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR");
|
||||
#else
|
||||
if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans));
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(enable));
|
||||
CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res);
|
||||
#ifdef IPV6_BINDANY
|
||||
} else { /* IPv6 */
|
||||
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans));
|
||||
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &enable, sizeof(enable));
|
||||
CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res);
|
||||
#endif /* IPV6_BINDANY */
|
||||
}
|
||||
@@ -284,19 +346,30 @@ int bind_peer(int fd, int fd_from)
|
||||
"bind", errno, strerror(errno));
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is more than one transparent mode proxy going on, such as
|
||||
* using sslh as the target of stunnel also in transparent mode, then
|
||||
* the (ip,port) combination will already be bound for the previous application.
|
||||
* In that case, the best we can do is bind with a different port.
|
||||
* This does mean the local server can't use the ident protocol as the port will
|
||||
* have changed, but most people won't care.
|
||||
* Also note that stunnel uses the same logic for the same situation.
|
||||
*/
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(disable));
|
||||
CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR");
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, &enable, sizeof(enable));
|
||||
CHECK_RES_RETURN(res, "setsockopt IP_BIND_ADDRESS_NO_PORT", res);
|
||||
((struct sockaddr_in *)from.ai_addr)->sin_port = 0;
|
||||
res = bind(fd, from.ai_addr, from.ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "bind", res);
|
||||
/*
|
||||
* There was a serious problem, when daisy-chaining programs using the same
|
||||
* ip transparent mechanism, as sslh uses. stunnel was mentioned in a previous
|
||||
* comment. This problem should now be solved through the two methods, getting
|
||||
* a connection established:
|
||||
* In the first try, SO_REUSEADDR is set to socket, which will allow the same
|
||||
* IP-address:port tuple, as it is used by another application. The check for
|
||||
* inconsistency with other connections (same 4-value-tupel) is done at the
|
||||
* moment, when the connection gets established.
|
||||
* If that fails, SO_REUSEADDR gets removed and IP_BIND_ADDRESS_NO_PORT get set.
|
||||
* This will search for a free port, which will not collide with current
|
||||
* connections. Read more in this excellent blog-post:
|
||||
* https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections
|
||||
* The problem will still appear, if the another application in the daisy-chain
|
||||
* does not use similar mechanisms. In that case you must either pull this
|
||||
* application at the beginning of the chain, or get it fixed.
|
||||
*/
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -319,11 +392,8 @@ int set_nonblock(int fd)
|
||||
}
|
||||
|
||||
|
||||
/* Connect to first address that works and returns a file descriptor, or -1 if
|
||||
* none work.
|
||||
* If transparent proxying is on, use fd_from peer address on external address
|
||||
* of new file descriptor. */
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
/* Connects to INET/INET6 domain sockets and return a fd */
|
||||
static int connect_inet(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
struct addrinfo *a, from;
|
||||
struct sockaddr_storage ss;
|
||||
@@ -342,10 +412,10 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
resolve_split_name(&(cnx->proto->saddr), cnx->proto->host,
|
||||
cnx->proto->port);
|
||||
}
|
||||
|
||||
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
||||
/* When transparent, make sure both connections use the same address family */
|
||||
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),
|
||||
@@ -390,6 +460,52 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Connects to AF_UNIX domain sockets */
|
||||
static int connect_unix(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
struct sockaddr_storage ss;
|
||||
struct sockaddr_un* sun = (struct sockaddr_un*)&ss;
|
||||
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
sun->sun_family = AF_UNIX;
|
||||
strncpy(sun->sun_path, cnx->proto->host, sizeof(sun->sun_path)-1);
|
||||
|
||||
int res = connect(fd, (struct sockaddr*)sun, sizeof(*sun));
|
||||
CHECK_RES_RETURN(res, "connect", res);
|
||||
|
||||
if (blocking == NON_BLOCKING) {
|
||||
set_nonblock(fd);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connect to the first backend server address that works and updates the *cnx
|
||||
* object accordingly (in cnx->q[1].fd). Set that to -1 in case of failure.
|
||||
*
|
||||
* If transparent proxying is on, use fd_from peer address on external address
|
||||
* of new file descriptor.
|
||||
* If proxyprotocol is used, write header on new backend server connection
|
||||
* */
|
||||
void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (cnx->proto->is_unix) {
|
||||
fd = connect_unix(cnx, fd_from, blocking);
|
||||
} else {
|
||||
fd = connect_inet(cnx, fd_from, blocking);
|
||||
}
|
||||
cnx->q[1].fd = fd;
|
||||
|
||||
if (cnx->proto->proxyprotocol_is_present) {
|
||||
pp_write_header(cnx->proto->proxyprotocol, cnx);
|
||||
/* If pp_write_header() fails, it already logs a message and there is
|
||||
* nothing much we can do. The server side will probably close the
|
||||
* connection */
|
||||
}
|
||||
}
|
||||
|
||||
/* Store some data to write to the queue later */
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size)
|
||||
{
|
||||
@@ -409,6 +525,27 @@ int defer_write(struct queue *q, void* data, ssize_t data_size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Store some data to write *before* what's already in the queue */
|
||||
int defer_write_before(struct queue *q, void* data, ssize_t data_size)
|
||||
{
|
||||
char *p;
|
||||
|
||||
print_message(msg_fd, "writing deferred to beginning on fd %d\n", q->fd);
|
||||
p = malloc(q->deferred_data_size + data_size);
|
||||
CHECK_ALLOC(p, "malloc");
|
||||
|
||||
memcpy(p, data, data_size);
|
||||
memcpy(p + data_size, q->deferred_data, q->deferred_data_size);
|
||||
|
||||
free(q->begin_deferred_data);
|
||||
|
||||
q->begin_deferred_data = p;
|
||||
q->deferred_data = p;
|
||||
q->deferred_data_size += (int)data_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* tries to flush some of the data for specified queue
|
||||
* Upon success, the number of bytes written is returned.
|
||||
* Upon failure, -1 returned (e.g. connexion closed)
|
||||
@@ -525,6 +662,9 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
char host[NI_MAXHOST], serv[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
memset(host, 0, sizeof(host));
|
||||
memset(serv, 0, sizeof(serv));
|
||||
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
@@ -648,7 +788,7 @@ int get_connection_desc(struct connection_desc* desc, const struct connection *c
|
||||
|
||||
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
#ifdef HAVE_LIBBSD
|
||||
struct connection_desc d;
|
||||
|
||||
if (!desc) {
|
||||
@@ -673,7 +813,7 @@ void set_proctitle_shovel(struct connection_desc* desc, const struct connection
|
||||
*/
|
||||
int check_access_rights(int in_socket, const char* service)
|
||||
{
|
||||
#ifdef LIBWRAP
|
||||
#ifdef HAVE_LIBWRAP
|
||||
union {
|
||||
struct sockaddr saddr;
|
||||
struct sockaddr_storage ss;
|
||||
@@ -700,7 +840,7 @@ int check_access_rights(int in_socket, const char* service)
|
||||
}
|
||||
}
|
||||
|
||||
if (!hosts_ctl(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);
|
||||
close(in_socket);
|
||||
return -1;
|
||||
@@ -739,7 +879,7 @@ void setup_signals(void)
|
||||
|
||||
/* Ask OS to keep capabilities over a setuid(nonzero) */
|
||||
void set_keepcaps(int val) {
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
int res;
|
||||
res = prctl(PR_SET_KEEPCAPS, val, 0, 0, 0);
|
||||
if (res) {
|
||||
@@ -752,7 +892,7 @@ void set_keepcaps(int val) {
|
||||
/* Returns true if anything requires transparent proxying. */
|
||||
static int use_transparent(void)
|
||||
{
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
if (cfg.transparent)
|
||||
return 1;
|
||||
|
||||
@@ -768,7 +908,7 @@ static int use_transparent(void)
|
||||
* IN: cap_net_admin: set to 1 to set CAP_NET_RAW
|
||||
* */
|
||||
void set_capabilities(int cap_net_admin) {
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
int res;
|
||||
cap_t caps;
|
||||
cap_value_t cap_list[10];
|
||||
@@ -861,7 +1001,6 @@ void write_pid_file(const char* pidfile)
|
||||
res = fprintf(f, "%d\n", getpid());
|
||||
if (res < 0) {
|
||||
print_message(msg_system_error, "write_pid_file: fprintf: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
res = fclose(f);
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
#include "version.h"
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
@@ -132,6 +133,7 @@ struct connection {
|
||||
struct listen_endpoint {
|
||||
int socketfd; /* file descriptor of listening socket */
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
int family; /* AF_INET | AF_UNIX */
|
||||
};
|
||||
|
||||
#define FD_CNXCLOSED 0
|
||||
@@ -154,7 +156,7 @@ typedef enum {
|
||||
/* common.c */
|
||||
void init_cnx(struct connection *cnx);
|
||||
int set_nonblock(int fd);
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
|
||||
void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
|
||||
int fd2fd(struct queue *target, struct queue *from);
|
||||
char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
|
||||
void resolve_name(struct addrinfo **out, char* fullname);
|
||||
@@ -173,6 +175,7 @@ int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[]);
|
||||
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size);
|
||||
int defer_write_before(struct queue *q, void* data, ssize_t data_size);
|
||||
int flush_deferred(struct queue *q);
|
||||
|
||||
extern struct sslhcfg_item cfg;
|
||||
|
||||
+12
@@ -3,7 +3,19 @@
|
||||
#ifndef CONFIG_H
|
||||
/* Template for config.h, filled by `configure`. */
|
||||
|
||||
/* Libwrap, to support host_ctl, /etc/allow and /etc/deny */
|
||||
#undef HAVE_LIBWRAP
|
||||
|
||||
/* Landlock sandboxing Linux LSM */
|
||||
#undef HAVE_LANDLOCK
|
||||
|
||||
/* Support for Proxy-protocol using libproxyprotocol */
|
||||
#undef HAVE_PROXYPROTOCOL
|
||||
|
||||
/* libcap support, to use Linux capabilities */
|
||||
#undef HAVE_LIBCAP
|
||||
|
||||
/* libbsd, to change process name */
|
||||
#undef HAVE_LIBBSD
|
||||
|
||||
#endif
|
||||
@@ -1445,6 +1445,53 @@ fi
|
||||
|
||||
} # ac_fn_c_try_compile
|
||||
|
||||
# ac_fn_c_try_link LINENO
|
||||
# -----------------------
|
||||
# Try to link conftest.$ac_ext, and return whether this succeeded.
|
||||
ac_fn_c_try_link ()
|
||||
{
|
||||
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext
|
||||
if { { ac_try="$ac_link"
|
||||
case "(($ac_try" in
|
||||
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
|
||||
*) ac_try_echo=$ac_try;;
|
||||
esac
|
||||
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
|
||||
printf "%s\n" "$ac_try_echo"; } >&5
|
||||
(eval "$ac_link") 2>conftest.err
|
||||
ac_status=$?
|
||||
if test -s conftest.err; then
|
||||
grep -v '^ *+' conftest.err >conftest.er1
|
||||
cat conftest.er1 >&5
|
||||
mv -f conftest.er1 conftest.err
|
||||
fi
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; } && {
|
||||
test -z "$ac_c_werror_flag" ||
|
||||
test ! -s conftest.err
|
||||
} && test -s conftest$ac_exeext && {
|
||||
test "$cross_compiling" = yes ||
|
||||
test -x conftest$ac_exeext
|
||||
}
|
||||
then :
|
||||
ac_retval=0
|
||||
else $as_nop
|
||||
printf "%s\n" "$as_me: failed program was:" >&5
|
||||
sed 's/^/| /' conftest.$ac_ext >&5
|
||||
|
||||
ac_retval=1
|
||||
fi
|
||||
# Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
|
||||
# created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
|
||||
# interfere with the next link command; also delete a directory that is
|
||||
# left behind by Apple's compiler. We do this before executing the actions.
|
||||
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
|
||||
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
|
||||
as_fn_set_status $ac_retval
|
||||
|
||||
} # ac_fn_c_try_link
|
||||
|
||||
# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
|
||||
# -------------------------------------------------------
|
||||
# Tests whether HEADER exists and can be compiled using the include files in
|
||||
@@ -3159,6 +3206,130 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
|
||||
ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
||||
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for hosts_ctl in -lwrap" >&5
|
||||
printf %s "checking for hosts_ctl in -lwrap... " >&6; }
|
||||
if test ${ac_cv_lib_wrap_hosts_ctl+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lwrap $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 hosts_ctl ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return hosts_ctl ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_wrap_hosts_ctl=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_wrap_hosts_ctl=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_wrap_hosts_ctl" >&5
|
||||
printf "%s\n" "$ac_cv_lib_wrap_hosts_ctl" >&6; }
|
||||
if test "x$ac_cv_lib_wrap_hosts_ctl" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_LIBWRAP 1" >>confdefs.h
|
||||
LIBS="$LIBS -lwrap"
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for cap_get_proc in -lcap" >&5
|
||||
printf %s "checking for cap_get_proc in -lcap... " >&6; }
|
||||
if test ${ac_cv_lib_cap_cap_get_proc+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lcap $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 cap_get_proc ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return cap_get_proc ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_cap_cap_get_proc=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_cap_cap_get_proc=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_cap_cap_get_proc" >&5
|
||||
printf "%s\n" "$ac_cv_lib_cap_cap_get_proc" >&6; }
|
||||
if test "x$ac_cv_lib_cap_cap_get_proc" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_LIBCAP 1" >>confdefs.h
|
||||
LIBS="$LIBS -lcap"
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setproctitle in -lbsd" >&5
|
||||
printf %s "checking for setproctitle in -lbsd... " >&6; }
|
||||
if test ${ac_cv_lib_bsd_setproctitle+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lbsd $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 setproctitle ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return setproctitle ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_bsd_setproctitle=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_bsd_setproctitle=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_bsd_setproctitle" >&5
|
||||
printf "%s\n" "$ac_cv_lib_bsd_setproctitle" >&6; }
|
||||
if test "x$ac_cv_lib_bsd_setproctitle" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_LIBBSD 1" >>confdefs.h
|
||||
LIBS="$LIBS -lbsd"
|
||||
fi
|
||||
|
||||
|
||||
ac_header= ac_cache=
|
||||
for ac_item in $ac_header_c_list
|
||||
do
|
||||
@@ -3199,6 +3370,21 @@ then :
|
||||
fi
|
||||
|
||||
done
|
||||
for ac_header in proxy_protocol.h
|
||||
do :
|
||||
ac_fn_c_check_header_compile "$LINENO" "proxy_protocol.h" "ac_cv_header_proxy_protocol_h" "$ac_includes_default"
|
||||
if test "x$ac_cv_header_proxy_protocol_h" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_PROXY_PROTOCOL_H 1" >>confdefs.h
|
||||
printf "%s\n" "#define HAVE_PROXYPROTOCOL 1" >>confdefs.h
|
||||
LIBS="$LIBS -lproxyprotocol"
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
LIBS="$LIBS"
|
||||
|
||||
|
||||
cat >confcache <<\_ACEOF
|
||||
# This file is a shell script that caches the results of configure
|
||||
# tests run on this system so they can be shared between configure
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
|
||||
dnl Use autoconf to generate the `configure` script from this and Makefile.in
|
||||
dnl This is awkardly adapted from https://github.com/edrosten/autoconf_tutorial
|
||||
dnl (all mistakes mine)
|
||||
|
||||
AC_INIT
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
|
||||
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" ], [])
|
||||
|
||||
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
|
||||
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
|
||||
|
||||
LIBS="$LIBS"
|
||||
AC_SUBST([LIBS])
|
||||
|
||||
AC_OUTPUT
|
||||
@@ -0,0 +1,283 @@
|
||||
# Daisy-Chaining-Transparency #
|
||||
This documentation goes a level deeper, what happens in the operating system with IP-addresses, and why some combinations of programs are failing, when they use the same transparency method.
|
||||
There are situations, where you need to combine two applications, both working as ip-transparent proxies, to reach your goal. One example is, having nginx or stunnel as an proxytunnel-endpoint for tls tunneled ssh connections through https-proxies. An example for such a combination will be desribed at the end of this article.<br>
|
||||
Unfortunately you will see a lot of errors popping out: **Address already in use**<br>
|
||||
[This article from Cloudflare blog](https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections) explains why this is happening, while it is describing the solution to another problem. However this is a close relative to our problem.
|
||||
|
||||
Let us look to the following example: We have sslh (S) accepting connections from a client (C) and forwarding one of those connections to a man-in-the-middle (M), which finally forwards this connection to sshd. If everything works perfectly, we would like to see those connections.
|
||||
|
||||

|
||||
|
||||
But unfortunately we are receiving in many constellations errors, when M tries to open its connection to our final target sshd, here called T.
|
||||
Let us look more close, why that is happening. We need for this two terminal windows on the same server.<br>
|
||||
### First example, uncooperative applications ###
|
||||
As the problem has nothing to do with transparency itself, but only of reuse of same IP-addresses and ports, we avoid the overhead of the additional capablities, to keep the example easy and clear.
|
||||
In the first terminal we are starting python3 and entering the following three lines:
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
>>>
|
||||
```
|
||||
|
||||
Now we are going to the second terminal window, and trying just the same:
|
||||
```
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
OSError: [Errno 98] Address already in use
|
||||
sock.bind(("192.168.255.254", 12346)) ##this however works!!
|
||||
```
|
||||
Here we are getting the error, which caused many of us hours of research.
|
||||
The problem is caused by the fact, that the kernel does not know at the moment of the bind()-call, how we want to use the socket. If we use this as a server socket, and will do a listen()-call as next, this will not work, as for server sockets, the two-value tuple ADDRESS:PORT needs to be unique only to the process, tied to this socket.
|
||||
That is the reason, that there are port ranges, reserved for servers, where the administrator is responsible not to assign the same port to two applications.
|
||||
But as server ports are coming from a range, which will not be used for client connections, a server can be sure, that if it is started at any time in the future, no outgoing client has used its port for an outbound connection.
|
||||
Clients are usually using ports from a so called [ephemeral port range](https://en.wikipedia.org/wiki/Ephemeral_port).
|
||||
However, for clients each connection is valid, as long as one value in the four-tuples describing the connection is different. In our example above, the two values from the destination are different, so this connection could be established (in theory) without conflicts.
|
||||
To make that happen, you need to deploy a special socket option to this socket, to explain, that we "know the risks" and we will reuse the ip-port combination.
|
||||
And, as we see from the second bind() in the second example, the error message: _**Address already in use**_ really means: _**Address:Port already in use**_
|
||||
|
||||
### Taking Care, part I ###
|
||||
|
||||
Ok, now we are entering our two terminals again, pressing Ctrl-D to finish python, and start a new session like this in both terminals:
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
>>>
|
||||
```
|
||||
And we will see, that the problem is solved, when both applications are taking care.
|
||||
|
||||
### Taking Care, part II ###
|
||||
|
||||
Ok, now we are going back to our terminals, pressing again Ctrl-D to finish python, and start new sessions like this:
|
||||
In the first terminal we repeat the input from our first example, without the setsockopt() call:
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
>>>
|
||||
```
|
||||
in the second terminal, we enter our modified cooperative example:
|
||||
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
OSError: [Errno 98] Address already in use
|
||||
>>>
|
||||
```
|
||||
Oops, here is our error again. And this is the reason, why I called this method "cooperative". As the first application has bound to that IP:PORT combination, without telling, that "it knows the risk", the kernel denies us, to use this combinations, as we may break the already active application. The first application is not "cooperative" :-(<br>
|
||||
Ok, but the kernel gives as some more possibilities: As we now get connections from a uncoperative application, we can no longer use the Client-IP-Port combination. We need to use a conflict free port for the client IP, to succeed. So lets get back to terminal 2 and continue after the error with the following commands:
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
OSError: [Errno 98] Address already in use
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
|
||||
>>> sock.setsockopt(socket.SOL_IP, socket.IP_BIND_ADDRESS_NO_PORT, 1)
|
||||
>>> sock.bind(("192.168.255.254", 0))
|
||||
>>>
|
||||
```
|
||||
This last example makes it neccessary, that you use a recent version of the python3 socket library, as older versions, have the option _IP_BIND_ADDRESS_NO_PORT_ not yet defined.
|
||||
With his behaviour, we are telling the kernel, that the kernel should assign us a conflict free port address at latest possible moment, while calling connect().
|
||||
From `man ip`:
|
||||
```
|
||||
Inform the kernel to not reserve an ephemeral port when using bind(2)
|
||||
with a port number of 0. The port will later be automatically chosen
|
||||
at connect(2) time, in a way that allows sharing a source port as
|
||||
long as the 4-tuple is unique.
|
||||
```
|
||||
|
||||
Ok, now with those two actions an application is really ready for cooperative working in ip-transparent chains.
|
||||
If you are running in problems, with any kind of application, where you are redirecting transparent traffic to from sslh, check the following:
|
||||
- Are you using a recent version of sslh, having the described feature enabled
|
||||
- are you using the most recent version of the other application
|
||||
If you can confirm both checks, tell the maintainers of the other application, about possible fixes, and send them a link to this article.
|
||||
|
||||
## Practical Use Of Daisy-Chaining: Proxytunnel Endpoint ##
|
||||
|
||||
One reasons, why we want to combine two programs is related to the core functionality of sslh. You wish to hide your ssh connection behind a https port.
|
||||
But now you would like, to reach this port via the [proxy-tunnel application](https://github.com/proxytunnel/proxytunnel), though an restrictive http(s) proxy. This is in many cases, one of the few methods, to escape from restricted private networks, like in companies, schools and universities. Unfortunately, many of those proxy-servers will check, that the protocol leaving the proxy is really tls. Therefore you need and endpoint in your system, which will terminate the tls-connection and forward the encapsulated ssh stream to the sshd. Sslh can't do tls termination, as this is not a core job of tls. One of the solutions tried here is stunnel. Stunnel can do transparency like sslh, but unfortunately belongs to the uncooperative ip-transparent programs. At the time of writing this article, you can use stunnel as the first-in-chain, and a very recent sslh as second in chain. But nginx (or openresty) is capable of this, however it prefers (at least in the tested versions), mostly the second way just selecting a new random port and not (always) preserving the original source port, what makes debugging of events much easier.
|
||||
|
||||
|
||||
```
|
||||
stream {
|
||||
ssl_preread on;
|
||||
|
||||
map $ssl_preread_server_name $name {
|
||||
default master.yourdomain.top;
|
||||
t1.yourdomain.top t1.yourdomain.top;
|
||||
t2.yourdomain.top t2.yourdomain.top;
|
||||
cryptic.foo.bar location.selfsigned.cert;
|
||||
}
|
||||
|
||||
## $destination port :443 is assumed, beeing as real
|
||||
## webserver. Either anothe nginx http-server or apache
|
||||
## or anything other ...
|
||||
map $ssl_preread_server_name $dest {
|
||||
default 192.168.255.254:443;
|
||||
t1.yourdomain.top 192.168.255.254:443;
|
||||
t2.yourdomain.top 192.168.255.254:444;
|
||||
cryptic.foo.notexist 192.168.255.254:445;
|
||||
}
|
||||
|
||||
|
||||
## this is the server, to handle incoming tcp streams
|
||||
## and dispatching them transparent to $dest
|
||||
## 192.168.255.254:1443 is the address, where
|
||||
## the front facing sslh sends traffic to
|
||||
server {
|
||||
listen 192.168.255.254:1443;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_timeout 3m;
|
||||
proxy_bind $remote_addr transparent;
|
||||
proxy_pass $dest;
|
||||
ssl_preread on;
|
||||
}
|
||||
|
||||
## this is a basic endpoint for proxy-tunnel connections
|
||||
server {
|
||||
listen 192.168.255.254:444 ssl;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256";
|
||||
## give facl to the nginx user, see later in article
|
||||
ssl_certificate /etc/letsencrypt/live/$name/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/$name/privkey.pem;
|
||||
ssl_dhparam /etc/nginx/dhparam-nginx.pem;
|
||||
ssl_session_cache shared:MySSL:10m;
|
||||
ssl_session_tickets off;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_timeout 3m;
|
||||
proxy_bind $remote_addr transparent;
|
||||
proxy_pass 192.168.255.254:22 ;
|
||||
}
|
||||
|
||||
|
||||
## this is a fancy destination, using some tricks, to fool
|
||||
## some proxies.
|
||||
server {
|
||||
listen 192.168.255.254:445 ssl;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256";
|
||||
## this points to a directory, containg a self signed certificate
|
||||
## created with CN and SAN to an not officially existing
|
||||
## domain, e.g. cryptic.foo.notexists
|
||||
ssl_certificate /etc/nginx/$name/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/$name/privkey.pem;
|
||||
ssl_dhparam /etc/nginx/dhparam-nginx.pem;
|
||||
ssl_session_cache shared:MySSL:10m;
|
||||
ssl_session_tickets off;
|
||||
## additional trick: requiring client certificate authentication
|
||||
ssl_client_certificate /etc/nginx/public.cert ;
|
||||
ssl_verify_client on ;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_timeout 3m;
|
||||
proxy_bind $remote_addr transparent;
|
||||
proxy_pass 192.168.255.254:22;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This nginx stream server shows a combination of different possibilities, how to establish an endpoint for proxytunnel or similar programs, to hide your ssh connection. Remember: Hiding ssh tunneled in tls does not neccessarly raise the security of your connection. In theory a combination of two methods may also lower the security. In addition there are more drawbacks: Such a connection needs more cpu on both ends for doing double crypto, and the throughput through he connection drops, as the ratio payload/header increases. the single ssh-packets are smaller, than usual.
|
||||
So it is not recommended for doing scp or sftp through such connections.
|
||||
|
||||
### Nginx Configration Explained ###
|
||||
|
||||
However, there are situations, where this is the only way, to reach your server.
|
||||
So we have now this nginx instance helping us out.
|
||||
The server stanza listening on _192.168.255.254:1443_ is the main process, accepting incoming connections forwarded from sslh. It prereads tls SNI names, to get destination information. When in the nginx-configuration a variable is used, which is set by an map action, the mapping takes place just in that moment, when the variable needs to be expanded.
|
||||
So we have either default values, in case there is no SNI, or values for individual SNI.
|
||||
|
||||
The connection stream is than forwarded ip-transparent to a given destination, which is itself another nginx instance, defined in the configuration file.
|
||||
|
||||
#### The Standard TLS-Termination ####
|
||||
|
||||
The server stanza listening on _192.168.255.254:444_ is a very basic TLS terminating endpoint. In most cases, a proxy is happy with a destination like this. Here nginx just terminates the TLS connection and shovels the incoming packets over to sshd in ip-transparent way. The proxytunnel configuration for this target will look like:
|
||||
```
|
||||
host t2.yourdomain.top
|
||||
ProxyCommand /path/to/proxytunnel -e -p PROXY-IP:3128 -d t2.yourdomain.top:443
|
||||
ServerAliveInterval 30
|
||||
```
|
||||
If you have no proxytunnel on your system, you can also use openssl. In this case the configuration looks like:
|
||||
```
|
||||
host t2-alias
|
||||
ProxyCommand openssl s_client -proxy PROXY-IP:3128 -connect t2.yourdomain.top:443
|
||||
ServerAliveInterval 30
|
||||
```
|
||||
`ssh ts-alias` will use this configuration.
|
||||
|
||||
One point from the above configuration needs further explanation, as we just refer to our common letsencrypt store, for certificates.
|
||||
|
||||
##### Excurse To File ACLs #####
|
||||
|
||||
We are using here our common letsencrypt certificate store, as copying certificates after renewal is a pain in the as and prone to errors.
|
||||
A small script makes sure, that all applications can read the certificates:
|
||||
```
|
||||
for i in Debian-exim dovecot mail www-data nginx ; do
|
||||
setfacl -Rdm u:$i:rX ./archive/
|
||||
setfacl -Rdm u:$i:rX ./live/
|
||||
setfacl -Rm u:$i:rX ./archive/
|
||||
setfacl -Rm u:$i:rX ./live/
|
||||
for file in $( find ./live ./archive -type l,f ) ; do
|
||||
echo -e "$i $file set"
|
||||
setfacl -m u:$i:r $file
|
||||
done
|
||||
done
|
||||
```
|
||||
This script needs only to be run, when a new application user needs access.
|
||||
The first two lines are making sure (watch the **d**), that the given options
|
||||
on those directories will be the default for newly created files below.
|
||||
The uppercase **X** means, that **x**-access will only be granted to directories or files, having already **x** set for others.
|
||||
|
||||
#### The Tricky TLS Termination ####
|
||||
|
||||
The server stanza listening on _192.168.255.254:445_ is a more tricky TLS terminating endpoint. Some proxies are trying to inspect the destination, before letting you go. Some of them you can fool, others not.
|
||||
One trick can be to use an phantasy domain name with a self signed certificate, no dns server will ever resolve. As you are using this name as SNI name in your proxy-tunnel connection, it will work. This can be also a way, to hide your sshd, if someone, who knows you are using sslh tries to find your sshd. As long, as this person does not get access to proxies you are using, this may help in some situations.
|
||||
Another trick is, requiring a client certificate for authentication. This is in each case the much better approach, as you can combine this also with official connected domain names. Without the client certificate no sshd!
|
||||
The client certificate does not increase the above mentioned ratio between payload and headers, as this is only used while establishing the TLS connection.
|
||||
It prevents however a proxy, doing a parallel sneak to your destination, to figure out what is behind. I have seen proxies letting you connect with this method, others are denying access.
|
||||
|
||||
To access this endpoint, the proxytunnel configuration inside `~/.ssh/config`
|
||||
will look like:
|
||||
```
|
||||
host cryptic.foo.notexists
|
||||
ProxyCommand /path/to/proxytunnel -e -c ~/public.cert -k ~/private.pem -C ~/myOwnCA.cert -p PROXY-IP:3128 -o cryptic.foo.notexists -d t1.yourdomain.top:443
|
||||
ServerAliveInterval 30
|
||||
```
|
||||
-C gives the certfile to your certificate used for selfsigning the server certificate. You can also set -z for not verifying the certificate, but that is not recommended! `t1.yourdomain.top` represents a valid domain name, where the listening sslh can be reached. This name is not taken for SNI, because **-o** sets our hidden SNI name. Whenever you enter `ssh cryptic.foo.notexists` you will get connected to your server!
|
||||
|
||||
The best reommendation however is: Avoid self signed certs, if you are not really sure, what you are doing.
|
||||
|
||||
+10
-9
@@ -12,19 +12,20 @@ GitHub.
|
||||
Getting more info
|
||||
=================
|
||||
|
||||
In general, if something doesn't work, you'll want to run
|
||||
`sslh` with lots of logging, and the logging directly in
|
||||
the terminal (Otherwise, logs are sent to `syslog`, and
|
||||
usually end up in `/var/log/auth.log`). You will achieve
|
||||
this by running `sslh` in foreground with verbose:
|
||||
There are several `verbose` options that each enable a set
|
||||
of messages, each related to some event type. See
|
||||
`example.cfg` for a list of them.
|
||||
|
||||
If something doesn't work, you'll want to run `sslh` with
|
||||
lots of logging, and the logging directly in the terminal
|
||||
(Otherwise, logs are sent to `syslog`, and usually end up in
|
||||
`/var/log/auth.log`). There is a general `--verbose` option
|
||||
that will allow you to enable all messages:
|
||||
|
||||
```
|
||||
sslh -v 1 -f -F myconfig.cfg
|
||||
sslh -v 3 -f -F myconfig.cfg
|
||||
```
|
||||
|
||||
Higher values of `verbose` produce more information. 1 is
|
||||
usually sufficient. 2 will also print incoming packets used
|
||||
for probing.
|
||||
|
||||
forward to [PROBE] failed:connect: Connection refused
|
||||
=====================================================
|
||||
|
||||
+32
-6
@@ -22,17 +22,20 @@ Dependencies
|
||||
|
||||
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
|
||||
For Debian, this is contained in packages `libwrap0-dev`.
|
||||
You can compile with or without it using USELIBWRAP in the Makefile.
|
||||
Presence of libwrap is checked by the configure script.
|
||||
|
||||
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`.
|
||||
You can compile with or without it using USESYSTEMD in the Makefile.
|
||||
|
||||
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`.
|
||||
You can compile with or without it using USELIBCAP in the Makefile
|
||||
Presence of libcap is checked by the configure script.
|
||||
|
||||
* [libconfig++-dev](https://packages.debian.org/bookworm/libconfig++-dev), in package `lìbconfig++-dev`
|
||||
|
||||
* libbsd, to enable to change the process name (as shown in `ps`,
|
||||
so each forked process shows what protocol and what connection it is serving),
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at compile-time.
|
||||
Presence of libbsd is checked by the configure script.
|
||||
|
||||
* libpcre2, in package `libpcre2-dev`.
|
||||
You can compile with or without it using ENABLE_REGEX in the Makefile.
|
||||
@@ -40,6 +43,16 @@ Dependencies
|
||||
* libev-dev, in package `libev-dev`.
|
||||
If you build a binary specifically and do not build `sslh-ev`, you don't need this.
|
||||
|
||||
* [libproxyprotocol](https://github.com/kosmas-valianos/libproxyprotocol.git)
|
||||
to support HAProxy's [ProxyProtocol](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt).
|
||||
As this is not part of the distribution packages, set
|
||||
C_INCLUDE_PATH, LD_LIBRARY_PATH, and LIBRARY_PATH to the appropriate
|
||||
values:
|
||||
```
|
||||
export C_INCLUDE_PATH=/home/user/src/libproxyprotocol/src
|
||||
export LD_LIBRARY_PATH=/home/user/src/libproxyprotocol/libs
|
||||
export LIBRARY_PATH=/home/user/src/libproxyprotocol/libs
|
||||
```
|
||||
|
||||
For OpenSUSE, these are contained in packages libconfig9 and
|
||||
libconfig-dev in repository
|
||||
@@ -59,13 +72,18 @@ The test scripts are written in Perl, and will require
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
After this, the Makefile should work:
|
||||
|
||||
make install
|
||||
First you have to run `./configure` in the _**./sslh**_ directory. After this,
|
||||
the Makefile is created, and you can do your configuration changes in the Makefile.
|
||||
After each run of ./configure, those changes are gone and the Makefile is recreated.
|
||||
|
||||
There are a couple of configuration options at the beginning of the Makefile:
|
||||
|
||||
* `# override undefine HAVE_LANDLOCK` if you uncomment this line, sslh will be compiled
|
||||
without landlock. This works with gcc versions < 12. Otherwise, if your system has
|
||||
linux/landlock.h in the include path, the configure script creates a _**config.h**_ file,
|
||||
which defines HAVE_LANDLOCK. It is not enough, to set this to 0, you must delete it,
|
||||
when you don't wish to have landlock in your binary.
|
||||
|
||||
* `USELIBWRAP` compiles support for host access control (see `hosts_access(3)`),
|
||||
you will need `libwrap` headers and library to compile (`libwrap0-dev` in Debian).
|
||||
|
||||
@@ -77,6 +95,14 @@ There are a couple of configuration options at the beginning of the Makefile:
|
||||
|
||||
* `USELIBBSD` compiles support for updating the process name (as shown by `ps`).
|
||||
|
||||
* `USELIBCAP` compiles support for libcap, which allows to inherit capabilities to
|
||||
daughter-processes, which run as restricted users. You need this, when you wish to
|
||||
make sure, that the --user= parameter can be used, without setting capabilities etc.
|
||||
to your binaries, to make this work.
|
||||
|
||||
Now you can do either a plain `make` to create the binaries, or you can do an
|
||||
`make install` to create the binaries and install them.
|
||||
|
||||
|
||||
Generating the configuration parser
|
||||
-----------------------------------
|
||||
|
||||
+7
-4
@@ -76,10 +76,10 @@ Configuration goes like this on the server side, using `stunnel3`:
|
||||
Capabilities support
|
||||
--------------------
|
||||
|
||||
On Linux (only?), you can compile sslh with `USELIBCAP=1` to
|
||||
make use of POSIX capabilities; this will save the required
|
||||
capabilities needed for transparent proxying for unprivileged
|
||||
processes.
|
||||
On Linux (only?), you can compile sslh with `USELIBCAP=1` set
|
||||
in the Makefile to make use of POSIX capabilities; this will
|
||||
save the required capabilities needed for transparent proxying
|
||||
for unprivileged processes.
|
||||
|
||||
Alternatively, you may use filesystem capabilities instead
|
||||
of starting sslh as root and asking it to drop privileges.
|
||||
@@ -102,6 +102,9 @@ Transparent proxy support
|
||||
Transparent proxying is described in its own
|
||||
[document](tproxy.md).
|
||||
|
||||
It might be easier to configure `sslh` to use Proxyprotocol
|
||||
if the backend server supports it.
|
||||
|
||||
Systemd Socket Activation
|
||||
-------------------------
|
||||
If compiled with `USESYSTEMD` then it is possible to activate
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
File diff suppressed because it is too large.
Load diff
|
After Width: | Height: | Size: 59 KiB |
@@ -3,7 +3,7 @@
|
||||

|
||||
|
||||
## Introduction ##
|
||||
The first example is the configuration, which was described in the previousd document. I omitted the loopback interface "lo" in those diagrams, trying not no overload the picture.
|
||||
The first example is the configuration, which was described in the previous document. I omitted the loopback interface "lo" in those diagrams, trying not no overload the picture.
|
||||
The connections have two different endings, showing the direction of the opening connection (SYN flag) and the answer connection (SYN-ACK flags). This is important, as the traffic in the transparent proxy setup flows somewhat unexpected.
|
||||
|
||||
## Example 1 ##
|
||||
@@ -41,6 +41,8 @@ ip route add default via SERVER1_ETH1_IP dev eth0 table sslh_routeback
|
||||
```
|
||||
This is setting up a default route for all traffic, originating from the ip address sshd (or any other service) is using, back to the host, hosting sslh. On that host, those packets will be deflected again with the same rule from scenario 2.
|
||||
|
||||
Be aware, that scenario 3 can look very different and the picture above shows only one of those setups. Each configuration, where packets from the target system can find their way back, without beeing forcibly routed through the sslh hosting system, belongs into this category. This are e.g. virtual machines or containers, having interfaces in the same network, like the sslh hosting system. Even, when they look in some drawings embedded in their host, as soon, as they have network interfaces, allowing a direct connection to the client, it is scenario 3!
|
||||
|
||||
## Modifications ##
|
||||
Now you can think about many modifications, but the tools will be the same, for all other thinkable scenarios. You must always make sure, that packets from foreign hosts, will find their way back to the sslh host. So if the chain consists of three or four servers, all need the deflection rules.
|
||||
|
||||
|
||||
@@ -68,9 +68,11 @@ To do so go to _**/etc/iproute2/rt_tables**_ and add a line
|
||||
```
|
||||
111 sslh
|
||||
```
|
||||
With newer versions of iproute2 the /etc/iproute2 directory with the embedded templates got no longer installed. The cause maybe, that the example names, which were not used in any configuration, generated confusion. However, once you need those files, generate them and they will be honoured. You still can use just numbers for your routing table. But doing this, and having more than one routing table, you need a list, which numer belongs to which configuration.
|
||||
And seeing in the output from `ip route list table all ` the tables names instead just numbers is worth creating the file.
|
||||
|
||||
### Dummy Interface ###
|
||||
Now we configure our dedicatet interface.
|
||||
Now we configure our dedicated interface.
|
||||
In the file _**/etc/network/interfaces**_, we place this entry:
|
||||
```
|
||||
auto dummy0
|
||||
@@ -91,7 +93,7 @@ If you are updating a older current configuration, make sure, that you have no l
|
||||
sysctl net.ipv4.conf.default.route_localnet
|
||||
sysctl net.ipv4.conf.all.route_localnet
|
||||
```
|
||||
should both report "0"!
|
||||
should both report "0"!
|
||||
|
||||
|
||||
### Explanation Of The Routing Rules ###
|
||||
|
||||
+49
-26
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Apr 30 09:55:03 2022.
|
||||
* on Sun Apr 6 11:44:59 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -36,6 +36,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include "echosrv-conf.h"
|
||||
#include "argtable3.h"
|
||||
#ifdef LIBPCRE
|
||||
@@ -216,6 +218,19 @@ char* config_error_text(config_t* c) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static jmp_buf c2s_asprintf_fail;
|
||||
|
||||
static int c2s_asprintf(char **restrict strp, const char *restrict fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
int res = vasprintf(strp, fmt, ap);
|
||||
va_end(ap);
|
||||
if (res == -1) longjmp(c2s_asprintf_fail, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* This is the same as config_setting_lookup_string() except
|
||||
it allocates a new string which belongs to the caller */
|
||||
static int myconfig_setting_lookup_stringcpy(
|
||||
@@ -226,7 +241,7 @@ static int myconfig_setting_lookup_stringcpy(
|
||||
const char* str;
|
||||
*value = NULL;
|
||||
if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
|
||||
asprintf(value, "%s", str);
|
||||
c2s_asprintf(value, "%s", str);
|
||||
return CONFIG_TRUE;
|
||||
} else {
|
||||
return CONFIG_FALSE;
|
||||
@@ -310,7 +325,7 @@ static int settingcpy(config_type type, void* target, const config_setting_t* se
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
c2s_asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -353,7 +368,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -365,7 +380,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy the value of a string argument to arbitrary memory
|
||||
/* Copy the value of a string argument to arbitary memory
|
||||
* location that must be large enough, converting on the way
|
||||
* (i.e. CFG_INT gets atoi() and so on) */
|
||||
/* 0: success
|
||||
@@ -765,7 +780,7 @@ static int read_block_setval(void* target,
|
||||
/* setting is present in cfg, look it up */
|
||||
if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) {
|
||||
TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type]));
|
||||
asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
desc->name, type2str[desc->type]);
|
||||
return 0;
|
||||
}
|
||||
@@ -822,7 +837,7 @@ static int read_block(config_setting_t* cfg, void* target, struct config_desc* d
|
||||
set = read_block_setval(target, cfg, desc, errmsg);
|
||||
|
||||
if (!set && desc->mandatory) {
|
||||
asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -862,7 +877,7 @@ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, con
|
||||
if (pmatch[pmatch_cnt].rm_so == -1) {
|
||||
/* This should not happen as regexec() did
|
||||
* match before, unless there is a
|
||||
* discrepancy between the regex and the
|
||||
* discrepency between the regex and the
|
||||
* number of backreferences */
|
||||
return 0;
|
||||
}
|
||||
@@ -980,13 +995,13 @@ static int regcompmatch_posix( regmatch_t* pmatch,
|
||||
int errlen = regerror(res, &preg, NULL, 0);
|
||||
regerr = malloc(errlen);
|
||||
regerror(res, &preg, regerr, errlen);
|
||||
asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
free(regerr);
|
||||
return 0;
|
||||
}
|
||||
res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0);
|
||||
if (res) {
|
||||
asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
c2s_asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
arg_cl->hdr.longopts,
|
||||
arg->regex);
|
||||
return 0;
|
||||
@@ -1012,7 +1027,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);
|
||||
if (!pcre) {
|
||||
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
arg->regex, error, err_str, error_offset);
|
||||
return 0;
|
||||
}
|
||||
@@ -1022,7 +1037,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
0, 0, matches, NULL);
|
||||
if (res < 0) {
|
||||
pcre2_get_error_message(res, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
arg_cl->sval[arg_index], arg->regex, res, err_str);
|
||||
return 0;
|
||||
}
|
||||
@@ -1111,13 +1126,13 @@ static int c2s_parse_file(const char* filename, config_t* c, char**errmsg)
|
||||
/* Read config file */
|
||||
if (config_read_file(c, filename) == CONFIG_FALSE) {
|
||||
if (config_error_line(c) != 0) {
|
||||
asprintf(errmsg, "%s:%d:%s",
|
||||
c2s_asprintf(errmsg, "%s:%d:%s",
|
||||
filename,
|
||||
config_error_line(c),
|
||||
config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -1128,23 +1143,23 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
||||
{
|
||||
switch(config_setting_type(s)) {
|
||||
case CONFIG_TYPE_INT:
|
||||
asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
c2s_asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_BOOL:
|
||||
asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_INT64:
|
||||
asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_FLOAT:
|
||||
asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
c2s_asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_STRING:
|
||||
asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
break;
|
||||
|
||||
default: /* This means a bug */
|
||||
@@ -1155,7 +1170,7 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
||||
|
||||
/* Typesets all the settings in a configuration as a
|
||||
* newly-allocated string. The string management is caller's
|
||||
* responsibility.
|
||||
* responsability.
|
||||
* Returns the number of scalars in the configuration */
|
||||
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
|
||||
{
|
||||
@@ -1172,9 +1187,9 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
if(config_setting_is_list(parent) ||
|
||||
config_setting_is_array(parent)) {
|
||||
asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
} else {
|
||||
asprintf(&subpath, "%s/%s", path, name);
|
||||
c2s_asprintf(&subpath, "%s/%s", path, name);
|
||||
}
|
||||
|
||||
if (config_setting_is_scalar(child)) {
|
||||
@@ -1182,12 +1197,12 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
/* Add value to the output string */
|
||||
if (*strp) {
|
||||
asprintf(&old, "%s", *strp);
|
||||
c2s_asprintf(&old, "%s", *strp);
|
||||
free(*strp);
|
||||
} else {
|
||||
asprintf(&old, "%s", "");
|
||||
c2s_asprintf(&old, "%s", "");
|
||||
}
|
||||
asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
c2s_asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
free(value);
|
||||
free(old);
|
||||
|
||||
@@ -1224,6 +1239,14 @@ int echocfg_cl_parse(int argc, char* argv[], struct echocfg_item* cfg)
|
||||
|
||||
};
|
||||
|
||||
/* Set up failure handler in case asprintf() runs out of
|
||||
* memory */
|
||||
;
|
||||
if (setjmp(c2s_asprintf_fail)) {
|
||||
fprintf(stderr, "asprintf: probably out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse command line */
|
||||
nerrors = arg_parse(argc, argv, argtable);
|
||||
if (nerrors) {
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Apr 30 09:55:03 2022.
|
||||
* on Sun Apr 6 11:44:59 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
|
||||
+20
-2
@@ -16,6 +16,7 @@ chroot: "/var/empty";
|
||||
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
|
||||
# Defaults are indicated here, and should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
# Each option relates to a different set of messages.
|
||||
verbose-config: 0; # print configuration at startup
|
||||
verbose-config-error: 3; # print configuration errors
|
||||
verbose-connections: 3; # trace established incoming address to forward address
|
||||
@@ -28,6 +29,9 @@ verbose-probe-error: 3; # failures and problems during probing
|
||||
verbose-system-error: 3; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 3; # internal errors, the kind that should never happen
|
||||
|
||||
# This one is special and overrides all previous options if
|
||||
# set, as a quick way to get "as much as possible"
|
||||
#verbose: 3;
|
||||
|
||||
# Specify a path to the logfile.
|
||||
#logfile: "/var/log/sslh.log"
|
||||
@@ -49,7 +53,8 @@ listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; },
|
||||
{ host: "thelonious"; port: "8080"; keepalive: true; },
|
||||
{ host: "thelonious"; is_udp: true; port: "443" }
|
||||
{ host: "thelonious"; is_udp: true; port: "443"; },
|
||||
{ host: "/tmp/unix_socket"; is_unix: true; port: ""; }
|
||||
);
|
||||
|
||||
# List of protocols
|
||||
@@ -72,6 +77,13 @@ listen:
|
||||
# transparent: Set to true to proxy this protocol
|
||||
# transparently (server sees the remote client IP
|
||||
# address). Same as the global option, but per-protocol
|
||||
# is_unix: [true|false] connect to a UNIX socket. The host
|
||||
# field becomes the pathname to the socket, and the port
|
||||
# field is unused (but necessary).
|
||||
# proxyprotocol: <1|2>; When connecting to the backend
|
||||
# server, a proxyprotocol header of the specified
|
||||
# version will be added, containing the client's
|
||||
# connection information.
|
||||
#
|
||||
# Probe-specific options:
|
||||
# (sslh will try each probe in order they are declared, and
|
||||
@@ -96,12 +108,18 @@ listen:
|
||||
# that target.
|
||||
#
|
||||
# You can specify several of 'regex' and 'tls'.
|
||||
#
|
||||
# If you want to filter on incoming IP addresses, you can
|
||||
# use libwrap which will use /etc/hosts.allow and
|
||||
# /etc/hosts.deny.
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22";
|
||||
keepalive: true; fork: true; tfo_ok: true },
|
||||
{ name: "http"; host: "localhost"; port: "80"; },
|
||||
|
||||
# UNIX socket to a local NGINX. The socket name is in 'host'; 'port' is necessary but not used.
|
||||
{ name: "http"; is_unix: true; host: "/tmp/nginx.sock"; port: ""; },
|
||||
|
||||
# match BOTH ALPN/SNI
|
||||
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0; tfo_ok: true },
|
||||
|
||||
@@ -90,6 +90,9 @@ static int add_path_ro(int ruleset_fd, ll_obj_type otype, const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// close helper handle
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
# proxyprotocol: Support for HAProxy's proxyprotocol
|
||||
#
|
||||
# Copyright (C) 2025 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
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_PROXYPROTOCOL
|
||||
|
||||
#include <proxy_protocol.h>
|
||||
#include "common.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
/* Converts socket family to libproxyprotocol family */
|
||||
static int family_to_pp(int af_family)
|
||||
{
|
||||
switch (af_family) {
|
||||
case AF_INET:
|
||||
return ADDR_FAMILY_INET;
|
||||
|
||||
case AF_INET6:
|
||||
return ADDR_FAMILY_INET6;
|
||||
|
||||
case AF_UNIX:
|
||||
return ADDR_FAMILY_UNIX;
|
||||
|
||||
default:
|
||||
print_message(msg_int_error, "Unknown internal socket family %d\n", af_family);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
typedef char libpp_addr[108]; /* This is hardcoded in libproxyprotocol/proxy_protocol.h */
|
||||
|
||||
/* Fills *addr, *host and *serv with the connection information corresponding
|
||||
* to fd. *host is the IP address as string and *serv is the service (port)
|
||||
* */
|
||||
static int get_info(int fd, struct addrinfo* addr, libpp_addr* host, uint16_t* serv)
|
||||
{
|
||||
char serv_str[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "getpeername", -1);
|
||||
|
||||
res = getnameinfo(addr->ai_addr, addr->ai_addrlen,
|
||||
(char*)host, sizeof(*host),
|
||||
serv_str, sizeof(serv_str),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV );
|
||||
CHECK_RES_RETURN(res, "getnameinfo", -1);
|
||||
|
||||
*serv = atoi(serv_str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int pp_write_header(int pp_version, struct connection* cnx)
|
||||
{
|
||||
pp_info_t pp_info_in_v1 = {
|
||||
.transport_protocol = TRANSPORT_PROTOCOL_STREAM,
|
||||
};
|
||||
uint16_t pp1_hdr_len;
|
||||
int32_t error;
|
||||
|
||||
struct sockaddr_storage ss;
|
||||
struct addrinfo addr;
|
||||
int res;
|
||||
|
||||
addr.ai_addr = (struct sockaddr*)&ss;
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
|
||||
res = get_info(cnx->q[0].fd,
|
||||
&addr,
|
||||
&pp_info_in_v1.src_addr,
|
||||
&pp_info_in_v1.src_port);
|
||||
if (res == -1) return -1;
|
||||
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
|
||||
|
||||
res = get_info(cnx->q[1].fd,
|
||||
&addr,
|
||||
&pp_info_in_v1.dst_addr,
|
||||
&pp_info_in_v1.dst_port
|
||||
);
|
||||
if (res == -1) return -1;
|
||||
|
||||
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_hrd:%d:%s\n", error, pp_strerror(error));
|
||||
return -1;
|
||||
}
|
||||
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
|
||||
|
||||
pp_info_clear(&pp_info_in_v1);
|
||||
free(pp1_hdr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* HAVE_PROXYPROTOCOL */
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef PROXYPROTOCOL_H
|
||||
#define PROXYPROTOCOL_H
|
||||
|
||||
|
||||
#if HAVE_PROXYPROTOCOL
|
||||
int pp_write_header(int pp_version, struct connection* cnx);
|
||||
|
||||
|
||||
#else /* HAVE_PROXYPROTOCOL */
|
||||
|
||||
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
|
||||
|
||||
#endif /* HAVE_PROXYPROTOCOL */
|
||||
|
||||
#endif /* PROXYPROTOCOL_H */
|
||||
@@ -0,0 +1,186 @@
|
||||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: sslh
|
||||
# Required-Start: $remote_fs $syslog $network
|
||||
# Required-Stop: $remote_fs $syslog $network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: ssl/ssh multiplexer
|
||||
# Description: sslh lets one accept both HTTPS and SSH connections on the
|
||||
# same port. It makes it possible to connect to an SSH server
|
||||
# on port 443 (e.g. from inside a corporate firewall) while
|
||||
# still serving HTTPS on that port.
|
||||
### END INIT INFO
|
||||
|
||||
# Original Author: Guillaume Delacour <gui@iroqwa.org>
|
||||
# modified and optimized for current sslh-fork
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="ssl/ssh multiplexer"
|
||||
NAME=sslh
|
||||
DAEMON=/usr/sbin/$NAME
|
||||
DAEMON_OPTS=""
|
||||
PIDFILE=/var/run/sslh/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
RUN=yes
|
||||
|
||||
|
||||
# If you want to use a configuration file, put -F/path/to/sslh.cfg
|
||||
# into /etc/default/sslh DAEMON_OPTS
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Exit if the package is not installed
|
||||
if [ -x "$DAEMON" ]
|
||||
then
|
||||
echo "Can not start \"$DAEMON\", path not available"
|
||||
log_failure_msg "Can not start \"$DAEMON\", path not available"
|
||||
fi
|
||||
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
|
||||
# Use this if you want the user to explicitly set 'RUN' in
|
||||
# /etc/default/
|
||||
if [ "$RUN" != "yes" ]
|
||||
then
|
||||
echo "$NAME disabled, please adjust the configuration to your needs "
|
||||
log_failure_msg "and then set RUN to 'yes' in /etc/default/$NAME to enable it."
|
||||
return 2
|
||||
fi
|
||||
|
||||
# sslh write the pid as sslh user
|
||||
if [ ! -d /var/run/sslh/ ]
|
||||
then
|
||||
mkdir -p /var/run/sslh
|
||||
chown sslh:sslh /var/run/sslh
|
||||
fi
|
||||
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
|
||||
$DAEMON_OPTS \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/45/KILL/5 --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# As long, as the started sslh is sslh-fork, don't kill the still existing
|
||||
# connections. You may need the following construct for sslh-ev and sslh-select,
|
||||
# as sslh has currently no function reloading its configuration.
|
||||
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
#start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
#[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
# don't activate this, as this kills only the leading process
|
||||
# of sslh-fork, and the spawned worker stays connected listening.
|
||||
# After that, the Owner of the PID from PIDFILE is gone, the
|
||||
# listening connection is still blocked
|
||||
# sslh can't reload its configuration as of Aug 2024
|
||||
#do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
# start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||
# return 0
|
||||
#}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
# check if sslh is launched via inetd
|
||||
if [ -f /etc/inetd.conf ] && [ $(egrep -q "^https.*/usr/sbin/sslh" /etc/inetd.conf|wc -l) -ne 0 ]
|
||||
then
|
||||
echo "sslh is started from inetd."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
+164
-64
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Tue Jan 2 21:25:47 2024.
|
||||
* on Sun Apr 6 11:44:58 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -36,6 +36,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include "sslh-conf.h"
|
||||
#include "argtable3.h"
|
||||
#ifdef LIBPCRE
|
||||
@@ -216,6 +218,19 @@ char* config_error_text(config_t* c) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static jmp_buf c2s_asprintf_fail;
|
||||
|
||||
static int c2s_asprintf(char **restrict strp, const char *restrict fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
int res = vasprintf(strp, fmt, ap);
|
||||
va_end(ap);
|
||||
if (res == -1) longjmp(c2s_asprintf_fail, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* This is the same as config_setting_lookup_string() except
|
||||
it allocates a new string which belongs to the caller */
|
||||
static int myconfig_setting_lookup_stringcpy(
|
||||
@@ -226,7 +241,7 @@ static int myconfig_setting_lookup_stringcpy(
|
||||
const char* str;
|
||||
*value = NULL;
|
||||
if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
|
||||
asprintf(value, "%s", str);
|
||||
c2s_asprintf(value, "%s", str);
|
||||
return CONFIG_TRUE;
|
||||
} else {
|
||||
return CONFIG_FALSE;
|
||||
@@ -310,7 +325,7 @@ static int settingcpy(config_type type, void* target, const config_setting_t* se
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
c2s_asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -353,7 +368,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -443,6 +458,7 @@ struct compound_cl_arg {
|
||||
|
||||
|
||||
struct arg_file* sslhcfg_conffile;
|
||||
struct arg_int* sslhcfg_verbose;
|
||||
struct arg_int* sslhcfg_verbose_config;
|
||||
struct arg_int* sslhcfg_verbose_config_error;
|
||||
struct arg_int* sslhcfg_verbose_connections;
|
||||
@@ -484,7 +500,7 @@ struct arg_file* sslhcfg_conffile;
|
||||
struct arg_str* sslhcfg_anyprot;
|
||||
struct arg_end* sslhcfg_end;
|
||||
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg_protocols[] = {
|
||||
|
||||
|
||||
@@ -552,6 +568,22 @@ static struct config_desc table_sslhcfg_protocols[] = {
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "is_unix",
|
||||
/* type */ CFG_BOOL,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_protocols_item, is_unix),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "is_udp",
|
||||
/* type */ CFG_BOOL,
|
||||
@@ -743,9 +775,25 @@ static struct config_desc table_sslhcfg_protocols[] = {
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "proxyprotocol",
|
||||
/* type */ CFG_INT,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_protocols_item, proxyprotocol),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_protocols_item, proxyprotocol_is_present),
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg_listen[] = {
|
||||
|
||||
|
||||
@@ -797,6 +845,22 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "is_unix",
|
||||
/* type */ CFG_BOOL,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_listen_item, is_unix),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "keepalive",
|
||||
/* type */ CFG_BOOL,
|
||||
@@ -814,10 +878,26 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg[] = {
|
||||
|
||||
|
||||
{
|
||||
/* name */ "verbose",
|
||||
/* type */ CFG_INT,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_verbose,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, verbose),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "verbose_config",
|
||||
/* type */ CFG_INT,
|
||||
@@ -1255,7 +1335,7 @@ static struct compound_cl_target sslhcfg_anyprot_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "anyprot" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1263,7 +1343,7 @@ static struct compound_cl_target sslhcfg_msrdp_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "msrdp" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1271,7 +1351,7 @@ static struct compound_cl_target sslhcfg_syslog_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "syslog" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1279,7 +1359,7 @@ static struct compound_cl_target sslhcfg_socks5_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "socks5" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1287,7 +1367,7 @@ static struct compound_cl_target sslhcfg_adb_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "adb" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1295,7 +1375,7 @@ static struct compound_cl_target sslhcfg_http_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "http" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1303,7 +1383,7 @@ static struct compound_cl_target sslhcfg_xmpp_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "xmpp" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1311,8 +1391,8 @@ static struct compound_cl_target sslhcfg_wireguard_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "wireguard" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1320,8 +1400,8 @@ static struct compound_cl_target sslhcfg_tinc_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tinc" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1329,8 +1409,8 @@ static struct compound_cl_target sslhcfg_openvpn_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "openvpn" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1338,8 +1418,8 @@ static struct compound_cl_target sslhcfg_ssl_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1347,8 +1427,8 @@ static struct compound_cl_target sslhcfg_tls_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1356,9 +1436,9 @@ static struct compound_cl_target sslhcfg_ssh_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "ssh" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[6], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1372,7 +1452,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: listen */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_listen,
|
||||
.base_entry = & table_sslhcfg [25],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_listen_targets,
|
||||
|
||||
|
||||
@@ -1384,7 +1464,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssh */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssh,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_ssh_targets,
|
||||
|
||||
|
||||
@@ -1396,7 +1476,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tls */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tls,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_tls_targets,
|
||||
|
||||
|
||||
@@ -1408,7 +1488,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssl */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssl,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_ssl_targets,
|
||||
|
||||
|
||||
@@ -1420,7 +1500,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: openvpn */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_openvpn,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_openvpn_targets,
|
||||
|
||||
|
||||
@@ -1432,7 +1512,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tinc */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tinc,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_tinc_targets,
|
||||
|
||||
|
||||
@@ -1444,7 +1524,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: wireguard */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_wireguard,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_wireguard_targets,
|
||||
|
||||
|
||||
@@ -1456,7 +1536,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: xmpp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_xmpp,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_xmpp_targets,
|
||||
|
||||
|
||||
@@ -1468,7 +1548,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: http */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_http,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_http_targets,
|
||||
|
||||
|
||||
@@ -1480,7 +1560,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: adb */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_adb,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_adb_targets,
|
||||
|
||||
|
||||
@@ -1492,7 +1572,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: socks5 */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_socks5,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_socks5_targets,
|
||||
|
||||
|
||||
@@ -1504,7 +1584,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: syslog */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_syslog,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_syslog_targets,
|
||||
|
||||
|
||||
@@ -1516,7 +1596,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: msrdp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_msrdp,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_msrdp_targets,
|
||||
|
||||
|
||||
@@ -1528,7 +1608,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: anyprot */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_anyprot,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.targets = sslhcfg_anyprot_targets,
|
||||
|
||||
|
||||
@@ -1743,7 +1823,7 @@ static int read_block_setval(void* target,
|
||||
/* setting is present in cfg, look it up */
|
||||
if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) {
|
||||
TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type]));
|
||||
asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
desc->name, type2str[desc->type]);
|
||||
return 0;
|
||||
}
|
||||
@@ -1800,7 +1880,7 @@ static int read_block(config_setting_t* cfg, void* target, struct config_desc* d
|
||||
set = read_block_setval(target, cfg, desc, errmsg);
|
||||
|
||||
if (!set && desc->mandatory) {
|
||||
asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1958,13 +2038,13 @@ static int regcompmatch_posix( regmatch_t* pmatch,
|
||||
int errlen = regerror(res, &preg, NULL, 0);
|
||||
regerr = malloc(errlen);
|
||||
regerror(res, &preg, regerr, errlen);
|
||||
asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
free(regerr);
|
||||
return 0;
|
||||
}
|
||||
res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0);
|
||||
if (res) {
|
||||
asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
c2s_asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
arg_cl->hdr.longopts,
|
||||
arg->regex);
|
||||
return 0;
|
||||
@@ -1990,7 +2070,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);
|
||||
if (!pcre) {
|
||||
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
arg->regex, error, err_str, error_offset);
|
||||
return 0;
|
||||
}
|
||||
@@ -2000,7 +2080,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
0, 0, matches, NULL);
|
||||
if (res < 0) {
|
||||
pcre2_get_error_message(res, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
arg_cl->sval[arg_index], arg->regex, res, err_str);
|
||||
return 0;
|
||||
}
|
||||
@@ -2089,13 +2169,13 @@ static int c2s_parse_file(const char* filename, config_t* c, char**errmsg)
|
||||
/* Read config file */
|
||||
if (config_read_file(c, filename) == CONFIG_FALSE) {
|
||||
if (config_error_line(c) != 0) {
|
||||
asprintf(errmsg, "%s:%d:%s",
|
||||
c2s_asprintf(errmsg, "%s:%d:%s",
|
||||
filename,
|
||||
config_error_line(c),
|
||||
config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -2106,23 +2186,23 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
||||
{
|
||||
switch(config_setting_type(s)) {
|
||||
case CONFIG_TYPE_INT:
|
||||
asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
c2s_asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_BOOL:
|
||||
asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_INT64:
|
||||
asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_FLOAT:
|
||||
asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
c2s_asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_STRING:
|
||||
asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
break;
|
||||
|
||||
default: /* This means a bug */
|
||||
@@ -2150,9 +2230,9 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
if(config_setting_is_list(parent) ||
|
||||
config_setting_is_array(parent)) {
|
||||
asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
} else {
|
||||
asprintf(&subpath, "%s/%s", path, name);
|
||||
c2s_asprintf(&subpath, "%s/%s", path, name);
|
||||
}
|
||||
|
||||
if (config_setting_is_scalar(child)) {
|
||||
@@ -2160,12 +2240,12 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
/* Add value to the output string */
|
||||
if (*strp) {
|
||||
asprintf(&old, "%s", *strp);
|
||||
c2s_asprintf(&old, "%s", *strp);
|
||||
free(*strp);
|
||||
} else {
|
||||
asprintf(&old, "%s", "");
|
||||
c2s_asprintf(&old, "%s", "");
|
||||
}
|
||||
asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
c2s_asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
free(value);
|
||||
free(old);
|
||||
|
||||
@@ -2193,6 +2273,7 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
#ifdef LIBCONFIG
|
||||
sslhcfg_conffile = arg_filen("F", "config", "<file>", 0, 1, "Specify configuration file"),
|
||||
#endif
|
||||
sslhcfg_verbose = arg_intn("v", "verbose", "<n>", 0, 1, "Override all verbosness options"),
|
||||
sslhcfg_verbose_config = arg_intn(NULL, "verbose-config", "<n>", 0, 1, "Print configuration at startup"),
|
||||
sslhcfg_verbose_config_error = arg_intn(NULL, "verbose-config-error", "<n>", 0, 1, "Print configuration errors"),
|
||||
sslhcfg_verbose_connections = arg_intn(NULL, "verbose-connections", "<n>", 0, 1, "Trace established incoming address to forward address"),
|
||||
@@ -2236,16 +2317,21 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
|
||||
};
|
||||
|
||||
/* Set up failure handler in case asprintf() runs out of
|
||||
* memory */
|
||||
;
|
||||
if (setjmp(c2s_asprintf_fail)) {
|
||||
fprintf(stderr, "asprintf: probably out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse command line */
|
||||
nerrors = arg_parse(argc, argv, argtable);
|
||||
if (nerrors) {
|
||||
// print bad args
|
||||
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
|
||||
// print usage
|
||||
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
|
||||
arg_print_syntax(stdout, argtable, "\n");
|
||||
// print options
|
||||
arg_print_glossary(stdout, argtable, " %-25s\t%s\n");
|
||||
fprintf(stderr, "Invalid args are ignored, please fix them\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -2308,6 +2394,9 @@ static void sslhcfg_protocols_fprint(
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "is_unix: %d", sslhcfg_protocols->is_unix);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "is_udp: %d", sslhcfg_protocols->is_udp);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
@@ -2354,6 +2443,11 @@ static void sslhcfg_protocols_fprint(
|
||||
if (! sslhcfg_protocols->minlength_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "proxyprotocol: %d", sslhcfg_protocols->proxyprotocol);
|
||||
if (! sslhcfg_protocols->proxyprotocol_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
|
||||
static void sslhcfg_listen_fprint(
|
||||
@@ -2372,6 +2466,9 @@ static void sslhcfg_listen_fprint(
|
||||
fprintf(out, "is_udp: %d", sslhcfg_listen->is_udp);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "is_unix: %d", sslhcfg_listen->is_unix);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "keepalive: %d", sslhcfg_listen->keepalive);
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
@@ -2382,6 +2479,9 @@ void sslhcfg_fprint(
|
||||
int depth)
|
||||
{
|
||||
int i;
|
||||
indent(out, depth);
|
||||
fprintf(out, "verbose: %d", sslhcfg->verbose);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "verbose_config: %d", sslhcfg->verbose_config);
|
||||
fprintf(out, "\n");
|
||||
|
||||
+7
-2
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Tue Jan 2 21:25:47 2024.
|
||||
* on Sun Apr 6 11:44:58 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -44,6 +44,7 @@ struct sslhcfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
int is_udp;
|
||||
int is_unix;
|
||||
int keepalive;
|
||||
};
|
||||
|
||||
@@ -53,6 +54,7 @@ struct sslhcfg_protocols_item {
|
||||
char* port;
|
||||
int service_is_present;
|
||||
char* service;
|
||||
int is_unix;
|
||||
int is_udp;
|
||||
int udp_timeout;
|
||||
int fork;
|
||||
@@ -69,6 +71,8 @@ struct sslhcfg_protocols_item {
|
||||
char** regex_patterns;
|
||||
int minlength_is_present;
|
||||
int minlength;
|
||||
int proxyprotocol_is_present;
|
||||
int proxyprotocol;
|
||||
T_PROBE* probe;
|
||||
struct addrinfo* saddr;
|
||||
void* data;
|
||||
@@ -76,6 +80,7 @@ struct sslhcfg_protocols_item {
|
||||
};
|
||||
|
||||
struct sslhcfg_item {
|
||||
int verbose;
|
||||
int verbose_config;
|
||||
int verbose_config_error;
|
||||
int verbose_connections;
|
||||
|
||||
+7
-11
@@ -26,7 +26,7 @@
|
||||
#include "tcp-probe.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef LIBBSD
|
||||
#if HAVE_LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -74,7 +74,6 @@ void start_shoveler(int in_socket)
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
int res = PROBE_AGAIN;
|
||||
int out_socket;
|
||||
struct connection cnx;
|
||||
struct connection_desc desc;
|
||||
|
||||
@@ -110,13 +109,11 @@ void start_shoveler(int in_socket)
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(&cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(out_socket, "connect");
|
||||
connect_addr(&cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(cnx.q[1].fd, "connect");
|
||||
|
||||
set_capabilities(0);
|
||||
|
||||
cnx.q[1].fd = out_socket;
|
||||
|
||||
get_connection_desc(&desc, &cnx);
|
||||
log_connection(&desc, &cnx);
|
||||
set_proctitle_shovel(&desc, &cnx);
|
||||
@@ -126,7 +123,7 @@ void start_shoveler(int in_socket)
|
||||
shovel(&cnx);
|
||||
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
close(cnx.q[1].fd);
|
||||
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
@@ -147,7 +144,7 @@ void stop_listeners(int sig)
|
||||
|
||||
void set_listen_procname(struct listen_endpoint *listen_socket)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
#if HAVE_LIBBSD
|
||||
int res;
|
||||
struct addrinfo addr;
|
||||
struct sockaddr_storage ss;
|
||||
@@ -184,7 +181,6 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
||||
|
||||
while (1) {
|
||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||
CHECK_RES_RETURN(in_socket, "accept", /*void*/ );
|
||||
if (in_socket == -1) {
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
|
||||
__FILE__, __LINE__, "accept", errno, strerror(errno));
|
||||
@@ -247,7 +243,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
case 0:
|
||||
set_listen_procname(&listen_sockets[i]);
|
||||
if (listen_sockets[i].type == SOCK_DGRAM)
|
||||
print_message(msg_config_error, "UDP not (yet?) supported in sslh-fork\n");
|
||||
print_message(msg_config_error, "UDP not supported in sslh-fork\n");
|
||||
else
|
||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||
|
||||
@@ -272,7 +268,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
/* The actual main is in common.c: it's the same for both version of
|
||||
/* The actual main() is in sslh_main.c: it's the same for all versions of
|
||||
* the server
|
||||
*/
|
||||
|
||||
+74
-14
@@ -30,20 +30,24 @@
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "tcp-probe.h"
|
||||
|
||||
#if HAVE_LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBCAP
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
/* Constants for options that have no one-character shorthand */
|
||||
#define OPT_ONTIMEOUT 257
|
||||
|
||||
static void printcaps(void) {
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
cap_t caps;
|
||||
char* desc;
|
||||
ssize_t len;
|
||||
@@ -67,13 +71,17 @@ static void printsettings(void)
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||
p = &cfg.protocols[i];
|
||||
strcpy(buf, "resolve on forward");
|
||||
if (!p->resolve_on_forward) {
|
||||
sprintaddr(buf, sizeof(buf), p->saddr);
|
||||
size_t len = strlen(buf);
|
||||
sprintf(buf+len, " family %d %d",
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family);
|
||||
if (p->is_unix) {
|
||||
sprintf(buf, "unix socket: %s", p->host);
|
||||
} else {
|
||||
strcpy(buf, "resolve on forward");
|
||||
if (!p->resolve_on_forward) {
|
||||
sprintaddr(buf, sizeof(buf), p->saddr);
|
||||
size_t len = strlen(buf);
|
||||
sprintf(buf+len, " family %d %d",
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family);
|
||||
}
|
||||
}
|
||||
print_message(msg_config,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d [%s] [%s] [%s]\n",
|
||||
@@ -129,6 +137,36 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Perform some fixups on configuration after reading it.
|
||||
* if verbose is present, override all other verbose options
|
||||
*/
|
||||
void config_finish(struct sslhcfg_item* cfg)
|
||||
{
|
||||
if (cfg->verbose) {
|
||||
cfg->verbose_config = cfg->verbose;
|
||||
cfg->verbose_config_error = cfg->verbose;
|
||||
cfg->verbose_connections = cfg->verbose;
|
||||
cfg->verbose_connections_try = cfg->verbose;
|
||||
cfg->verbose_connections_error = cfg->verbose;
|
||||
cfg->verbose_fd = cfg->verbose;
|
||||
cfg->verbose_packets = cfg->verbose;
|
||||
cfg->verbose_probe_info = cfg->verbose;
|
||||
cfg->verbose_probe_error = cfg->verbose;
|
||||
cfg->verbose_system_error = cfg->verbose;
|
||||
cfg->verbose_int_error = cfg->verbose;
|
||||
}
|
||||
}
|
||||
|
||||
/* Checks that the UNIX socket specified exists and is accessible
|
||||
* Dies otherwise
|
||||
*/
|
||||
static void check_access_unix_socket(struct sslhcfg_protocols_item* p)
|
||||
{
|
||||
/* TODO */
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* For each protocol in the configuration, resolve address and set up protocol
|
||||
* options if required
|
||||
*/
|
||||
@@ -138,7 +176,9 @@ static void config_protocols()
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
|
||||
|
||||
if (
|
||||
if (p->is_unix) {
|
||||
check_access_unix_socket(p);
|
||||
} else if (
|
||||
!p->resolve_on_forward &&
|
||||
resolve_split_name(&(p->saddr), p->host, p->port)
|
||||
) {
|
||||
@@ -219,6 +259,24 @@ void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
}
|
||||
}
|
||||
|
||||
/* Connect stdin, stdout, stderr to /dev/null. It is better to keep them around
|
||||
* so they do not get re-used by socket descriptors, and accidently used by
|
||||
* some library code.
|
||||
*/
|
||||
void close_std(void)
|
||||
{
|
||||
int newfd;
|
||||
|
||||
if ((newfd = open("/dev/null", O_RDWR))) {
|
||||
dup2 (newfd, STDIN_FILENO);
|
||||
dup2 (newfd, STDOUT_FILENO);
|
||||
dup2 (newfd, STDERR_FILENO);
|
||||
/* close the helper handle, as this is now unnecessary */
|
||||
close(newfd);
|
||||
} else {
|
||||
print_message(msg_system_error, "Error closing standard filehandles for background daemon\n");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[], char* envp[])
|
||||
{
|
||||
@@ -228,13 +286,14 @@ int main(int argc, char *argv[], char* envp[])
|
||||
int res, num_addr_listen;
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
#ifdef LIBBSD
|
||||
#if HAVE_LIBBSD
|
||||
setproctitle_init(argc, argv, envp);
|
||||
#endif
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
||||
if (res) exit(6);
|
||||
config_finish(&cfg);
|
||||
|
||||
if (cfg.version) {
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
@@ -265,6 +324,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
|
||||
if (!cfg.foreground) {
|
||||
if (fork() > 0) exit(0); /* Detach */
|
||||
close_std();
|
||||
|
||||
/* New session -- become group leader */
|
||||
if (getuid() == 0) {
|
||||
|
||||
+1
-1
@@ -210,7 +210,7 @@ void start_shoveler(int listen_socket) {
|
||||
}
|
||||
|
||||
|
||||
/* The actual main is in common.c: it's the same for both version of
|
||||
/* The actual main is in sslh-main.c: it's the same for all versions of
|
||||
* the server
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
=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>] [-v] [-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<--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
|
||||
|
||||
@@ -14,7 +14,7 @@ B<sslh> accepts connections on specified ports, and forwards
|
||||
them further based on tests performed on the first data
|
||||
packet sent by the remote client.
|
||||
|
||||
Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are
|
||||
Probes for HTTP, TLS, SSH, OpenVPN, tinc, XMPP are
|
||||
implemented, and any other protocol that can be tested using
|
||||
a regular expression, can be recognised. A typical use case
|
||||
is to allow serving several services on port 443 (e.g. to
|
||||
@@ -102,8 +102,10 @@ clients wait for the server to send its banner.
|
||||
|
||||
Makes B<sslh> behave as a transparent proxy, i.e. the
|
||||
receiving service sees the original client's IP address.
|
||||
This works on Linux only and involves B<iptables> settings.
|
||||
Refer to the README for more information.
|
||||
This works on Linux only and involves B<iproute2> settings.
|
||||
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>
|
||||
for more information.
|
||||
|
||||
=item B<-p> I<listening address>, B<--listen> I<listening address>
|
||||
|
||||
@@ -123,10 +125,11 @@ Note that you can set B<sslh> to listen on I<ext_ip:443> and
|
||||
B<httpd> to listen on I<localhost:443>: this allows clients
|
||||
inside your network to just connect directly to B<httpd>.
|
||||
|
||||
Also, B<sslh> probes for SSLv3 (or TLSv1) handshake and will
|
||||
Also, B<sslh> probes for TLS handshakes and will
|
||||
reject connections from clients requesting SSLv2. This is
|
||||
compliant with RFC6176 which prohibits the usage of SSLv2. If
|
||||
you wish to accept SSLv2, use B<--anyprot> instead.
|
||||
compliant with RFC6176 which prohibits the usage of SSLv2.
|
||||
If you wish to accept SSLv2, use B<--anyprot> instead.
|
||||
|
||||
|
||||
=item B<--ssh> I<target address>
|
||||
|
||||
@@ -164,9 +167,10 @@ specified on the command line, this should be specified
|
||||
last. If no default is specified, B<sslh> will forward
|
||||
unknown protocols to the first protocol specified.
|
||||
|
||||
=item B<-v>, B<--verbose>
|
||||
=item B<-v>, B<--verbose> I<n>
|
||||
|
||||
Increase verboseness.
|
||||
Override all verboseness. Refer to B<example.cfg> for all
|
||||
verbose sub-options.
|
||||
|
||||
=item B<-n>, B<--numeric>
|
||||
|
||||
@@ -233,8 +237,8 @@ detailed explanation of the variables used by B<sslh>.
|
||||
=head1 SEE ALSO
|
||||
|
||||
The latest version is available from
|
||||
L<http://www.rutschle.net/tech/sslh>, and can be tracked
|
||||
from L<http://freecode.com/projects/sslh>.
|
||||
L<https://github.com/yrutschle/sslh>. There you can find a more
|
||||
detailed and recent documentation.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ config: {
|
||||
name : "sslhcfg",
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "verbose"; type: "int" default: 0;
|
||||
short: "v";
|
||||
description: "Override all verbosness options" },
|
||||
{ name: "verbose-config"; type: "int"; default: 0;
|
||||
description: "Print configuration at startup" },
|
||||
{ name: "verbose-config-error"; type: "int"; default: 3;
|
||||
@@ -95,6 +98,7 @@ config: {
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "is_unix"; type: "bool"; default: false },
|
||||
{ name: "keepalive"; type: "bool"; default: false; }
|
||||
)
|
||||
},
|
||||
@@ -107,6 +111,7 @@ config: {
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "service"; type: "string"; optional: true; },
|
||||
{ name: "is_unix"; type: "bool"; default: false },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "udp_timeout"; type: "int"; default: 60 },
|
||||
{ name: "fork"; type: "bool"; default: false },
|
||||
@@ -131,6 +136,7 @@ config: {
|
||||
element_type: "string"
|
||||
},
|
||||
{ name: "minlength"; type: "int"; optional: true },
|
||||
{ name: "proxyprotocol"; type: "int"; optional: true },
|
||||
|
||||
# Runtime data
|
||||
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
|
||||
|
||||
+5
-8
@@ -153,7 +153,7 @@ static int connect_queue(struct connection* cnx,
|
||||
{
|
||||
struct queue *q = &cnx->q[1];
|
||||
|
||||
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
||||
connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
||||
if (q->fd != -1) {
|
||||
log_connection(NULL, cnx);
|
||||
flush_deferred(q);
|
||||
@@ -227,7 +227,6 @@ static void shovel_single(struct connection *cnx)
|
||||
static void connect_proxy(struct connection *cnx)
|
||||
{
|
||||
int in_socket;
|
||||
int out_socket;
|
||||
|
||||
/* Minimize the file descriptor value to help select() */
|
||||
in_socket = dup(cnx->q[0].fd);
|
||||
@@ -238,18 +237,16 @@ static void connect_proxy(struct connection *cnx)
|
||||
cnx->q[0].fd = in_socket;
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(out_socket, "connect");
|
||||
|
||||
cnx->q[1].fd = out_socket;
|
||||
/* Connect the backend server socket */
|
||||
connect_addr(cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(cnx->q[1].fd, "connect");
|
||||
|
||||
log_connection(NULL, cnx);
|
||||
|
||||
shovel_single(cnx);
|
||||
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
close(cnx->q[1].fd);
|
||||
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ listen:
|
||||
(
|
||||
{ host: "localhost"; port: "8080"; keepalive: true; },
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; },
|
||||
{ host: "ip4-localhost"; is_udp: true; port: "8086"; }
|
||||
{ host: "ip4-localhost"; is_udp: true; port: "8086"; },
|
||||
{ host: "/tmp/sslh.sock"; is_unix: true; port: ""; }
|
||||
);
|
||||
|
||||
|
||||
@@ -44,7 +45,7 @@ protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "http"; host: "localhost"; port: "9002"; },
|
||||
{ name: "http"; host: "localhost"; port: "80"; proxyprotocol: 2; },
|
||||
{ name: "tinc"; host: "localhost"; port: "9003"; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "9004"; },
|
||||
{ name: "xmpp"; host: "localhost"; port: "9009"; },
|
||||
|
||||
Reference in new issue
Block a user