mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-22 18:12:10 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
591cfc9a8a | ||
|
|
3013658b20 | ||
|
|
a704c7f7f5 | ||
|
|
1a3341c2a4 | ||
|
|
4a6bbda60d | ||
|
|
fa848f2ae9 | ||
|
|
6aa19d080a | ||
|
|
a43385b8db | ||
|
|
1261a5f4c7 | ||
|
|
40da147efd | ||
|
|
e4936454c5 | ||
|
|
63f9c4a582 | ||
|
|
0e45107797 | ||
|
|
4584e719e1 | ||
|
|
75e426c80f | ||
|
|
e40d43bea5 | ||
|
|
2196146224 | ||
|
|
2f822741b3 | ||
|
|
760def3444 | ||
|
|
4b885b4a2c | ||
|
|
ce23f202b7 | ||
|
|
82b8ba547e | ||
|
|
8e38d56167 | ||
|
|
ea3b6794e1 | ||
|
|
e42f165ef5 | ||
|
|
37d2756703 | ||
|
|
ec25ab56e8 | ||
|
|
7fb65ad0ac | ||
|
|
c43882c85f | ||
|
|
4a4c571116 | ||
|
|
c049885758 | ||
|
|
de0ec959d9 | ||
|
|
e7df8eeaa1 | ||
|
|
8af87ebbad | ||
|
|
bf57d63c3a | ||
|
|
862e33cfec | ||
|
|
24e7f46a43 | ||
|
|
bf2053eb79 | ||
|
|
1ad450a444 | ||
|
|
300e1916c3 | ||
|
|
e678428334 | ||
|
|
edc42ca13b | ||
|
|
c41ec489e0 | ||
|
|
79f49f4481 | ||
|
|
8e130882fc | ||
|
|
030ef64b99 | ||
|
|
cf4f4cbebe | ||
|
|
905ac95ca1 | ||
|
|
da3b51e056 | ||
|
|
308b54aa8a | ||
|
|
987643878f | ||
|
|
46e741e9c2 | ||
|
|
c3d019284d | ||
|
|
4c570e8d57 | ||
|
|
0a00220b42 | ||
|
|
a0f9fc164e | ||
|
|
517e4ad5b4 | ||
|
|
b258b0e0f7 | ||
|
|
02f6c6999d | ||
|
|
41a914b350 | ||
|
|
a487ad4cbe | ||
|
|
21ed247acb | ||
|
|
dfa5d3cedb | ||
|
|
74ce2acdc5 | ||
|
|
0e3242c115 | ||
|
|
5715a9f0bd | ||
|
|
de474e1d07 | ||
|
|
cb199ed3c6 | ||
|
|
7485afe397 | ||
|
|
417722e3c1 | ||
|
|
9fca586735 | ||
|
|
0613d412a2 | ||
|
|
b2ee07655a | ||
|
|
465ecdda5b | ||
|
|
3edf6a9d22 | ||
|
|
7a88a50c32 | ||
|
|
e7a5bbc4e1 | ||
|
|
40557c58ad | ||
|
|
f0e1aaf82c | ||
|
|
f51781664c | ||
|
|
5948270b95 | ||
|
|
758be2a8ab | ||
|
|
654ff0b5b0 | ||
|
|
15a5073559 | ||
|
|
c2b6bf246c | ||
|
|
6366d50b89 | ||
|
|
5b93e4ab55 | ||
|
|
1c63b06cc8 | ||
|
|
71c617050d | ||
|
|
0409775a1a | ||
|
|
b72baa0622 | ||
|
|
790d639ad0 | ||
|
|
e884fc616d | ||
|
|
7e63dedca3 | ||
|
|
49d4080afd | ||
|
|
be4e126f3a | ||
|
|
34bcc2bbc8 | ||
|
|
90975fd6c3 | ||
|
|
f91f16d753 | ||
|
|
dae8101a50 | ||
|
|
68fc10aed0 | ||
|
|
49c136691c | ||
|
|
adb27aa4a3 | ||
|
|
9ff9723278 | ||
|
|
52a9356c35 | ||
|
|
ac3b43d4bd | ||
|
|
368f286ce5 | ||
|
|
05a835ff1f | ||
|
|
0a3d5874b4 | ||
|
|
a42d4f8049 | ||
|
|
1e33455fe7 | ||
|
|
a3d9df87cb | ||
|
|
247d60d8e6 | ||
|
|
7d820049a2 | ||
|
|
09597bfa42 | ||
|
|
41e3b0cd1d | ||
|
|
76d0351452 | ||
|
|
fccaa5fa9f | ||
|
|
f077101835 | ||
|
|
f3230b4a94 | ||
|
|
c12f7a1ade | ||
|
|
ebeabb6c18 | ||
|
|
8a1dae8c22 | ||
|
|
07719f55b8 | ||
|
|
6c94bf71b8 | ||
|
|
0d9e004d32 | ||
|
|
0c388cb367 | ||
|
|
9f99f296b1 | ||
|
|
8b6e06e6c7 | ||
|
|
5cdff8c558 | ||
|
|
72438dc1ae | ||
|
|
e67d6ff905 | ||
|
|
0af37a1bed | ||
|
|
08ce89b6e2 | ||
|
|
99d4a6d4a2 | ||
|
|
2c93a015ea | ||
|
|
3315a727dc | ||
|
|
8638199f13 | ||
|
|
d7cf82424b | ||
|
|
ac8563525e | ||
|
|
2f983625d0 | ||
|
|
f6b11a424b | ||
|
|
cf46cd5bb4 | ||
|
|
b473622698 | ||
|
|
de8e5725c2 | ||
|
|
95a6577cda |
No files matched your search
@@ -1,4 +1,65 @@
|
||||
v1.23.0:
|
||||
Obsolete. Please use v2.0 or above. This is a branch
|
||||
where no new features will be introduced, only
|
||||
bugfixes to versions below 1.22.
|
||||
|
||||
Reintroduce --ssl as alias to --tls.
|
||||
|
||||
|
||||
v1.22: 17AUG2021
|
||||
sslh-select now supports UDP protocols.
|
||||
Probes specified in the `protocols`
|
||||
configuration entry are tried on incoming packets,
|
||||
TCP or UDP, and forwarded based on the input
|
||||
protocol (an incoming TCP connection will be
|
||||
forwarded as TCP, and same with UDP).
|
||||
This has been tested with DNS as shown in udp.cfg:
|
||||
incoming packets that contain my domain name are
|
||||
assumed to be a DNS request and forwarded
|
||||
accordingly. Note this could cause problems if
|
||||
combined with incoming TLS with SNI. UDP clients
|
||||
and servers need to agree on the IPv4/IPv6 they use:
|
||||
use the same protocol on all sides! Often, this
|
||||
means explicitely using 'ip4-localhost'.
|
||||
UDP sender-receiver pairs (connections, so to speak)
|
||||
are kept for 60s, which can be changed with
|
||||
`udp_timeout` in the configuration.
|
||||
|
||||
Added probes for UDP protocols QUICK and Teamspeak.
|
||||
|
||||
Added probes for syslog protocol.
|
||||
|
||||
sslh-select refactored to change linear searches
|
||||
through connections to linear searches through
|
||||
fd_set.
|
||||
|
||||
Fixed a libconfig call to support libconfig 1.7.3.
|
||||
|
||||
Added symbol to support libconfig 1.4.9, still in
|
||||
use in CentOS7.
|
||||
|
||||
Warn about unknown settings in the configuration
|
||||
file.
|
||||
|
||||
Added per-protocol `transparent` option. sslh-fork
|
||||
drops the capability after creating the server-side
|
||||
transparent socket. Transparent now uses CAP_NET_RAW
|
||||
instead of CAP_NET_ADMIN.
|
||||
|
||||
Removed compile-time option to use POSIX regex. Now
|
||||
regex must be PCRE2 (Perl-Compatible). This was in
|
||||
fact the case since v1.21, as PCRE are used to parse
|
||||
the config file.
|
||||
|
||||
v1.21: 11JUL2020
|
||||
WARNING:
|
||||
Moved configuration and command-line management to
|
||||
use conf2struct. Changes are:
|
||||
* `--ssl` and using `name: 'ssl'` in config file is no longer supported, use `tls` instead.
|
||||
* command line option <-F|--config> no longer defaults to /etc/sslh.cfg, so you have to
|
||||
specify it explicitly.
|
||||
* command line option <-v|--verbose> takes a mandatory integer parameter
|
||||
|
||||
Added TCP_FASTOPEN support for client sockets (if
|
||||
tfo_ok is specified in their configuration) and for
|
||||
listenint socket, if all client protocols support it.
|
||||
@@ -8,10 +69,6 @@ v1.21: 11JUL2020
|
||||
than that many bytes have been received (mostly for
|
||||
regex)
|
||||
|
||||
Moved configuration and command-line management to
|
||||
use conf2struct. Hopefully this should be transparent
|
||||
to users.
|
||||
|
||||
Update Let's Encrypt entry in example.cfg for tls-alpn-01
|
||||
challenges; tls-sni-* challenges are now deprecated.
|
||||
|
||||
@@ -21,6 +78,9 @@ v1.21: 11JUL2020
|
||||
Use syslog_facility: "none" to disable syslog
|
||||
output.
|
||||
|
||||
Changed exit code for illegal command line parameter
|
||||
from 1 to 6 (for testing purposes)
|
||||
|
||||
v1.20: 20NOV2018
|
||||
Added support for socks5 protocol (Eugene Protozanov)
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
FROM alpine:latest as build
|
||||
|
||||
ADD . /sslh
|
||||
|
||||
RUN \
|
||||
apk add \
|
||||
gcc \
|
||||
libconfig-dev \
|
||||
make \
|
||||
musl-dev \
|
||||
pcre-dev \
|
||||
perl && \
|
||||
cd /sslh && \
|
||||
make sslh-select && \
|
||||
strip sslh-select
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=build /sslh/sslh-select /sslh
|
||||
|
||||
RUN apk --no-cache add libconfig pcre
|
||||
|
||||
ENTRYPOINT [ "/sslh", "--foreground"]
|
||||
@@ -1,9 +1,10 @@
|
||||
# Configuration
|
||||
|
||||
VERSION=$(shell ./genver.sh -r)
|
||||
|
||||
# Configuration -- you probably need to `make clean` if you
|
||||
# change any of these
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBPCRE=1 # Use libpcre? (needed for regex on musl)
|
||||
USELIBWRAP?= # Use libwrap?
|
||||
USELIBCAP= # Use libcap?
|
||||
USESYSTEMD= # Make use of systemd socket activation
|
||||
@@ -23,10 +24,10 @@ ifneq ($(strip $(COV_TEST)),)
|
||||
endif
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?=-Wall -g $(CFLAGS_COV)
|
||||
CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV)
|
||||
|
||||
LIBS=
|
||||
OBJS=sslh-conf.o common.o sslh-main.o probe.o tls.o argtable3.o
|
||||
LIBS=-lm -lpcre2-8
|
||||
OBJS=sslh-conf.o common.o sslh-main.o probe.o tls.o argtable3.o udp-listener.o collection.o gap.o
|
||||
|
||||
CONDITIONAL_TARGETS=
|
||||
|
||||
@@ -39,11 +40,6 @@ ifneq ($(strip $(ENABLE_REGEX)),)
|
||||
CPPFLAGS+=-DENABLE_REGEX
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBPCRE)),)
|
||||
CPPFLAGS+=-DLIBPCRE
|
||||
LIBS:=$(LIBS) -lpcreposix
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBCONFIG)),)
|
||||
LIBS:=$(LIBS) -lconfig
|
||||
CPPFLAGS+=-DLIBCONFIG
|
||||
@@ -76,24 +72,27 @@ version.h:
|
||||
|
||||
sslh: sslh-fork sslh-select
|
||||
|
||||
$(OBJS): version.h
|
||||
$(OBJS): version.h common.h collection.h sslh-conf.h gap.h
|
||||
|
||||
sslh-conf.c: sslhconf.cfg
|
||||
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
||||
conf2struct sslhconf.cfg
|
||||
|
||||
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile common.h
|
||||
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
|
||||
#strip sslh-fork
|
||||
|
||||
sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
|
||||
sslh-select: version.h $(OBJS) sslh-select.o Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
|
||||
#strip sslh-select
|
||||
|
||||
systemd-sslh-generator: systemd-sslh-generator.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
|
||||
|
||||
echosrv: version.h $(OBJS) echosrv.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
|
||||
echosrv-conf.c echosrv-conf.h: echosrv.cfg
|
||||
conf2struct echosrv.cfg
|
||||
|
||||
echosrv: version.h echosrv-conf.c echosrv.o echosrv-conf.o argtable3.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS)
|
||||
|
||||
$(MAN): sslh.pod Makefile
|
||||
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
|
||||
@@ -102,6 +101,16 @@ $(MAN): sslh.pod Makefile
|
||||
# configuration
|
||||
release:
|
||||
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
|
||||
gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz
|
||||
|
||||
# Build docker image
|
||||
docker:
|
||||
docker image build -t "sslh:${VERSION}" .
|
||||
docker image tag "sslh:${VERSION}" sslh:latest
|
||||
|
||||
docker-clean:
|
||||
yes | docker image rm "sslh:${VERSION}" sslh:latest
|
||||
yes | docker image prune
|
||||
|
||||
# generic install: install binary and man page
|
||||
install: sslh $(MAN)
|
||||
@@ -121,7 +130,7 @@ uninstall:
|
||||
update-rc.d sslh remove
|
||||
|
||||
distclean: clean
|
||||
rm -f tags sslh-conf.c sslh-conf.h cscope.*
|
||||
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
|
||||
|
||||
clean:
|
||||
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
|
||||
@@ -20,9 +20,9 @@ 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, a fork-based and a
|
||||
select-based model, and more.
|
||||
systemd support, transparent proxying, chroot, logging,
|
||||
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
|
||||
model, and more.
|
||||
|
||||
Install
|
||||
=======
|
||||
@@ -37,11 +37,59 @@ Please refer to the [configuration guide](doc/config.md).
|
||||
|
||||
|
||||
|
||||
Docker image
|
||||
------------
|
||||
|
||||
How to use
|
||||
|
||||
---
|
||||
|
||||
Build docker image
|
||||
|
||||
make docker
|
||||
|
||||
|
||||
```bash
|
||||
docker container run \
|
||||
--rm \
|
||||
-it \
|
||||
--listen=0.0.0.0:443 \
|
||||
--ssh=hostname:22 \
|
||||
--tlshostname:443 \
|
||||
sslh:latest
|
||||
```
|
||||
|
||||
docker-compose example
|
||||
|
||||
```
|
||||
---
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
sslh:
|
||||
image: sslh:latest
|
||||
hostname: sslh
|
||||
ports:
|
||||
- 443:443/tcp
|
||||
command: --listen=0.0.0.0:443 --tlshostname:443 --openvpn=openvpn:1194
|
||||
depends_on:
|
||||
- nginx
|
||||
- openvpn
|
||||
|
||||
nginx:
|
||||
image: nginx
|
||||
hostname: nginx
|
||||
|
||||
openvpn:
|
||||
image: openvpn:latest
|
||||
hostname: openvpn
|
||||
```
|
||||
|
||||
Comments? Questions?
|
||||
====================
|
||||
|
||||
You can subscribe to the `sslh` mailing list here:
|
||||
<http://rutschle.net/cgi-bin/mailman/listinfo/sslh>
|
||||
<https://lists.rutschle.net/mailman/listinfo/sslh>
|
||||
|
||||
This mailing list should be used for discussion, feature
|
||||
requests, and will be the preferred channel for announcements.
|
||||
|
||||
+3578
-2590
File diff suppressed because it is too large.
Load diff
+198
-230
@@ -1,4 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* argtable3: Declares the main interfaces of the library
|
||||
*
|
||||
* This file is part of the argtable3 library.
|
||||
*
|
||||
* Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann
|
||||
@@ -31,273 +33,239 @@
|
||||
#ifndef ARGTABLE3
|
||||
#define ARGTABLE3
|
||||
|
||||
#include <stdio.h> /* FILE */
|
||||
#include <time.h> /* struct tm */
|
||||
#include <stdio.h> /* FILE */
|
||||
#include <time.h> /* struct tm */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ARG_REX_ICASE 1
|
||||
|
||||
#define ARG_DSTR_SIZE 200
|
||||
#define ARG_CMD_NAME_LEN 100
|
||||
#define ARG_CMD_DESCRIPTION_LEN 256
|
||||
|
||||
#ifndef ARG_REPLACE_GETOPT
|
||||
#define ARG_REPLACE_GETOPT 1 /* use the embedded getopt as the system getopt(3) */
|
||||
#endif /* ARG_REPLACE_GETOPT */
|
||||
|
||||
/* bit masks for arg_hdr.flag */
|
||||
enum
|
||||
{
|
||||
ARG_TERMINATOR=0x1,
|
||||
ARG_HASVALUE=0x2,
|
||||
ARG_HASOPTVALUE=0x4
|
||||
};
|
||||
enum { ARG_TERMINATOR = 0x1, ARG_HASVALUE = 0x2, ARG_HASOPTVALUE = 0x4 };
|
||||
|
||||
typedef void (arg_resetfn)(void *parent);
|
||||
typedef int (arg_scanfn)(void *parent, const char *argval);
|
||||
typedef int (arg_checkfn)(void *parent);
|
||||
typedef void (arg_errorfn)(void *parent, FILE *fp, int error, const char *argval, const char *progname);
|
||||
#if defined(_WIN32)
|
||||
#if defined(argtable3_EXPORTS)
|
||||
#define ARG_EXTERN __declspec(dllexport)
|
||||
#elif defined(argtable3_IMPORTS)
|
||||
#define ARG_EXTERN __declspec(dllimport)
|
||||
#else
|
||||
#define ARG_EXTERN
|
||||
#endif
|
||||
#else
|
||||
#define ARG_EXTERN
|
||||
#endif
|
||||
|
||||
typedef struct _internal_arg_dstr* arg_dstr_t;
|
||||
typedef void* arg_cmd_itr_t;
|
||||
|
||||
typedef void(arg_resetfn)(void* parent);
|
||||
typedef int(arg_scanfn)(void* parent, const char* argval);
|
||||
typedef int(arg_checkfn)(void* parent);
|
||||
typedef void(arg_errorfn)(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname);
|
||||
typedef void(arg_dstr_freefn)(char* buf);
|
||||
typedef int(arg_cmdfn)(int argc, char* argv[], arg_dstr_t res);
|
||||
typedef int(arg_comparefn)(const void* k1, const void* k2);
|
||||
|
||||
/*
|
||||
* The arg_hdr struct defines properties that are common to all arg_xxx structs.
|
||||
* The argtable library requires each arg_xxx struct to have an arg_hdr
|
||||
* struct as its first data member.
|
||||
* The argtable library functions then use this data to identify the
|
||||
* properties of the command line option, such as its option tags,
|
||||
* datatype string, and glossary strings, and so on.
|
||||
* Moreover, the arg_hdr struct contains pointers to custom functions that
|
||||
* are provided by each arg_xxx struct which perform the tasks of parsing
|
||||
* that particular arg_xxx arguments, performing post-parse checks, and
|
||||
* reporting errors.
|
||||
* These functions are private to the individual arg_xxx source code
|
||||
* and are the pointer to them are initiliased by that arg_xxx struct's
|
||||
* constructor function. The user could alter them after construction
|
||||
* if desired, but the original intention is for them to be set by the
|
||||
* constructor and left unaltered.
|
||||
*/
|
||||
struct arg_hdr
|
||||
{
|
||||
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
||||
const char *shortopts; /* String defining the short options */
|
||||
const char *longopts; /* String defiing the long options */
|
||||
const char *datatype; /* Description of the argument data type */
|
||||
const char *glossary; /* Description of the option as shown by arg_print_glossary function */
|
||||
int mincount; /* Minimum number of occurences of this option accepted */
|
||||
int maxcount; /* Maximum number of occurences if this option accepted */
|
||||
void *parent; /* Pointer to parent arg_xxx struct */
|
||||
arg_resetfn *resetfn; /* Pointer to parent arg_xxx reset function */
|
||||
arg_scanfn *scanfn; /* Pointer to parent arg_xxx scan function */
|
||||
arg_checkfn *checkfn; /* Pointer to parent arg_xxx check function */
|
||||
arg_errorfn *errorfn; /* Pointer to parent arg_xxx error function */
|
||||
void *priv; /* Pointer to private header data for use by arg_xxx functions */
|
||||
};
|
||||
* The arg_hdr struct defines properties that are common to all arg_xxx structs.
|
||||
* The argtable library requires each arg_xxx struct to have an arg_hdr
|
||||
* struct as its first data member.
|
||||
* The argtable library functions then use this data to identify the
|
||||
* properties of the command line option, such as its option tags,
|
||||
* datatype string, and glossary strings, and so on.
|
||||
* Moreover, the arg_hdr struct contains pointers to custom functions that
|
||||
* are provided by each arg_xxx struct which perform the tasks of parsing
|
||||
* that particular arg_xxx arguments, performing post-parse checks, and
|
||||
* reporting errors.
|
||||
* These functions are private to the individual arg_xxx source code
|
||||
* and are the pointer to them are initiliased by that arg_xxx struct's
|
||||
* constructor function. The user could alter them after construction
|
||||
* if desired, but the original intention is for them to be set by the
|
||||
* constructor and left unaltered.
|
||||
*/
|
||||
typedef struct arg_hdr {
|
||||
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
||||
const char* shortopts; /* String defining the short options */
|
||||
const char* longopts; /* String defiing the long options */
|
||||
const char* datatype; /* Description of the argument data type */
|
||||
const char* glossary; /* Description of the option as shown by arg_print_glossary function */
|
||||
int mincount; /* Minimum number of occurences of this option accepted */
|
||||
int maxcount; /* Maximum number of occurences if this option accepted */
|
||||
void* parent; /* Pointer to parent arg_xxx struct */
|
||||
arg_resetfn* resetfn; /* Pointer to parent arg_xxx reset function */
|
||||
arg_scanfn* scanfn; /* Pointer to parent arg_xxx scan function */
|
||||
arg_checkfn* checkfn; /* Pointer to parent arg_xxx check function */
|
||||
arg_errorfn* errorfn; /* Pointer to parent arg_xxx error function */
|
||||
void* priv; /* Pointer to private header data for use by arg_xxx functions */
|
||||
} arg_hdr_t;
|
||||
|
||||
struct arg_rem
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
};
|
||||
typedef struct arg_rem {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
} arg_rem_t;
|
||||
|
||||
struct arg_lit
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
};
|
||||
typedef struct arg_lit {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
} arg_lit_t;
|
||||
|
||||
struct arg_int
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
int *ival; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_int {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
int* ival; /* Array of parsed argument values */
|
||||
} arg_int_t;
|
||||
|
||||
struct arg_dbl
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
double *dval; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_dbl {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
double* dval; /* Array of parsed argument values */
|
||||
} arg_dbl_t;
|
||||
|
||||
struct arg_str
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char **sval; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_str {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char** sval; /* Array of parsed argument values */
|
||||
} arg_str_t;
|
||||
|
||||
struct arg_rex
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char **sval; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_rex {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char** sval; /* Array of parsed argument values */
|
||||
} arg_rex_t;
|
||||
|
||||
struct arg_file
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args*/
|
||||
const char **filename; /* Array of parsed filenames (eg: /home/foo.bar) */
|
||||
const char **basename; /* Array of parsed basenames (eg: foo.bar) */
|
||||
const char **extension; /* Array of parsed extensions (eg: .bar) */
|
||||
};
|
||||
typedef struct arg_file {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args*/
|
||||
const char** filename; /* Array of parsed filenames (eg: /home/foo.bar) */
|
||||
const char** basename; /* Array of parsed basenames (eg: foo.bar) */
|
||||
const char** extension; /* Array of parsed extensions (eg: .bar) */
|
||||
} arg_file_t;
|
||||
|
||||
struct arg_date
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
const char *format; /* strptime format string used to parse the date */
|
||||
int count; /* Number of matching command line args */
|
||||
struct tm *tmval; /* Array of parsed time values */
|
||||
};
|
||||
typedef struct arg_date {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
const char* format; /* strptime format string used to parse the date */
|
||||
int count; /* Number of matching command line args */
|
||||
struct tm* tmval; /* Array of parsed time values */
|
||||
} arg_date_t;
|
||||
|
||||
enum {ARG_ELIMIT=1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG};
|
||||
struct arg_end
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of errors encountered */
|
||||
int *error; /* Array of error codes */
|
||||
void **parent; /* Array of pointers to offending arg_xxx struct */
|
||||
const char **argval; /* Array of pointers to offending argv[] string */
|
||||
};
|
||||
enum { ARG_ELIMIT = 1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG };
|
||||
typedef struct arg_end {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of errors encountered */
|
||||
int* error; /* Array of error codes */
|
||||
void** parent; /* Array of pointers to offending arg_xxx struct */
|
||||
const char** argval; /* Array of pointers to offending argv[] string */
|
||||
} arg_end_t;
|
||||
|
||||
typedef struct arg_cmd_info {
|
||||
char name[ARG_CMD_NAME_LEN];
|
||||
char description[ARG_CMD_DESCRIPTION_LEN];
|
||||
arg_cmdfn* proc;
|
||||
} arg_cmd_info_t;
|
||||
|
||||
/**** arg_xxx constructor functions *********************************/
|
||||
|
||||
struct arg_rem* arg_rem(const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_rem* arg_rem(const char* datatype, const char* glossary);
|
||||
|
||||
struct arg_lit* arg_lit0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* glossary);
|
||||
struct arg_lit* arg_lit1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char *glossary);
|
||||
struct arg_lit* arg_litn(const char* shortopts,
|
||||
const char* longopts,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_key* arg_key0(const char* keyword,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
struct arg_key* arg_key1(const char* keyword,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
struct arg_key* arg_keyn(const char* keyword,
|
||||
int flags,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_int* arg_int0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_int* arg_int1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_int* arg_intn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char *datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_dbl* arg_dbl0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_dbl* arg_dbl1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_dbl* arg_dbln(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char *datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_str* arg_str0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_str* arg_str1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_str* arg_strn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rexn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
|
||||
struct arg_rex* arg_rex0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
struct arg_rex* arg_rex1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int flags,
|
||||
const char *glossary);
|
||||
struct arg_rex* arg_rexn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
int flags,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_file* arg_file0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_file* arg_file1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_file* arg_filen(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_date* arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_date* arg_date0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* format,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_date* arg_date1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* format,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_date* arg_daten(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* format,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
|
||||
struct arg_end* arg_end(int maxerrors);
|
||||
ARG_EXTERN struct arg_end* arg_end(int maxerrors);
|
||||
|
||||
#define ARG_DSTR_STATIC ((arg_dstr_freefn*)0)
|
||||
#define ARG_DSTR_VOLATILE ((arg_dstr_freefn*)1)
|
||||
#define ARG_DSTR_DYNAMIC ((arg_dstr_freefn*)3)
|
||||
|
||||
/**** other functions *******************************************/
|
||||
int arg_nullcheck(void **argtable);
|
||||
int arg_parse(int argc, char **argv, void **argtable);
|
||||
void arg_print_option(FILE *fp, const char *shortopts, const char *longopts, const char *datatype, const char *suffix);
|
||||
void arg_print_syntax(FILE *fp, void **argtable, const char *suffix);
|
||||
void arg_print_syntaxv(FILE *fp, void **argtable, const char *suffix);
|
||||
void arg_print_glossary(FILE *fp, void **argtable, const char *format);
|
||||
void arg_print_glossary_gnu(FILE *fp, void **argtable);
|
||||
void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
|
||||
void arg_freetable(void **argtable, size_t n);
|
||||
ARG_EXTERN int arg_nullcheck(void** argtable);
|
||||
ARG_EXTERN int arg_parse(int argc, char** argv, void** argtable);
|
||||
ARG_EXTERN void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntax(FILE* fp, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_glossary(FILE* fp, void** argtable, const char* format);
|
||||
ARG_EXTERN void arg_print_glossary_gnu(FILE* fp, void** argtable);
|
||||
ARG_EXTERN void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
|
||||
ARG_EXTERN void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format);
|
||||
ARG_EXTERN void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable);
|
||||
ARG_EXTERN void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname);
|
||||
ARG_EXTERN void arg_freetable(void** argtable, size_t n);
|
||||
|
||||
ARG_EXTERN arg_dstr_t arg_dstr_create(void);
|
||||
ARG_EXTERN void arg_dstr_destroy(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_reset(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_free(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc);
|
||||
ARG_EXTERN void arg_dstr_cat(arg_dstr_t ds, const char* str);
|
||||
ARG_EXTERN void arg_dstr_catc(arg_dstr_t ds, char c);
|
||||
ARG_EXTERN void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...);
|
||||
ARG_EXTERN char* arg_dstr_cstr(arg_dstr_t ds);
|
||||
|
||||
ARG_EXTERN void arg_cmd_init(void);
|
||||
ARG_EXTERN void arg_cmd_uninit(void);
|
||||
ARG_EXTERN void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description);
|
||||
ARG_EXTERN void arg_cmd_unregister(const char* name);
|
||||
ARG_EXTERN int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res);
|
||||
ARG_EXTERN unsigned int arg_cmd_count(void);
|
||||
ARG_EXTERN arg_cmd_info_t* arg_cmd_info(const char* name);
|
||||
ARG_EXTERN arg_cmd_itr_t arg_cmd_itr_create(void);
|
||||
ARG_EXTERN void arg_cmd_itr_destroy(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN int arg_cmd_itr_advance(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN char* arg_cmd_itr_key(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k);
|
||||
ARG_EXTERN void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn);
|
||||
ARG_EXTERN void arg_make_get_help_msg(arg_dstr_t res);
|
||||
ARG_EXTERN void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable);
|
||||
ARG_EXTERN void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end);
|
||||
ARG_EXTERN int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode);
|
||||
ARG_EXTERN void arg_set_module_name(const char* name);
|
||||
ARG_EXTERN void arg_set_module_version(int major, int minor, int patch, const char* tag);
|
||||
|
||||
/**** deprecated functions, for back-compatibility only ********/
|
||||
void arg_free(void **argtable);
|
||||
ARG_EXTERN void arg_free(void** argtable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
collection.c: management of a collection of connections, for sslh-select
|
||||
|
||||
# Copyright (C) 2021 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
|
||||
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "collection.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "gap.h"
|
||||
|
||||
/* Info to keep track of all connections */
|
||||
struct cnx_collection {
|
||||
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
|
||||
};
|
||||
|
||||
/* Allocates and initialises a new collection of connections with at least
|
||||
* `len` elements. */
|
||||
cnx_collection* collection_init(int len)
|
||||
{
|
||||
cnx_collection* collection;
|
||||
|
||||
collection = malloc(sizeof(*collection));
|
||||
CHECK_ALLOC(collection, "collection_init(collection)");
|
||||
|
||||
memset(collection, 0, sizeof(*collection));
|
||||
|
||||
collection->fd2cnx = gap_init(len);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/* Caveat: might not work, as has never been used */
|
||||
void collection_destroy(cnx_collection* collection)
|
||||
{
|
||||
/* Caveat 2: no code to free connections yet */
|
||||
gap_destroy(collection->fd2cnx);
|
||||
free(collection);
|
||||
}
|
||||
|
||||
/* Points the file descriptor to the specified connection index */
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
|
||||
{
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocates a connection and inits it with specified file descriptor */
|
||||
struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collection, int fd)
|
||||
{
|
||||
struct connection* cnx = malloc(sizeof(*cnx));
|
||||
|
||||
if (!cnx) return NULL;
|
||||
|
||||
init_cnx(cnx);
|
||||
cnx->type = SOCK_STREAM;
|
||||
cnx->q[0].fd = fd;
|
||||
cnx->state = ST_PROBING;
|
||||
cnx->probe_timeout = time(NULL) + cfg.timeout;
|
||||
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
|
||||
return cnx;
|
||||
}
|
||||
|
||||
/* Remove a connection from the collection */
|
||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx)
|
||||
{
|
||||
if (cnx->q[0].fd != -1)
|
||||
gap_set(collection->fd2cnx, cnx->q[0].fd, NULL);
|
||||
if (cnx->q[1].fd != -1)
|
||||
gap_set(collection->fd2cnx, cnx->q[1].fd, NULL);
|
||||
free(cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns the connection that contains the file descriptor */
|
||||
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd)
|
||||
{
|
||||
return gap_get(collection->fd2cnx, fd);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef COLLECTION_H
|
||||
#define COLLECTION_H
|
||||
|
||||
typedef struct cnx_collection cnx_collection;
|
||||
|
||||
|
||||
cnx_collection* collection_init(int len);
|
||||
void collection_destroy(cnx_collection* collection);
|
||||
|
||||
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
|
||||
|
||||
/* Remove a connection from the collection */
|
||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
|
||||
|
||||
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd);
|
||||
|
||||
#endif
|
||||
@@ -70,7 +70,7 @@ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall
|
||||
}
|
||||
}
|
||||
|
||||
int get_fd_sockets(int *sockfd[])
|
||||
int get_fd_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
int sd = 0;
|
||||
|
||||
@@ -85,7 +85,8 @@ int get_fd_sockets(int *sockfd[])
|
||||
*sockfd = malloc(sd * sizeof(*sockfd[0]));
|
||||
CHECK_ALLOC(*sockfd, "malloc");
|
||||
for (i = 0; i < sd; i++) {
|
||||
(*sockfd)[i] = SD_LISTEN_FDS_START + i;
|
||||
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
|
||||
(*sockfd)[i].type = SOCK_STREAM;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -111,81 +112,94 @@ int make_listen_tfo(int s)
|
||||
return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen));
|
||||
}
|
||||
|
||||
/* Starts listening sockets on specified addresses.
|
||||
* IN: addr[], num_addr
|
||||
* OUT: *sockfd[] pointer to newly-allocated array of file descriptors
|
||||
* Returns number of addresses bound
|
||||
* Bound file descriptors are returned in newly-allocated *sockfd pointer
|
||||
*/
|
||||
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
|
||||
/* Starts listening on a single address
|
||||
* Returns a socket filehandle, or dies with message in case of major error */
|
||||
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
{
|
||||
struct sockaddr_storage *saddr;
|
||||
struct addrinfo *addr;
|
||||
int i, res, one;
|
||||
int num_addr = 0;
|
||||
int sd_socks = 0;
|
||||
struct sockaddr_storage *saddr;
|
||||
int sockfd, one, res;
|
||||
|
||||
sd_socks = get_fd_sockets(sockfd);
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
|
||||
if (sd_socks > 0) {
|
||||
return sd_socks;
|
||||
}
|
||||
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
check_res_dump(CR_DIE, sockfd, addr, "socket");
|
||||
|
||||
for (addr = addr_list; addr; addr = addr->ai_next)
|
||||
num_addr++;
|
||||
one = 1;
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
|
||||
if (num_addr == 0) {
|
||||
fprintf(stderr, "FATAL: No available addresses.\n");
|
||||
exit(1);
|
||||
}
|
||||
res = make_listen_tfo(sockfd);
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)");
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "listening to %d addresses\n", num_addr);
|
||||
if (keepalive) {
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
|
||||
}
|
||||
|
||||
*sockfd = malloc(num_addr * sizeof(*sockfd[0]));
|
||||
CHECK_ALLOC(*sockfd, "malloc");
|
||||
if (IP_FREEBIND) {
|
||||
res = setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
|
||||
}
|
||||
|
||||
for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
|
||||
if (!addr) {
|
||||
fprintf(stderr, "FATAL: Inconsistent listen number. This should not happen.\n");
|
||||
exit(1);
|
||||
}
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
|
||||
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
|
||||
check_res_dump(CR_DIE, (*sockfd)[i], addr, "socket");
|
||||
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(CR_DIE, res, addr, "bind");
|
||||
|
||||
one = 1;
|
||||
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
if (!udp) {
|
||||
res = listen (sockfd, 50);
|
||||
check_res_dump(CR_DIE, res, addr, "listen");
|
||||
}
|
||||
|
||||
res = make_listen_tfo((*sockfd)[i]);
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)");
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
if (addr->ai_flags & SO_KEEPALIVE) {
|
||||
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
|
||||
}
|
||||
/* 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;
|
||||
|
||||
if (IP_FREEBIND) {
|
||||
res = setsockopt((*sockfd)[i], IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
|
||||
}
|
||||
sd_socks = get_fd_sockets(sockfd);
|
||||
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt((*sockfd)[i], IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
if (sd_socks > 0) {
|
||||
return sd_socks;
|
||||
}
|
||||
|
||||
res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(CR_DIE, res, addr, "bind");
|
||||
*sockfd = NULL;
|
||||
|
||||
res = listen ((*sockfd)[i], 50);
|
||||
check_res_dump(CR_DIE, res, addr, "listen");
|
||||
if (cfg.verbose) fprintf(stderr, "Listening to:\n");
|
||||
|
||||
}
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
keepalive = cfg.listen[i].keepalive;
|
||||
udp = cfg.listen[i].is_udp;
|
||||
|
||||
return num_addr;
|
||||
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;
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "%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" : "");
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
|
||||
@@ -272,16 +286,34 @@ int bind_peer(int fd, int fd_from)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Make the file descriptor non-block */
|
||||
int set_nonblock(int fd)
|
||||
{
|
||||
int flags;
|
||||
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
flags |= O_NONBLOCK;
|
||||
|
||||
flags = fcntl(fd, F_SETFL, flags);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
/* 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)
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
struct addrinfo *a, from;
|
||||
struct sockaddr_storage ss;
|
||||
char buf[NI_MAXHOST];
|
||||
int fd, res, one;
|
||||
int transparent = cnx->proto->transparent || cfg.transparent;
|
||||
|
||||
memset(&from, 0, sizeof(from));
|
||||
from.ai_addr = (struct sockaddr*)&ss;
|
||||
@@ -292,7 +324,7 @@ int connect_addr(struct connection *cnx, int fd_from)
|
||||
|
||||
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
||||
/* When transparent, make sure both connections use the same address family */
|
||||
if (cfg.transparent && a->ai_family != from.ai_addr->sa_family)
|
||||
if (transparent && a->ai_family != from.ai_addr->sa_family)
|
||||
continue;
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "connecting to %s family %d len %d\n",
|
||||
@@ -309,29 +341,29 @@ int connect_addr(struct connection *cnx, int fd_from)
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(one));
|
||||
/* no need to check return value; if it's not supported, that's okay */
|
||||
|
||||
if (cfg.transparent) {
|
||||
if (blocking == NON_BLOCKING) {
|
||||
set_nonblock(fd);
|
||||
}
|
||||
|
||||
if (transparent) {
|
||||
res = bind_peer(fd, fd_from);
|
||||
CHECK_RES_RETURN(res, "bind_peer", res);
|
||||
}
|
||||
res = connect(fd, a->ai_addr, a->ai_addrlen);
|
||||
if (res == -1) {
|
||||
switch (errno) {
|
||||
case EINPROGRESS:
|
||||
/* Can't be done yet, or TFO already done */
|
||||
break;
|
||||
|
||||
default:
|
||||
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
|
||||
cnx->proto->name, strerror(errno));
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
if (cnx->proto->keepalive) {
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res);
|
||||
}
|
||||
return fd;
|
||||
/* EINPROGRESS indicates it might take time. If it eventually
|
||||
* fails, it'll be caught as a failed read */
|
||||
if ((res == -1) && (errno != EINPROGRESS)) {
|
||||
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
|
||||
cnx->proto->name, strerror(errno));
|
||||
close(fd);
|
||||
continue; /* Try the next address */
|
||||
}
|
||||
if (cnx->proto->keepalive) {
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
@@ -399,8 +431,10 @@ void init_cnx(struct connection *cnx)
|
||||
void dump_connection(struct connection *cnx)
|
||||
{
|
||||
printf("state: %d\n", cnx->state);
|
||||
printf("fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
||||
printf("fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
|
||||
printf("0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
||||
hexdump(cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
|
||||
printf("1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
|
||||
hexdump(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
|
||||
}
|
||||
|
||||
|
||||
@@ -451,7 +485,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
|
||||
case ECONNRESET:
|
||||
case EPIPE:
|
||||
/* remove end closed -- drop the connection */
|
||||
/* remote end closed -- drop the connection */
|
||||
return FD_CNXCLOSED;
|
||||
}
|
||||
} else if (size_w < size_r) {
|
||||
@@ -768,16 +802,33 @@ void set_keepcaps(int val) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* set needed capabilities for effective and permitted, clear rest */
|
||||
void set_capabilities(void) {
|
||||
/* Returns true if anything requires transparent proxying. */
|
||||
static int use_transparent(void)
|
||||
{
|
||||
#ifdef LIBCAP
|
||||
if (cfg.transparent)
|
||||
return 1;
|
||||
|
||||
for (int i = 0; i < cfg.protocols_len; i++)
|
||||
if (cfg.protocols[i].transparent)
|
||||
return 1;
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set needed capabilities for effective and permitted, clear rest
|
||||
* IN: cap_net_admin: set to 1 to set CAP_NET_RAW
|
||||
* */
|
||||
void set_capabilities(int cap_net_admin) {
|
||||
#ifdef LIBCAP
|
||||
int res;
|
||||
cap_t caps;
|
||||
cap_value_t cap_list[10];
|
||||
int ncap = 0;
|
||||
|
||||
if (cfg.transparent)
|
||||
cap_list[ncap++] = CAP_NET_ADMIN;
|
||||
if (cap_net_admin)
|
||||
cap_list[ncap++] = CAP_NET_RAW;
|
||||
|
||||
caps = cap_init();
|
||||
|
||||
@@ -845,7 +896,7 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
res = setuid(pw->pw_uid);
|
||||
CHECK_RES_DIE(res, "setuid");
|
||||
|
||||
set_capabilities();
|
||||
set_capabilities(use_transparent());
|
||||
set_keepcaps(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
#define CHECK_RES_DIE(res, str) \
|
||||
if (res == -1) { \
|
||||
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
|
||||
@@ -93,14 +96,35 @@ struct queue {
|
||||
};
|
||||
|
||||
struct connection {
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
struct sslhcfg_protocols_item* proto; /* Where to connect to */
|
||||
|
||||
/* SOCK_STREAM */
|
||||
enum connection_state state;
|
||||
time_t probe_timeout;
|
||||
struct sslhcfg_protocols_item* proto;
|
||||
|
||||
/* q[0]: queue for external connection (client);
|
||||
* q[1]: queue for internal connection (httpd or sshd);
|
||||
* */
|
||||
struct queue q[2];
|
||||
|
||||
/* SOCK_DGRAM */
|
||||
struct sockaddr client_addr; /* Contains the remote client address */
|
||||
socklen_t addrlen;
|
||||
|
||||
int local_endpoint; /* Contains the local address */
|
||||
|
||||
time_t last_active;
|
||||
|
||||
/* We need one local socket for each target server, so we know where to
|
||||
* forward server responses */
|
||||
int target_sock;
|
||||
};
|
||||
|
||||
|
||||
struct listen_endpoint {
|
||||
int socketfd; /* file descriptor of listening socket */
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
};
|
||||
|
||||
#define FD_CNXCLOSED 0
|
||||
@@ -114,10 +138,16 @@ struct connection_desc {
|
||||
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
NON_BLOCKING = 0,
|
||||
BLOCKING = 1
|
||||
} connect_blocking;
|
||||
|
||||
|
||||
/* common.c */
|
||||
void init_cnx(struct connection *cnx);
|
||||
int connect_addr(struct connection *cnx, int fd_from);
|
||||
int set_nonblock(int fd);
|
||||
int 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);
|
||||
@@ -128,24 +158,24 @@ int check_access_rights(int in_socket, const char* service);
|
||||
void setup_signals(void);
|
||||
void setup_syslog(const char* bin_name);
|
||||
void drop_privileges(const char* user_name, const char* chroot_path);
|
||||
void set_capabilities(int cap_net_admin);
|
||||
void write_pid_file(const char* pidfile);
|
||||
void log_message(int type, const char* msg, ...);
|
||||
void dump_connection(struct connection *cnx);
|
||||
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
||||
|
||||
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list);
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[]);
|
||||
|
||||
int defer_write(struct queue *q, void* data, int data_size);
|
||||
int flush_deferred(struct queue *q);
|
||||
|
||||
extern struct sslhcfg_item cfg;
|
||||
extern struct addrinfo *addr_listen;
|
||||
extern const char* USAGE_STRING;
|
||||
extern const char* server_type;
|
||||
|
||||
/* sslh-fork.c */
|
||||
void start_shoveler(int);
|
||||
|
||||
void main_loop(int *listen_sockets, int num_addr_listen);
|
||||
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
||||
|
||||
#endif
|
||||
+127
@@ -40,3 +40,130 @@ to:
|
||||
netstat -lpt
|
||||
```
|
||||
|
||||
I get a segmentation fault!
|
||||
===========================
|
||||
|
||||
Well, it's not yours (fault): a segfault is always a bug in
|
||||
the programme. Usually standard use cases are well tested,
|
||||
so it may be related to something unusual in your
|
||||
configuration, or even something wrong, but it should still
|
||||
never result in a segfault.
|
||||
|
||||
Thankfully, when they are deterministic, segfaults are
|
||||
usually fairly easy to fix if you're willing to run a few
|
||||
diagnostics to help the developer.
|
||||
|
||||
First, make sure you have debug symbols:
|
||||
```
|
||||
$ file sslh-select
|
||||
sslh-select: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a758ac75ff11f1ace577705b4d6627e301940b59, with debug_info, not stripped
|
||||
```
|
||||
|
||||
Note `with debug_info, not stripped` at the end. If you
|
||||
don't have that, your distribution stripped the binary: you
|
||||
will need to get the source code and compile it yourself
|
||||
(that way, you will also get the latest version).
|
||||
|
||||
Install `valgrind` and run `sslh` under it:
|
||||
|
||||
```
|
||||
valgrind --leak-check=full ./sslh-fork -v 2 -f -F yourconfig.cfg
|
||||
```
|
||||
|
||||
Report the full output to the mailing list or github.
|
||||
Valgrind is very powerful and gives precise hints of what is
|
||||
wrong and why. For example on `sslh` issue
|
||||
(#273)[https://github.com/yrutschle/sslh/issues/273]:
|
||||
|
||||
```
|
||||
sudo valgrind --leak-check=full ./sslh-fork -v 2 -f -F /etc/sslh.cfg
|
||||
==20037== Memcheck, a memory error detector
|
||||
==20037== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
|
||||
==20037== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
|
||||
==20037== Command: ./sslh-fork -v 2 -f -F /etc/sslh.cfg
|
||||
==20037==
|
||||
sslh-fork v1.21b-1-g2c93a01-dirty started
|
||||
--20037-- WARNING: unhandled arm-linux syscall: 403
|
||||
--20037-- You may be able to write your own handler.
|
||||
--20037-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
|
||||
--20037-- Nevertheless we consider this a bug. Please report
|
||||
--20037-- it at http://valgrind.org/support/bug_reports.html.
|
||||
==20040== Conditional jump or move depends on uninitialised value(s)
|
||||
==20040== at 0x112A3C: parse_tls_header (tls.c:162)
|
||||
==20040== by 0x111CEF: is_tls_protocol (probe.c:214)
|
||||
==20040== by 0x11239F: probe_client_protocol (probe.c:366)
|
||||
==20040== by 0x10A8F7: start_shoveler (sslh-fork.c:98)
|
||||
==20040== by 0x10AE9B: main_loop (sslh-fork.c:200)
|
||||
==20040== by 0x1114FB: main (sslh-main.c:322)
|
||||
==20040==
|
||||
```
|
||||
|
||||
Here we see that something wrong is happening at `tls.c`
|
||||
line 162, and it's linked to an uninitialised value.
|
||||
|
||||
Using sslh for virtual hosting
|
||||
==============================
|
||||
|
||||
Virtual hosting refers to having several domain names behind
|
||||
a single IP address. All Web servers handle this, but
|
||||
sometimes it can be useful to do it with `sslh`.
|
||||
|
||||
TLS virtual hosting with SNI
|
||||
----------------------------
|
||||
|
||||
For TLS, this is done very simply using Server Name
|
||||
Indication, SNI for short, which is a TLS extension whereby
|
||||
the client indicates the name of the server it wishes to
|
||||
connect to. This can be a very powerful way to separate
|
||||
several TLS-based services hosted behind the same port:
|
||||
simply name each service with its own hostname. For example,
|
||||
we could define `mail.rutschle.net`, `im.rutschle.net`,
|
||||
`www.rutschle.net`, all of which point to the same IP
|
||||
address. `sslh` uses the `sni_hostnames` setting of the
|
||||
TLS probe to do this, e.g.:
|
||||
|
||||
```
|
||||
protocols: (
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "993";
|
||||
sni_hostnames: [ "mail.rutschle.net" ];
|
||||
},
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "xmpp-client";
|
||||
sni_hostnames: [ "im.rutschle.net" ];
|
||||
},
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "4443";
|
||||
sni_hostnames: [ "www.rutschle.net" ];
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
HTTP virtual hosting with regex
|
||||
-------------------------------
|
||||
|
||||
If you wish to serve several Web domains over HTTP through
|
||||
`sslh`, you can do this simply by using regular expressions
|
||||
on the Host specification part of the HTTP query.
|
||||
|
||||
The following example forwards connections to `host_A.acme`
|
||||
to 192.168.0.2, and connections to `host_B.acme` to
|
||||
192.168.0.3.
|
||||
|
||||
```
|
||||
protocols: (
|
||||
{ name: "regex";
|
||||
host: "192.168.0.2";
|
||||
port: "80";
|
||||
regex_patterns:
|
||||
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_A.acme"] },
|
||||
{ name: "regex";
|
||||
host: "192.168.0.3";
|
||||
port: "80";
|
||||
regex_patterns:
|
||||
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_B.acme"] }
|
||||
);
|
||||
```
|
||||
+1
-1
@@ -42,7 +42,7 @@ For Fedora, you'll need packages `libconfig` and
|
||||
If you want to rebuild `sslh-conf.c` (after a `make
|
||||
distclean` for example), you will also need to add
|
||||
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||
(v1.1) to your path.
|
||||
(v1.5) to your path.
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
+22
-12
@@ -84,21 +84,17 @@ processes.
|
||||
Alternatively, you may use filesystem capabilities instead
|
||||
of starting sslh as root and asking it to drop privileges.
|
||||
You will need `CAP_NET_BIND_SERVICE` for listening on port 443
|
||||
and `CAP_NET_ADMIN` for transparent proxying (see
|
||||
and `CAP_NET_RAW` for transparent proxying (see
|
||||
`capabilities(7)`).
|
||||
|
||||
You can use the `setcap(8)` utility to give these capabilities
|
||||
to the executable:
|
||||
|
||||
sudo setcap cap_net_bind_service,cap_net_admin+pe sslh-select
|
||||
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
|
||||
|
||||
Then you can run sslh-select as an unpriviledged user, e.g.:
|
||||
|
||||
sslh-select -p myname:443 --ssh localhost:22 --ssl localhost:443
|
||||
|
||||
Caveat: `CAP_NET_ADMIN` does give sslh too many rights, e.g.
|
||||
configuring the interface. If you're not going to use
|
||||
transparent proxying, just don't use it (or use the libcap method).
|
||||
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
|
||||
|
||||
Transparent proxy support
|
||||
-------------------------
|
||||
@@ -126,7 +122,7 @@ but to the network setup that surrounds it.
|
||||
Linux:
|
||||
|
||||
`sslh` needs extended rights to perform this: you'll need to
|
||||
give it `CAP_NET_ADMIN` capabilities (see appropriate chapter)
|
||||
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
|
||||
or run it as root (but don't do that).
|
||||
|
||||
The firewalling tables also need to be adjusted as follows.
|
||||
@@ -233,11 +229,11 @@ This will only work if `sslh` does not use any loopback
|
||||
addresses (no `127.0.0.1` or `localhost`), you'll need to use
|
||||
explicit IP addresses (or names):
|
||||
|
||||
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --ssl 192.168.0.1:4443
|
||||
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
|
||||
|
||||
This will not work:
|
||||
|
||||
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4443
|
||||
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
|
||||
|
||||
Transparent proxying means the target server sees the real
|
||||
origin address, so it means if the client connects using
|
||||
@@ -284,9 +280,9 @@ Example service unit:
|
||||
PartOf=sslh.socket
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --ssl 127.0.0.1:443
|
||||
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --tls 127.0.0.1:443
|
||||
KillMode=process
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=full
|
||||
@@ -335,3 +331,17 @@ many connections to ssh from the same IP address...)
|
||||
|
||||
See example files in scripts/fail2ban.
|
||||
|
||||
UDP
|
||||
---
|
||||
|
||||
`sslh` can perform demultiplexing on UDP packets as well.
|
||||
This only works with `sslh-select` (it is not possible to
|
||||
support UDP with a forking model). Specify a listening
|
||||
address and target protocols with `is_udp: true`. `sslh`
|
||||
will wait for incoming UDP packets, run the probes in the
|
||||
usual fashion, and forward packets to the appropriate
|
||||
target. `sslh` will then remember the association between
|
||||
remote host to target server for 60 seconds by default,
|
||||
which can be overriden with `udp_timeout`. This allows to
|
||||
process both single-datagram protocols such as DNS, and
|
||||
connection-based protocols such as QUIC.
|
||||
+1307
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,66 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Fri Aug 13 18:03:20 2021.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef C2S_ECHOCFG_H
|
||||
#define C2S_ECHOCFG_H
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
struct echocfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
};
|
||||
|
||||
struct echocfg_item {
|
||||
int udp;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
struct echocfg_listen_item* listen;
|
||||
};
|
||||
|
||||
int echocfg_parse_file(
|
||||
const char* filename,
|
||||
struct echocfg_item* echocfg,
|
||||
const char** errmsg);
|
||||
|
||||
void echocfg_fprint(
|
||||
FILE* out,
|
||||
struct echocfg_item *echocfg,
|
||||
int depth);
|
||||
|
||||
int echocfg_cl_parse(
|
||||
int argc,
|
||||
char* argv[],
|
||||
struct echocfg_item *echocfg);
|
||||
|
||||
#endif
|
||||
@@ -27,8 +27,13 @@
|
||||
#include <syslog.h>
|
||||
#include <libgen.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define cfg sslhcfg
|
||||
#include "common.h"
|
||||
#undef cfg
|
||||
|
||||
#include "echosrv-conf.h"
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
* */
|
||||
@@ -36,60 +41,21 @@
|
||||
#define SA_NOCLDWAIT 0
|
||||
#endif
|
||||
|
||||
const char* USAGE_STRING =
|
||||
"echosrv\n" \
|
||||
"usage:\n" \
|
||||
"\techosrv [-v] --listen <address:port> [--prefix <prefix>]\n"
|
||||
"-v: verbose\n" \
|
||||
"--listen: address to listen on. Can be specified multiple times.\n" \
|
||||
"--prefix: add specified prefix before every line echoed.\n"
|
||||
"";
|
||||
struct echocfg_item cfg;
|
||||
|
||||
const char* server_type = "echsrv"; /* keep setup_syslog happy */
|
||||
|
||||
/*
|
||||
* Settings that depend on the command line.
|
||||
*/
|
||||
char* prefix = "";
|
||||
int port;
|
||||
|
||||
int verbose, numeric;
|
||||
|
||||
void parse_cmdline(int argc, char* argv[])
|
||||
void check_res_dump(int res, struct addrinfo *addr, char* syscall)
|
||||
{
|
||||
int c;
|
||||
struct option options[] = {
|
||||
{ "verbose", no_argument, &verbose, 1 },
|
||||
{ "numeric", no_argument, &numeric, 1 },
|
||||
{ "listen", required_argument, 0, 'l' },
|
||||
{ "prefix", required_argument, 0, 'p' },
|
||||
};
|
||||
struct addrinfo **a;
|
||||
char buf[NI_MAXHOST];
|
||||
|
||||
while ((c = getopt_long_only(argc, argv, "l:p:", options, NULL)) != -1) {
|
||||
if (c == 0) continue;
|
||||
if (res == -1) {
|
||||
if (addr)
|
||||
fprintf(stderr, "error %s:%s: %s\n",
|
||||
sprintaddr(buf, sizeof(buf), addr),
|
||||
syscall,
|
||||
strerror(errno));
|
||||
else
|
||||
fprintf(stderr, "Dying just because\n");
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'l':
|
||||
/* find the end of the listen list */
|
||||
for (a = &addr_listen; *a; a = &((*a)->ai_next));
|
||||
/* append the specified addresses */
|
||||
resolve_name(a, optarg);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
prefix = optarg;
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "%s", USAGE_STRING);
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (!addr_listen) {
|
||||
fprintf(stderr, "No listening port specified\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -99,19 +65,28 @@ void start_echo(int fd)
|
||||
int res;
|
||||
char buffer[1 << 20];
|
||||
int ret, prefix_len;
|
||||
int first = 1;
|
||||
|
||||
prefix_len = strlen(prefix);
|
||||
prefix_len = strlen(cfg.prefix);
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
strcpy(buffer, prefix);
|
||||
strcpy(buffer, cfg.prefix);
|
||||
|
||||
while (1) {
|
||||
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
|
||||
if (ret == -1) {
|
||||
if (ret <= 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
res = write(fd, buffer, ret + prefix_len);
|
||||
if (first) {
|
||||
res = write(fd, buffer, ret + prefix_len);
|
||||
first = 0;
|
||||
if (write(1, buffer, ret + prefix_len) < 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
res = write(fd, buffer + prefix_len, ret);
|
||||
}
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
return;
|
||||
@@ -119,30 +94,228 @@ void start_echo(int fd)
|
||||
}
|
||||
}
|
||||
|
||||
void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
/* TCP echo server: accepts connections to an endpoint, forks an echo for each
|
||||
* connection, forever. Prefix is added at start of response stream */
|
||||
void tcp_echo(struct listen_endpoint* listen_socket)
|
||||
{
|
||||
int in_socket, i;
|
||||
while (1) {
|
||||
int in_socket = accept(listen_socket->socketfd, 0, 0);
|
||||
CHECK_RES_DIE(in_socket, "accept");
|
||||
|
||||
if (!fork())
|
||||
{
|
||||
close(listen_socket->socketfd);
|
||||
start_echo(in_socket);
|
||||
exit(0);
|
||||
}
|
||||
close(in_socket);
|
||||
}
|
||||
}
|
||||
|
||||
void print_udp_xchange(int sockfd, struct sockaddr* addr, socklen_t addrlen)
|
||||
{
|
||||
struct addrinfo src_addrinfo, to_addrinfo;
|
||||
char str_addr[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
char str_addr2[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
struct sockaddr_storage ss;
|
||||
|
||||
src_addrinfo.ai_addr = (struct sockaddr*)&ss;
|
||||
src_addrinfo.ai_addrlen = sizeof(ss);
|
||||
getsockname(sockfd, src_addrinfo.ai_addr, &src_addrinfo.ai_addrlen);
|
||||
|
||||
to_addrinfo.ai_addr = addr;
|
||||
to_addrinfo.ai_addrlen = sizeof(*addr);
|
||||
|
||||
fprintf(stderr, "UDP local %s remote %s\n",
|
||||
sprintaddr(str_addr, sizeof(str_addr), &src_addrinfo),
|
||||
sprintaddr(str_addr2, sizeof(str_addr2), &to_addrinfo)
|
||||
);
|
||||
}
|
||||
|
||||
/* UDP echo server: receive packets, return them, forever.
|
||||
* Prefix is added at each packet */
|
||||
void udp_echo(struct listen_endpoint* listen_socket)
|
||||
{
|
||||
char data[65536];
|
||||
struct sockaddr src_addr;
|
||||
socklen_t addrlen;
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
size_t prefix_len = strlen(cfg.prefix);
|
||||
memcpy(data, cfg.prefix, prefix_len);
|
||||
|
||||
while (1) {
|
||||
addrlen = sizeof(src_addr);
|
||||
size_t len = recvfrom(listen_socket->socketfd,
|
||||
data + prefix_len,
|
||||
sizeof(data) - prefix_len,
|
||||
0,
|
||||
&src_addr,
|
||||
&addrlen);
|
||||
|
||||
if (len < 0) {
|
||||
perror("recvfrom");
|
||||
}
|
||||
*(data + prefix_len + len) = 0;
|
||||
fprintf(stderr, "%ld: %s\n", len, data + prefix_len);
|
||||
|
||||
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
|
||||
|
||||
int res = sendto(listen_socket->socketfd,
|
||||
data,
|
||||
len + prefix_len,
|
||||
0,
|
||||
&src_addr,
|
||||
addrlen);
|
||||
if (res < 0) {
|
||||
perror("sendto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
if (!fork()) {
|
||||
while (1)
|
||||
{
|
||||
in_socket = accept(listen_sockets[i], 0, 0);
|
||||
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
||||
|
||||
if (!fork())
|
||||
{
|
||||
close(listen_sockets[i]);
|
||||
start_echo(in_socket);
|
||||
exit(0);
|
||||
}
|
||||
close(in_socket);
|
||||
if (cfg.udp) {
|
||||
udp_echo(&listen_sockets[i]);
|
||||
} else {
|
||||
tcp_echo(&listen_sockets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
/* Following is a number of utility functions copied from common.c: linking
|
||||
* against common.o directly means echosrv has to work with sslh config struct,
|
||||
* which makes it all too awkward */
|
||||
|
||||
/* simplified from common.c */
|
||||
char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
{
|
||||
char host[NI_MAXHOST], serv[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
0 );
|
||||
|
||||
if (res) {
|
||||
/* Name resolution failed: do it numerically instead */
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
/* should not fail but... */
|
||||
if (res) {
|
||||
strcpy(host, "?");
|
||||
strcpy(serv, "?");
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(buf, size, "%s:%s", host, serv);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/* simplified from common.c */
|
||||
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
{
|
||||
struct sockaddr_storage *saddr;
|
||||
int sockfd, one, res;
|
||||
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
|
||||
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
check_res_dump(sockfd, addr, "socket");
|
||||
|
||||
one = 1;
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
|
||||
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(res, addr, "bind");
|
||||
|
||||
if (!udp) {
|
||||
res = listen (sockfd, 50);
|
||||
check_res_dump(res, addr, "listen");
|
||||
}
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/* simplified from common.c */
|
||||
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
{
|
||||
struct addrinfo hint;
|
||||
char *end;
|
||||
int res;
|
||||
|
||||
memset(&hint, 0, sizeof(hint));
|
||||
hint.ai_family = PF_UNSPEC;
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
|
||||
* around IP address */
|
||||
if (host[0] == '[') {
|
||||
end = strrchr(host, ']');
|
||||
if (!end) {
|
||||
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
|
||||
return -1;
|
||||
}
|
||||
host++; /* skip first bracket */
|
||||
*end = 0; /* remove last bracket */
|
||||
}
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, out);
|
||||
|
||||
if (res)
|
||||
fprintf(stderr, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
return res;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
*sockfd = NULL;
|
||||
|
||||
fprintf(stderr, "Listening to:\n");
|
||||
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
udp = cfg.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));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
fprintf(stderr, "%d:\t%s\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr));
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
@@ -150,11 +323,15 @@ int main(int argc, char *argv[])
|
||||
extern int optind;
|
||||
int num_addr_listen;
|
||||
|
||||
int *listen_sockets;
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
parse_cmdline(argc, argv);
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
if (echocfg_cl_parse(argc, argv, &cfg))
|
||||
exit(1);
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
|
||||
echocfg_fprint(stdout, &cfg, 0);
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||
|
||||
main_loop(listen_sockets, num_addr_listen);
|
||||
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# conf2struct for echosrv
|
||||
|
||||
header: "echosrv-conf.h";
|
||||
parser: "echosrv-conf.c";
|
||||
|
||||
printer: true;
|
||||
|
||||
conffile_option: ("F", "config");
|
||||
|
||||
config: {
|
||||
name: "echocfg",
|
||||
type: "list",
|
||||
items: (
|
||||
{name: "udp", type: "bool"; default: false; },
|
||||
{name: "prefix", type: "string"; },
|
||||
{ name: "listen",
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
cl_groups: (
|
||||
{ name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port";
|
||||
short: "p"; argdesc: "<host:port>";
|
||||
list: "listen";
|
||||
# no override, this just adds to the list (and thus can be specified several times)
|
||||
targets: (
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" }
|
||||
);
|
||||
}
|
||||
)
|
||||
+15
-1
@@ -25,7 +25,8 @@ syslog_facility: "auth";
|
||||
listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; },
|
||||
{ host: "thelonious"; port: "8080"; keepalive: true; }
|
||||
{ host: "thelonious"; port: "8080"; keepalive: true; },
|
||||
{ host: "thelonious"; is_udp: true; port: "443" }
|
||||
);
|
||||
|
||||
# List of protocols
|
||||
@@ -43,6 +44,9 @@ listen:
|
||||
# fork: Should a new process be forked for this protocol?
|
||||
# (only useful for sslh-select)
|
||||
# tfo_ok: Set to true if the server supports TCP FAST OPEN
|
||||
# transparent: Set to true to proxy this protocol
|
||||
# transparently (server sees the remote client IP
|
||||
# address). Same as the global option, but per-protocol
|
||||
#
|
||||
# Probe-specific options:
|
||||
# (sslh will try each probe in order they are declared, and
|
||||
@@ -89,6 +93,16 @@ protocols:
|
||||
# catch anything else TLS
|
||||
{ name: "tls"; host: "localhost"; port: "443"; tfo_ok: true },
|
||||
|
||||
# Forward UDP
|
||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "123";
|
||||
udp_timeout: 20; # Time after which the "connection" is forgotten
|
||||
regex_patterns: [ "hello" ]; },
|
||||
# Forward Teamspeak3 (Voice only)
|
||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "9987"; regex_patterns: [ "TS3INIT1" ]; },
|
||||
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
|
||||
# Remember that the regex needs to be adjusted for every supported QUIC version.
|
||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
|
||||
|
||||
# Regex examples -- better use the built-in probes for real-world use!
|
||||
# OpenVPN
|
||||
{ name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
gap.c: gap, a simple, dynamically-growing array
|
||||
of pointers that never shrinks
|
||||
|
||||
# Copyright (C) 2021 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
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sslh-conf.h"
|
||||
#include "gap.h"
|
||||
|
||||
|
||||
typedef struct gap_array {
|
||||
int len; /* Number of elements in array */
|
||||
void** array;
|
||||
} gap_array;
|
||||
|
||||
/* Allocate one page-worth of elements */
|
||||
static int gap_len_alloc(int elem_size)
|
||||
{
|
||||
return getpagesize() / elem_size;
|
||||
}
|
||||
|
||||
/* Creates a new gap at least `len` big, all pointers are initialised at NULL */
|
||||
gap_array* gap_init(int len)
|
||||
{
|
||||
gap_array* gap = malloc(sizeof(*gap));
|
||||
if (!gap) return NULL;
|
||||
memset(gap, 0, sizeof(*gap));
|
||||
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
gap->len = gap_len_alloc(elem_size);
|
||||
if (gap->len < len) gap->len = len;
|
||||
gap->array = malloc(gap->len * elem_size);
|
||||
if (!gap->array) return NULL;
|
||||
|
||||
for (int i = 0; i < gap->len; i++)
|
||||
gap->array[i] = NULL;
|
||||
|
||||
return gap;
|
||||
}
|
||||
|
||||
void* gap_get(gap_array* gap, int index)
|
||||
{
|
||||
return gap->array[index];
|
||||
}
|
||||
|
||||
static int gap_extend(gap_array* gap)
|
||||
{
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
int new_length = gap->len + gap_len_alloc(elem_size);
|
||||
void** new = realloc(gap->array, new_length * elem_size);
|
||||
if (!new) return -1;
|
||||
|
||||
gap->array = new;
|
||||
|
||||
for (int i = gap->len; i < new_length; i++) {
|
||||
gap->array[i] = NULL;
|
||||
}
|
||||
|
||||
gap->len = new_length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gap_set(gap_array* gap, int index, void* ptr)
|
||||
{
|
||||
while (index >= gap->len) {
|
||||
int res = gap_extend(gap);
|
||||
if (res == -1) return -1;
|
||||
}
|
||||
|
||||
gap->array[index] = ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gap_destroy(gap_array* gap)
|
||||
{
|
||||
free(gap->array);
|
||||
free(gap);
|
||||
}
|
||||
|
||||
|
||||
/* In gap, find element pointing to ptr, then shift the rest of the array that
|
||||
* is considered len elements long.
|
||||
* A poor man's list, if you will. Currently only used to remove probing
|
||||
* connections, so it only copies a few pointers at most.
|
||||
* Returns -1 if ptr was not found */
|
||||
int gap_remove_ptr(gap_array* gap, void* ptr, int len)
|
||||
{
|
||||
int start, i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (gap->array[i] == ptr)
|
||||
break;
|
||||
|
||||
if (i < len)
|
||||
start = i;
|
||||
else
|
||||
return -1;
|
||||
|
||||
for (i = start; i < len; i++) {
|
||||
gap->array[i] = gap->array[i+1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef GAP_H
|
||||
#define GAP_H
|
||||
|
||||
typedef struct gap_array gap_array;
|
||||
|
||||
gap_array* gap_init(int len);
|
||||
void* gap_get(gap_array* gap, int index);
|
||||
int gap_set(gap_array* gap, int index, void* ptr);
|
||||
void gap_destroy(gap_array* gap);
|
||||
|
||||
int gap_remove_ptr(gap_array* gap, void* ptr, int len);
|
||||
|
||||
#endif
|
||||
@@ -22,26 +22,24 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#ifdef ENABLE_REGEX
|
||||
#ifdef LIBPCRE
|
||||
#include <pcreposix.h>
|
||||
#else
|
||||
#include <regex.h>
|
||||
#endif
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include "probe.h"
|
||||
|
||||
|
||||
|
||||
static int is_ssh_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_openvpn_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_tinc_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_xmpp_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_http_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_tls_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_adb_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_socks5_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_true(const char *p, int len, struct sslhcfg_protocols_item* proto) { return 1; }
|
||||
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_openvpn_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_tinc_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_xmpp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_socks5_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_true(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { return 1; }
|
||||
|
||||
/* Table of protocols that have a built-in probe
|
||||
*/
|
||||
@@ -53,9 +51,9 @@ static struct protocol_probe_desc builtins[] = {
|
||||
{ "xmpp", is_xmpp_protocol },
|
||||
{ "http", is_http_protocol },
|
||||
{ "tls", is_tls_protocol },
|
||||
{ "ssl", is_tls_protocol },
|
||||
{ "adb", is_adb_protocol },
|
||||
{ "socks5", is_socks5_protocol },
|
||||
{ "syslog", is_syslog_protocol },
|
||||
{ "anyprot", is_true }
|
||||
};
|
||||
|
||||
@@ -115,7 +113,7 @@ void hexdump(const char *mem, unsigned int len)
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of an SSH connection? */
|
||||
static int is_ssh_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (len < 4)
|
||||
return PROBE_AGAIN;
|
||||
@@ -133,7 +131,7 @@ static int is_ssh_protocol(const char *p, int len, struct sslhcfg_protocols_item
|
||||
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
|
||||
* and OpenVPN ssl.c, ssl.h and options.c
|
||||
*/
|
||||
static int is_openvpn_protocol (const char*p,int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int packet_len;
|
||||
|
||||
@@ -148,7 +146,7 @@ static int is_openvpn_protocol (const char*p,int len, struct sslhcfg_protocols_i
|
||||
* Protocol is documented here: http://www.tinc-vpn.org/documentation/tinc.pdf
|
||||
* First connection starts with "0 " in 1.0.15)
|
||||
* */
|
||||
static int is_tinc_protocol( const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_tinc_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
@@ -160,7 +158,7 @@ static int is_tinc_protocol( const char *p, int len, struct sslhcfg_protocols_it
|
||||
* (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
|
||||
* clients, just checking first frame containing "jabber" in xml entity)
|
||||
* */
|
||||
static int is_xmpp_protocol( const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_xmpp_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (memmem(p, len, "jabber", 6))
|
||||
return PROBE_MATCH;
|
||||
@@ -183,7 +181,7 @@ static int probe_http_method(const char *p, int len, const char *opt)
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of an HTTP connection? */
|
||||
static int is_http_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res;
|
||||
/* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */
|
||||
@@ -209,7 +207,7 @@ static int is_http_protocol(const char *p, int len, struct sslhcfg_protocols_ite
|
||||
}
|
||||
|
||||
/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
|
||||
static int is_tls_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
switch (parse_tls_header(proto->data, p, len)) {
|
||||
case TLS_MATCH: return PROBE_MATCH;
|
||||
@@ -229,7 +227,7 @@ static int probe_adb_cnxn_message(const char *p)
|
||||
return !memcmp(&p[0], "CNXN", 4) && !memcmp(&p[24], "host:", 5);
|
||||
}
|
||||
|
||||
static int is_adb_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
/* amessage.data_length is not being checked, under the assumption that
|
||||
* a packet >= 30 bytes will have "something" in the payload field.
|
||||
@@ -268,7 +266,7 @@ static int is_adb_protocol(const char *p, int len, struct sslhcfg_protocols_item
|
||||
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
|
||||
}
|
||||
|
||||
static int is_socks5_protocol(const char *p_in, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
unsigned char* p = (unsigned char*)p_in;
|
||||
int i;
|
||||
@@ -301,16 +299,33 @@ static int is_socks5_protocol(const char *p_in, int len, struct sslhcfg_protocol
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
static int regex_probe(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res, i, j;
|
||||
|
||||
res = sscanf(p, "<%d>", &i);
|
||||
if (res == 1) return 1;
|
||||
|
||||
res = sscanf(p, "%d <%d>", &i, &j);
|
||||
if (res == 2) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
#ifdef ENABLE_REGEX
|
||||
regex_t **probe = proto->data;
|
||||
regmatch_t pos = { 0, len };
|
||||
pcre2_code**probe = (pcre2_code**)proto->data;
|
||||
pcre2_match_data* matches;
|
||||
|
||||
for (; *probe && regexec(*probe, p, 0, &pos, REG_STARTEND); probe++)
|
||||
/* try them all */;
|
||||
matches = pcre2_match_data_create(1, NULL);
|
||||
|
||||
return (*probe != NULL);
|
||||
for (; *probe; probe++) {
|
||||
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, matches, NULL);
|
||||
if (res >= 0) return 1;
|
||||
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
/* Should never happen as we check when loading config file */
|
||||
fprintf(stderr, "FATAL: regex probe called but not built in\n");
|
||||
@@ -318,33 +333,23 @@ static int regex_probe(const char *p, int len, struct sslhcfg_protocols_item* pr
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the beginning of data coming from the client connection and check if
|
||||
* it's a known protocol.
|
||||
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
|
||||
* which case cnx->proto is set to the appropriate protocol.
|
||||
*/
|
||||
int probe_client_protocol(struct connection *cnx)
|
||||
/* Run all the probes on a buffer
|
||||
* Returns
|
||||
* PROBE_AGAIN if not enough data, and set *proto to NULL
|
||||
* PROBE_MATCH if protocol is identified, in which case *proto is set to
|
||||
* point to the appropriate protocol
|
||||
* */
|
||||
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
struct sslhcfg_protocols_item* p;
|
||||
int i, n, res, again = 0;
|
||||
int i, res, again = 0;
|
||||
|
||||
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
|
||||
/* It's possible that read() returns an error, e.g. if the client
|
||||
* disconnected between the previous call to select() and now. If that
|
||||
* happens, we just connect to the default protocol so the caller of this
|
||||
* function does not have to deal with a specific failure condition (the
|
||||
* connection will just fail later normally). */
|
||||
|
||||
if (n > 0) {
|
||||
if (cfg.verbose > 1) {
|
||||
fprintf(stderr, "hexdump of incoming packet:\n");
|
||||
hexdump(buffer, n);
|
||||
}
|
||||
defer_write(&cnx->q[1], buffer, n);
|
||||
if (cfg.verbose > 1) {
|
||||
fprintf(stderr, "hexdump of incoming packet:\n");
|
||||
hexdump(buf, len);
|
||||
}
|
||||
|
||||
*proto = NULL;
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
||||
p = &cfg.protocols[i];
|
||||
@@ -357,26 +362,56 @@ int probe_client_protocol(struct connection *cnx)
|
||||
if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot")))
|
||||
break;
|
||||
|
||||
if (p->minlength_is_present && (cnx->q[1].deferred_data_size < p->minlength )) {
|
||||
fprintf(stderr, "input too short, %d bytes but need %d\n", cnx->q[1].deferred_data_size , p->minlength);
|
||||
if (p->minlength_is_present && (len < p->minlength )) {
|
||||
fprintf(stderr, "input too short, %d bytes but need %d\n", len , p->minlength);
|
||||
again++;
|
||||
continue;
|
||||
}
|
||||
|
||||
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
|
||||
res = p->probe(buf, len, p);
|
||||
if (cfg.verbose) fprintf(stderr, "probed for %s: %s\n", p->name, probe_str[res]);
|
||||
|
||||
if (res == PROBE_MATCH) {
|
||||
cnx->proto = p;
|
||||
*proto = p;
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
if (res == PROBE_AGAIN)
|
||||
again++;
|
||||
}
|
||||
if ((again && (n > 0)) || ((n == -1) && (errno == EAGAIN)))
|
||||
if (again)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
/* Everything failed: match the last one */
|
||||
*proto = &cfg.protocols[cfg.protocols_len-1];
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the beginning of data coming from the client connection and check if
|
||||
* it's a known protocol.
|
||||
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
|
||||
* which case cnx->proto is set to the appropriate protocol.
|
||||
*/
|
||||
int probe_client_protocol(struct connection *cnx)
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
ssize_t n;
|
||||
|
||||
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
|
||||
/* It's possible that read() returns an error, e.g. if the client
|
||||
* disconnected between the previous call to select() and now. If that
|
||||
* happens, we just connect to the default protocol so the caller of this
|
||||
* function does not have to deal with a specific failure condition (the
|
||||
* connection will just fail later normally). */
|
||||
|
||||
if (n > 0) {
|
||||
defer_write(&cnx->q[1], buffer, n);
|
||||
return probe_buffer(cnx->q[1].begin_deferred_data,
|
||||
cnx->q[1].deferred_data_size,
|
||||
&cnx->proto);
|
||||
}
|
||||
|
||||
/* read() returned an error, so just connect to the last protocol to die */
|
||||
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ typedef enum {
|
||||
} probe_result;
|
||||
|
||||
struct sslhcfg_protocols_item;
|
||||
typedef int T_PROBE(const char*, int, struct sslhcfg_protocols_item*);
|
||||
typedef int T_PROBE(const char*, ssize_t, struct sslhcfg_protocols_item*);
|
||||
|
||||
struct protocol_probe_desc {
|
||||
const char* name;
|
||||
@@ -47,6 +47,9 @@ void set_protocol_list(struct sslhcfg_protocols_item*);
|
||||
*/
|
||||
int probe_client_protocol(struct connection *cnx);
|
||||
|
||||
/* Probe, but on a buffer */
|
||||
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto);
|
||||
|
||||
/* set the protocol to connect to in case of timeout */
|
||||
void set_ontimeout(const char* name);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# is needed in order to run as sslh user
|
||||
#
|
||||
#SSLH_USER=sslh
|
||||
#setcap cap_net_bind_service,cap_net_admin=+ep $SSLH
|
||||
#setcap cap_net_bind_service,cap_net_raw=+ep $SSLH
|
||||
|
||||
#
|
||||
# Configuration file for sslh
|
||||
|
||||
@@ -8,7 +8,7 @@ ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
SecureBits=noroot-locked
|
||||
ProtectSystem=strict
|
||||
|
||||
+593
-89
File diff suppressed because it is too large.
Load diff
+11
-3
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Fri Jun 19 23:36:04 2020.
|
||||
* on Tue Jan 2 21:42:30 2024.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2019 Yves Rutschle
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -30,7 +30,9 @@
|
||||
|
||||
#ifndef C2S_SSLHCFG_H
|
||||
#define C2S_SSLHCFG_H
|
||||
#include <libconfig.h>
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "probe.h"
|
||||
@@ -41,6 +43,7 @@
|
||||
struct sslhcfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
int is_udp;
|
||||
int keepalive;
|
||||
};
|
||||
|
||||
@@ -50,8 +53,11 @@ struct sslhcfg_protocols_item {
|
||||
char* port;
|
||||
int service_is_present;
|
||||
char* service;
|
||||
int is_udp;
|
||||
int udp_timeout;
|
||||
int fork;
|
||||
int tfo_ok;
|
||||
int transparent;
|
||||
int log_level;
|
||||
int keepalive;
|
||||
size_t sni_hostnames_len;
|
||||
@@ -69,6 +75,7 @@ struct sslhcfg_protocols_item {
|
||||
|
||||
struct sslhcfg_item {
|
||||
int verbose;
|
||||
int version;
|
||||
int foreground;
|
||||
int inetd;
|
||||
int numeric;
|
||||
@@ -82,6 +89,7 @@ struct sslhcfg_item {
|
||||
char* chroot;
|
||||
char* syslog_facility;
|
||||
char* on_timeout;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
struct sslhcfg_listen_item* listen;
|
||||
size_t protocols_len;
|
||||
|
||||
+57
-37
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
sslh-fork: forking server
|
||||
|
||||
# Copyright (C) 2007-2012 Yves Rutschle
|
||||
# Copyright (C) 2007-2021 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "udp-listener.h"
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
@@ -29,8 +31,6 @@
|
||||
|
||||
const char* server_type = "sslh-fork";
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
/* shovels data from one fd to the other and vice-versa
|
||||
returns after one socket closed
|
||||
*/
|
||||
@@ -57,7 +57,7 @@ int shovel(struct connection *cnx)
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (FD_ISSET(cnx->q[i].fd, &fds)) {
|
||||
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
||||
if (!res) {
|
||||
if (res == FD_CNXCLOSED) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
|
||||
return res;
|
||||
@@ -111,9 +111,11 @@ void start_shoveler(int in_socket)
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(&cnx, in_socket);
|
||||
out_socket = connect_addr(&cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(out_socket, "connect");
|
||||
|
||||
set_capabilities(0);
|
||||
|
||||
cnx.q[1].fd = out_socket;
|
||||
|
||||
get_connection_desc(&desc, &cnx);
|
||||
@@ -133,7 +135,7 @@ void start_shoveler(int in_socket)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int *listener_pid;
|
||||
static pid_t *listener_pid;
|
||||
static int listener_pid_number = 0;
|
||||
|
||||
void stop_listeners(int sig)
|
||||
@@ -145,7 +147,7 @@ void stop_listeners(int sig)
|
||||
}
|
||||
}
|
||||
|
||||
void set_listen_procname(int listen_socket)
|
||||
void set_listen_procname(struct listen_endpoint *listen_socket)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
int res;
|
||||
@@ -155,7 +157,7 @@ void set_listen_procname(int listen_socket)
|
||||
|
||||
addr.ai_addr = (struct sockaddr*)&ss;
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
res = getsockname(listen_socket, addr.ai_addr, &addr.ai_addrlen);
|
||||
res = getsockname(listen_socket->socketfd, addr.ai_addr, &addr.ai_addrlen);
|
||||
if (res != -1) {
|
||||
sprintaddr(listen_addr, sizeof(listen_addr), &addr);
|
||||
setproctitle("listener %s", listen_addr);
|
||||
@@ -163,9 +165,44 @@ void set_listen_procname(int listen_socket)
|
||||
#endif
|
||||
}
|
||||
|
||||
void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
|
||||
/* TCP listener: connections, fork a child for each new connection
|
||||
* IN:
|
||||
* endpoint: array of listening endpoint objects
|
||||
* num_endpoints: size of endpoint array
|
||||
* active_endpoint: which endpoint is this listener working on
|
||||
* Does not return
|
||||
* */
|
||||
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
|
||||
{
|
||||
int in_socket, i, res;
|
||||
int i, in_socket;
|
||||
|
||||
while (1) {
|
||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||
if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
||||
|
||||
switch(fork()) {
|
||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
|
||||
case 0: /* In child process */
|
||||
/* Shoveler processes don't need to hog file descriptors */
|
||||
for (i = 0; i < num_endpoints; i++)
|
||||
close(endpoint[i].socketfd);
|
||||
start_shoveler(in_socket);
|
||||
exit(0);
|
||||
|
||||
default: /* In parent process */
|
||||
break;
|
||||
}
|
||||
close(in_socket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
int i, res;
|
||||
struct sigaction action;
|
||||
|
||||
listener_pid_number = num_addr_listen;
|
||||
@@ -176,37 +213,19 @@ void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
listener_pid[i] = fork();
|
||||
switch(listener_pid[i]) {
|
||||
// Log if fork() fails for some reason
|
||||
/* Log if fork() fails for some reason */
|
||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
// We're in the child, we have work to do
|
||||
/* We're in the child, we have work to do */
|
||||
case 0:
|
||||
/* Listening process just accepts a connection, forks, and goes
|
||||
* back to listening */
|
||||
set_listen_procname(listen_sockets[i]);
|
||||
while (1)
|
||||
{
|
||||
in_socket = accept(listen_sockets[i], 0, 0);
|
||||
if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
||||
|
||||
switch(fork()) {
|
||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
|
||||
/* In child process */
|
||||
case 0:
|
||||
for (i = 0; i < num_addr_listen; ++i)
|
||||
close(listen_sockets[i]);
|
||||
start_shoveler(in_socket);
|
||||
exit(0);
|
||||
|
||||
/* In parent process */
|
||||
default: break;
|
||||
}
|
||||
close(in_socket);
|
||||
}
|
||||
set_listen_procname(&listen_sockets[i]);
|
||||
if (listen_sockets[i].type == SOCK_DGRAM)
|
||||
log_message(LOG_ERR, "UDP not (yet?) supported in sslh-fork\n");
|
||||
else
|
||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||
break;
|
||||
// We're in the parent, we don't need to do anything
|
||||
|
||||
/* We're in the parent, we don't need to do anything */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -220,6 +239,7 @@ void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
res = sigaction(SIGTERM, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
|
||||
+33
-112
@@ -26,11 +26,8 @@
|
||||
#include <libconfig.h>
|
||||
#endif
|
||||
#ifdef ENABLE_REGEX
|
||||
#ifdef LIBPCRE
|
||||
#include <pcreposix.h>
|
||||
#else
|
||||
#include <regex.h>
|
||||
#endif
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
@@ -40,29 +37,6 @@
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
|
||||
const char* USAGE_STRING =
|
||||
"sslh " VERSION "\n" \
|
||||
"usage:\n" \
|
||||
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F<file>]\n"
|
||||
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <addr> [-p <addr> ...] \n" \
|
||||
"%s\n\n" /* Dynamically built list of builtin protocols */ \
|
||||
"\t[--on-timeout <addr>]\n" \
|
||||
"-v: verbose\n" \
|
||||
"-V: version\n" \
|
||||
"-f: foreground\n" \
|
||||
"-n: numeric output\n" \
|
||||
"-u: specify under which user to run\n" \
|
||||
"-C: specify under which chroot path to run\n" \
|
||||
"--transparent: behave as a transparent proxy\n" \
|
||||
"-F: use configuration file (warning: no space between -F and file name!)\n" \
|
||||
"--on-timeout: connect to specified address upon timeout (default: ssh address)\n" \
|
||||
"-t: seconds to wait before connecting to --on-timeout address.\n" \
|
||||
"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
|
||||
"--[ssh,ssl,...]: where to connect connections from corresponding protocol.\n" \
|
||||
"-P: PID file.\n" \
|
||||
"-i: Run as a inetd service.\n" \
|
||||
"";
|
||||
|
||||
/* Constants for options that have no one-character shorthand */
|
||||
#define OPT_ONTIMEOUT 257
|
||||
|
||||
@@ -86,14 +60,13 @@ static void printcaps(void) {
|
||||
static void printsettings(void)
|
||||
{
|
||||
char buf[NI_MAXHOST];
|
||||
struct addrinfo *a;
|
||||
int i;
|
||||
struct sslhcfg_protocols_item *p;
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||
p = &cfg.protocols[i];
|
||||
fprintf(stderr,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s]\n",
|
||||
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s] [%s]\n",
|
||||
p->name,
|
||||
sprintaddr(buf, sizeof(buf), p->saddr),
|
||||
p->service,
|
||||
@@ -101,80 +74,22 @@ static void printsettings(void)
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family,
|
||||
p->keepalive ? "keepalive" : "",
|
||||
p->fork ? "fork" : "");
|
||||
}
|
||||
fprintf(stderr, "listening on:\n");
|
||||
for (a = addr_listen; a; a = a->ai_next) {
|
||||
fprintf(stderr,
|
||||
"\t%s\t[%s]\n",
|
||||
sprintaddr(buf, sizeof(buf), a),
|
||||
a->ai_flags & SO_KEEPALIVE ? "keepalive" : "");
|
||||
p->fork ? "fork" : "",
|
||||
p->transparent ? "transparent" : ""
|
||||
);
|
||||
}
|
||||
fprintf(stderr, "timeout: %d\non-timeout: %s\n", cfg.timeout,
|
||||
timeout_protocol()->name);
|
||||
}
|
||||
|
||||
|
||||
/* To be removed in v1.21 */
|
||||
const char* ssl_err_msg = "Usage of 'ssl' setting is deprecated and will be removed in v1.21. Please use 'tls' instead\n";
|
||||
void ssl_to_tls(char* setting)
|
||||
{
|
||||
if (!strcmp(setting, "ssl")) {
|
||||
strcpy(setting, "tls"); /* legacy configuration */
|
||||
log_message(LOG_INFO, ssl_err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Turn 'ssl' command line option to 'tls'. To be removed in v1.21 */
|
||||
void cmd_ssl_to_tls(int argc, char* argv[])
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--ssl")) {
|
||||
strcpy(argv[i], "--tls");
|
||||
/* foreground option not parsed yet, syslog not open, just print on
|
||||
* stderr and hope for the best */
|
||||
fprintf(stderr, "%s", ssl_err_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Extract configuration on addresses and ports on which to listen.
|
||||
* out: newly allocated list of addrinfo to listen to
|
||||
*/
|
||||
#ifdef LIBCONFIG
|
||||
static int config_resolve_listen(struct addrinfo **listen)
|
||||
{
|
||||
int i, res;
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
res = resolve_split_name(listen, cfg.listen[i].host, cfg.listen[i].port);
|
||||
if (res) return res;
|
||||
|
||||
/* getaddrinfo returned a list of addresses corresponding to the
|
||||
* specification; move the pointer to the end of that list before
|
||||
* processing the next specification, while setting flags for
|
||||
* start_listen_sockets() through ai_flags (which is not meant for
|
||||
* that, but is only used as hint in getaddrinfo, so it's OK) */
|
||||
for (; *listen; listen = &((*listen)->ai_next)) {
|
||||
if (cfg.listen[i].keepalive)
|
||||
(*listen)->ai_flags = SO_KEEPALIVE;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#ifdef LIBCONFIG
|
||||
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
#ifdef ENABLE_REGEX
|
||||
{
|
||||
int num_patterns, i, res;
|
||||
regex_t** pattern_list;
|
||||
size_t errsize;
|
||||
char* err;
|
||||
int num_patterns, i, error;
|
||||
pcre2_code** pattern_list;
|
||||
PCRE2_SIZE error_offset;
|
||||
PCRE2_UCHAR8 err_str[120];
|
||||
|
||||
num_patterns = p->regex_patterns_len;
|
||||
|
||||
@@ -183,19 +98,21 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
p->data = (void*)pattern_list;
|
||||
|
||||
for (i = 0; i < num_patterns; i++) {
|
||||
pattern_list[i] = malloc(sizeof(*(pattern_list[i])));
|
||||
CHECK_ALLOC(pattern_list[i], "malloc");
|
||||
res = regcomp(pattern_list[i], p->regex_patterns[i], REG_EXTENDED);
|
||||
if (res) {
|
||||
err = malloc(errsize = regerror(res, pattern_list[i], NULL, 0));
|
||||
CHECK_ALLOC(err, "malloc");
|
||||
regerror(res, pattern_list[i], err, errsize);
|
||||
fprintf(stderr, "%s:%s\n", p->regex_patterns[i], err);
|
||||
free(err);
|
||||
pattern_list[i] = pcre2_compile((PCRE2_SPTR8)p->regex_patterns[i],
|
||||
PCRE2_ZERO_TERMINATED, 0,
|
||||
&error, &error_offset, NULL);
|
||||
if (!pattern_list[i]) {
|
||||
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
||||
fprintf(stderr, "compiling pattern /%s/:%d:%s at offset %ld\n",
|
||||
p->regex_patterns[i], error, err_str, error_offset);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* For each protocol in the configuration, resolve address and set up protocol
|
||||
@@ -208,7 +125,7 @@ static void config_protocols()
|
||||
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
|
||||
if (resolve_split_name(&(p->saddr), p->host, p->port)) {
|
||||
fprintf(stderr, "cannot resolve %s:%s\n", p->host, p->port);
|
||||
exit(1);
|
||||
exit(4);
|
||||
}
|
||||
|
||||
p->probe = get_probe(p->name);
|
||||
@@ -244,7 +161,7 @@ void config_sanity_check(struct sslhcfg_item* cfg) {
|
||||
|
||||
/* If compiling with systemd socket support no need to require listen address */
|
||||
#ifndef SYSTEMD
|
||||
if (!addr_listen && !cfg->inetd) {
|
||||
if (!cfg->listen_len && !cfg->inetd) {
|
||||
fprintf(stderr, "No listening address specified; use at least one -p option\n");
|
||||
exit(1);
|
||||
}
|
||||
@@ -258,7 +175,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int res, num_addr_listen;
|
||||
int *listen_sockets;
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
#ifdef LIBBSD
|
||||
setproctitle_init(argc, argv, envp);
|
||||
@@ -266,11 +183,15 @@ int main(int argc, char *argv[], char* envp[])
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
||||
if (!res) exit(1);
|
||||
if (res) exit(6);
|
||||
if (cfg.verbose > 3)
|
||||
sslhcfg_fprint(stderr, &cfg, 0);
|
||||
res = config_resolve_listen(&addr_listen);
|
||||
if (res) exit(4);
|
||||
|
||||
if (cfg.version) {
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
config_protocols();
|
||||
config_sanity_check(&cfg);
|
||||
|
||||
@@ -284,7 +205,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
if (cfg.verbose)
|
||||
printsettings();
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||
|
||||
#ifdef SYSTEMD
|
||||
if (num_addr_listen < 1) {
|
||||
|
||||
+347
-206
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
sslh-select: mono-processus server
|
||||
|
||||
# Copyright (C) 2007-2010 Yves Rutschle
|
||||
# Copyright (C) 2007-2021 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
@@ -20,43 +20,50 @@
|
||||
|
||||
*/
|
||||
|
||||
/* Why use select(2) rather than poll(2)?
|
||||
* No real reason except that's how it was written at first. This article:
|
||||
* https://daniel.haxx.se/docs/poll-vs-select.html suggests that over a few
|
||||
* hundred file descriptors, both become very slow, so there is little
|
||||
* incentive to move to poll() to support more than FD_SETSIZE (which is 1024
|
||||
* on many Linux. To support large numbers of descriptors, either use the fork
|
||||
* version, or we'll have to write a new version based on libev. */
|
||||
|
||||
#define __LINUX__
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "udp-listener.h"
|
||||
#include "collection.h"
|
||||
#include "gap.h"
|
||||
|
||||
static int debug = 0;
|
||||
|
||||
const char* server_type = "sslh-select";
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
/* Global state for a select() loop */
|
||||
struct select_info {
|
||||
int max_fd; /* Highest fd number to pass to select() */
|
||||
|
||||
/* cnx_num_alloc is the number of connection to allocate at once (at start-up,
|
||||
* and then every time we get too many simultaneous connections: e.g. start
|
||||
* with 100 slots, then if we get more than 100 connections allocate another
|
||||
* 100 slots, and so on). We never free up connection structures. We try to
|
||||
* allocate as many structures at once as will fit in one page (which is 102
|
||||
* in sslh 1.9 on Linux on x86)
|
||||
*/
|
||||
static long cnx_num_alloc;
|
||||
int num_probing; /* Number of connections currently probing
|
||||
* We use this to know if we need to time out of
|
||||
* select() */
|
||||
gap_array* probing_list; /* Pointers to cnx that are in probing mode */
|
||||
|
||||
/* Make the file descriptor non-block */
|
||||
int set_nonblock(int fd)
|
||||
{
|
||||
int flags;
|
||||
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
|
||||
cnx_collection* collection; /* Collection of connections linked to this loop */
|
||||
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
time_t next_timeout; /* time at which next UDP connection times out */
|
||||
};
|
||||
|
||||
flags |= O_NONBLOCK;
|
||||
|
||||
flags = fcntl(fd, F_SETFL, flags);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
|
||||
static int tidy_connection(struct connection *cnx, struct select_info* fd_info)
|
||||
{
|
||||
int i;
|
||||
fd_set* fds = &fd_info->fds_r;
|
||||
fd_set* fds2 = &fd_info->fds_w;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (cnx->q[i].fd != -1) {
|
||||
@@ -70,13 +77,13 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
|
||||
free(cnx->q[i].deferred_data);
|
||||
}
|
||||
}
|
||||
init_cnx(cnx);
|
||||
collection_remove_cnx(fd_info->collection, cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
|
||||
* and FD_CLR. Need to drop connections if we go above that limit */
|
||||
int fd_is_in_range(int fd) {
|
||||
static int fd_is_in_range(int fd) {
|
||||
if (fd >= FD_SETSIZE) {
|
||||
log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n");
|
||||
return 0;
|
||||
@@ -87,73 +94,56 @@ int fd_is_in_range(int fd) {
|
||||
/* Accepts a connection from the main socket and assigns it to an empty slot.
|
||||
* If no slots are available, allocate another few. If that fails, drop the
|
||||
* connexion */
|
||||
int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_size)
|
||||
static struct connection* accept_new_connection(int listen_socket, struct cnx_collection *collection)
|
||||
{
|
||||
int in_socket, free, i, res;
|
||||
struct connection *new;
|
||||
int in_socket, res;
|
||||
|
||||
|
||||
if (cfg.verbose) fprintf(stderr, "accepting from %d\n", listen_socket);
|
||||
|
||||
in_socket = accept(listen_socket, 0, 0);
|
||||
CHECK_RES_RETURN(in_socket, "accept", -1);
|
||||
CHECK_RES_RETURN(in_socket, "accept", NULL);
|
||||
|
||||
if (!fd_is_in_range(in_socket)) {
|
||||
close(in_socket);
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = set_nonblock(in_socket);
|
||||
if (res == -1) {
|
||||
close(in_socket);
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find an empty slot */
|
||||
for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) {
|
||||
/* nothing */
|
||||
struct connection* cnx = collection_alloc_cnx_from_fd(collection, in_socket);
|
||||
if (!cnx) {
|
||||
close(in_socket);
|
||||
return NULL;
|
||||
}
|
||||
if (free >= *cnx_size) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "buying more slots from the slot machine.\n");
|
||||
new = realloc(*cnx, (*cnx_size + cnx_num_alloc) * sizeof((*cnx)[0]));
|
||||
if (!new) {
|
||||
log_message(LOG_ERR, "unable to realloc -- dropping connection\n");
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
*cnx = new;
|
||||
*cnx_size += cnx_num_alloc;
|
||||
for (i = free; i < *cnx_size; i++) {
|
||||
init_cnx(&(*cnx)[i]);
|
||||
}
|
||||
}
|
||||
(*cnx)[free].q[0].fd = in_socket;
|
||||
(*cnx)[free].state = ST_PROBING;
|
||||
(*cnx)[free].probe_timeout = time(NULL) + cfg.timeout;
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "accepted fd %d on slot %d\n", in_socket, free);
|
||||
|
||||
return in_socket;
|
||||
return cnx;
|
||||
}
|
||||
|
||||
|
||||
/* Connect queue 1 of connection to SSL; returns new file descriptor */
|
||||
int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
|
||||
static int connect_queue(struct connection* cnx,
|
||||
struct select_info* fd_info)
|
||||
{
|
||||
struct queue *q = &cnx->q[1];
|
||||
|
||||
q->fd = connect_addr(cnx, cnx->q[0].fd);
|
||||
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
||||
if ((q->fd != -1) && fd_is_in_range(q->fd)) {
|
||||
log_connection(NULL, cnx);
|
||||
set_nonblock(q->fd);
|
||||
flush_deferred(q);
|
||||
if (q->deferred_data) {
|
||||
FD_SET(q->fd, fds_w);
|
||||
FD_CLR(cnx->q[0].fd, fds_r);
|
||||
FD_SET(q->fd, &fd_info->fds_w);
|
||||
FD_CLR(cnx->q[0].fd, &fd_info->fds_r);
|
||||
}
|
||||
FD_SET(q->fd, fds_r);
|
||||
FD_SET(q->fd, &fd_info->fds_r);
|
||||
collection_add_fd(fd_info->collection, cnx, q->fd);
|
||||
return q->fd;
|
||||
} else {
|
||||
tidy_connection(cnx, fds_r, fds_w);
|
||||
tidy_connection(cnx, fd_info);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -161,8 +151,7 @@ int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
|
||||
/* shovels data from active fd to the other
|
||||
returns after one socket closed or operation would block
|
||||
*/
|
||||
void shovel(struct connection *cnx, int active_fd,
|
||||
fd_set *fds_r, fd_set *fds_w)
|
||||
static void shovel(struct connection *cnx, int active_fd, struct select_info* fd_info)
|
||||
{
|
||||
struct queue *read_q, *write_q;
|
||||
|
||||
@@ -175,12 +164,12 @@ void shovel(struct connection *cnx, int active_fd,
|
||||
switch(fd2fd(write_q, read_q)) {
|
||||
case -1:
|
||||
case FD_CNXCLOSED:
|
||||
tidy_connection(cnx, fds_r, fds_w);
|
||||
tidy_connection(cnx, fd_info);
|
||||
break;
|
||||
|
||||
case FD_STALLED:
|
||||
FD_SET(write_q->fd, fds_w);
|
||||
FD_CLR(read_q->fd, fds_r);
|
||||
FD_SET(write_q->fd, &fd_info->fds_w);
|
||||
FD_CLR(read_q->fd, &fd_info->fds_r);
|
||||
break;
|
||||
|
||||
default: /* Nothing */
|
||||
@@ -191,7 +180,7 @@ void shovel(struct connection *cnx, int active_fd,
|
||||
/* shovels data from one fd to the other and vice-versa
|
||||
returns after one socket closed
|
||||
*/
|
||||
void shovel_single(struct connection *cnx)
|
||||
static void shovel_single(struct connection *cnx)
|
||||
{
|
||||
fd_set fds_r, fds_w;
|
||||
int res, i;
|
||||
@@ -242,7 +231,7 @@ void shovel_single(struct connection *cnx)
|
||||
|
||||
/* Child process that makes internal connection and proxies
|
||||
*/
|
||||
void connect_proxy(struct connection *cnx)
|
||||
static void connect_proxy(struct connection *cnx)
|
||||
{
|
||||
int in_socket;
|
||||
int out_socket;
|
||||
@@ -257,7 +246,7 @@ void connect_proxy(struct connection *cnx)
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(cnx, in_socket);
|
||||
out_socket = connect_addr(cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(out_socket, "connect");
|
||||
|
||||
cnx->q[1].fd = out_socket;
|
||||
@@ -275,11 +264,243 @@ void connect_proxy(struct connection *cnx)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* returns true if specified fd is initialised and present in fd_set */
|
||||
int is_fd_active(int fd, fd_set* set)
|
||||
/* Removes cnx from probing list */
|
||||
static void remove_probing_cnx(struct select_info* fd_info, struct connection* cnx)
|
||||
{
|
||||
if (fd == -1) return 0;
|
||||
return FD_ISSET(fd, set);
|
||||
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
|
||||
fd_info->num_probing--;
|
||||
}
|
||||
|
||||
static void add_probing_cnx(struct select_info* fd_info, struct connection* cnx)
|
||||
{
|
||||
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
|
||||
fd_info->num_probing++;
|
||||
}
|
||||
|
||||
|
||||
/* Process read activity on a socket in probe state
|
||||
* IN/OUT cnx: connection data, updated if connected
|
||||
* IN/OUT info: updated if connected
|
||||
* */
|
||||
|
||||
static void probing_read_process(struct connection* cnx,
|
||||
struct select_info* fd_info)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* If timed out it's SSH, otherwise the client sent
|
||||
* data so probe the protocol */
|
||||
if ((cnx->probe_timeout < time(NULL))) {
|
||||
cnx->proto = timeout_protocol();
|
||||
if (cfg.verbose)
|
||||
log_message(LOG_INFO,
|
||||
"timed out, connect to %s\n",
|
||||
cnx->proto->name);
|
||||
} else {
|
||||
res = probe_client_protocol(cnx);
|
||||
if (res == PROBE_AGAIN)
|
||||
return;
|
||||
}
|
||||
|
||||
remove_probing_cnx(fd_info, cnx);
|
||||
cnx->state = ST_SHOVELING;
|
||||
|
||||
/* libwrap check if required for this protocol */
|
||||
if (cnx->proto->service &&
|
||||
check_access_rights(cnx->q[0].fd, cnx->proto->service)) {
|
||||
tidy_connection(cnx, fd_info);
|
||||
res = -1;
|
||||
} else if (cnx->proto->fork) {
|
||||
switch (fork()) {
|
||||
case 0: /* child */
|
||||
/* TODO: close all file descriptors except 2 */
|
||||
/* free(cnx); */
|
||||
connect_proxy(cnx);
|
||||
exit(0);
|
||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
default: /* parent */
|
||||
break;
|
||||
}
|
||||
tidy_connection(cnx, fd_info);
|
||||
res = -1;
|
||||
} else {
|
||||
res = connect_queue(cnx, fd_info);
|
||||
}
|
||||
|
||||
if (res >= fd_info->max_fd)
|
||||
fd_info->max_fd = res + 1;;
|
||||
}
|
||||
|
||||
|
||||
/* Returns the queue index that contains the specified file descriptor */
|
||||
int active_queue(struct connection* cnx, int fd)
|
||||
{
|
||||
if (cnx->q[0].fd == fd) return 0;
|
||||
if (cnx->q[1].fd == fd) return 1;
|
||||
|
||||
log_message(LOG_ERR, "file descriptor %d not found in connection object\n", fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Process a connection that is active in read */
|
||||
static void tcp_read_process(struct select_info* fd_info,
|
||||
int fd)
|
||||
{
|
||||
if (debug) fprintf(stderr, "cnx_read_process fd %d\n", fd);
|
||||
|
||||
cnx_collection* collection = fd_info->collection;
|
||||
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
|
||||
/* Determine active queue (0 or 1): if fd is that of q[1], active_q = 1,
|
||||
* otherwise it's 0 */
|
||||
int active_q = active_queue(cnx, fd);
|
||||
|
||||
switch (cnx->state) {
|
||||
|
||||
case ST_PROBING:
|
||||
if (active_q == 1) {
|
||||
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
|
||||
dump_connection(cnx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
probing_read_process(cnx, fd_info);
|
||||
|
||||
break;
|
||||
|
||||
case ST_SHOVELING:
|
||||
shovel(cnx, active_q, fd_info);
|
||||
break;
|
||||
|
||||
default: /* illegal */
|
||||
log_message(LOG_ERR, "Illegal connection state %d\n", cnx->state);
|
||||
dump_connection(cnx);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void cnx_read_process(struct select_info* fd_info, int fd)
|
||||
{
|
||||
cnx_collection* collection = fd_info->collection;
|
||||
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
|
||||
switch (cnx->type) {
|
||||
case SOCK_STREAM:
|
||||
tcp_read_process(fd_info, fd);
|
||||
break;
|
||||
|
||||
case SOCK_DGRAM:
|
||||
udp_s2c_forward(cnx);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_message(LOG_ERR, "cnx_read_process: Illegal connection type %d\n", cnx->type);
|
||||
dump_connection(cnx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Process a connection that is active in write */
|
||||
static void cnx_write_process(struct select_info* fd_info, int fd)
|
||||
{
|
||||
if (debug) fprintf(stderr, "cnx_write_process fd %d\n", fd);
|
||||
|
||||
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, fd);
|
||||
int res;
|
||||
int queue = active_queue(cnx, fd);
|
||||
|
||||
res = flush_deferred(&cnx->q[queue]);
|
||||
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
|
||||
if (cnx->state == ST_PROBING) remove_probing_cnx(fd_info, cnx);
|
||||
tidy_connection(cnx, fd_info);
|
||||
} else {
|
||||
/* If no deferred data is left, stop monitoring the fd
|
||||
* for write, and restart monitoring the other one for reads*/
|
||||
if (!cnx->q[queue].deferred_data_size) {
|
||||
FD_CLR(cnx->q[queue].fd, &fd_info->fds_w);
|
||||
FD_SET(cnx->q[1-queue].fd, &fd_info->fds_r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Process a connection that accepts a socket
|
||||
* (For UDP, this means all traffic coming from remote clients)
|
||||
* */
|
||||
void cnx_accept_process(struct select_info* fd_info, struct listen_endpoint* listen_socket)
|
||||
{
|
||||
int fd = listen_socket->socketfd;
|
||||
int type = listen_socket->type;
|
||||
struct connection* cnx;
|
||||
int new_fd;
|
||||
|
||||
if (debug) fprintf(stderr, "cnx_accept_process fd %d\n", fd);
|
||||
|
||||
switch (type) {
|
||||
case SOCK_STREAM:
|
||||
cnx = accept_new_connection(fd, fd_info->collection);
|
||||
|
||||
if (cnx) {
|
||||
add_probing_cnx(fd_info, cnx);
|
||||
new_fd = cnx->q[0].fd;
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCK_DGRAM:
|
||||
new_fd = udp_c2s_forward(fd, fd_info->collection, fd_info->max_fd);
|
||||
fprintf(stderr, "new_fd %d\n", new_fd);
|
||||
if (new_fd == -1)
|
||||
return;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_message(LOG_ERR, "Inconsistent cnx type: %d\n", type);
|
||||
exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
FD_SET(new_fd, &fd_info->fds_r);
|
||||
if (new_fd >= fd_info->max_fd)
|
||||
fd_info->max_fd = new_fd + 1;
|
||||
|
||||
}
|
||||
|
||||
/* Check all connections to see if a UDP connections has timed out, then free
|
||||
* it. At the same time, keep track of the closest, next timeout. Only do the
|
||||
* search through connections if that timeout actually happened. If the
|
||||
* connection that would have timed out has had activity, it doesn't matter: we
|
||||
* go through connections to find the next timeout, which was needed anyway. */
|
||||
static void udp_timeouts(struct select_info* fd_info)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (now < fd_info->next_timeout) return;
|
||||
|
||||
time_t next_timeout = INT_MAX;
|
||||
|
||||
for (int i = 0; i < fd_info->max_fd; i++) {
|
||||
/* if it's either in read or write set, there is a connection
|
||||
* behind that file descriptor */
|
||||
if (FD_ISSET(i, &fd_info->fds_r) || FD_ISSET(i, &fd_info->fds_w)) {
|
||||
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, i);
|
||||
if (cnx) {
|
||||
time_t timeout = udp_timeout(cnx);
|
||||
if (!timeout) continue; /* Not a UDP connection */
|
||||
if (cnx && (timeout <= now)) {
|
||||
if (cfg.verbose > 3)
|
||||
fprintf(stderr, "timed out UDP %d\n", cnx->target_sock);
|
||||
close(cnx->target_sock);
|
||||
FD_CLR(i, &fd_info->fds_r);
|
||||
FD_CLR(i, &fd_info->fds_w);
|
||||
collection_remove_cnx(fd_info->collection, cnx);
|
||||
} else {
|
||||
if (timeout < next_timeout) next_timeout = timeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_timeout != INT_MAX)
|
||||
fd_info->next_timeout = next_timeout;
|
||||
}
|
||||
|
||||
/* Main loop: the idea is as follow:
|
||||
@@ -287,7 +508,7 @@ int is_fd_active(int fd, fd_set* set)
|
||||
* - When a file descriptor goes off, process it: read from it, write the data
|
||||
* to its corresponding pair.
|
||||
* - When a file descriptor blocks when writing, remove the read fd from fds_r,
|
||||
* move the data to a deferred buffer, and add the write fd to fds_w. Defered
|
||||
* move the data to a deferred buffer, and add the write fd to fds_w. Deferred
|
||||
* buffer is allocated dynamically.
|
||||
* - When we can write to a file descriptor that has deferred data, we try to
|
||||
* write as much as we can. Once all data is written, remove the fd from fds_w
|
||||
@@ -296,166 +517,86 @@ int is_fd_active(int fd, fd_set* set)
|
||||
* That way, each pair of file descriptor (read from one, write to the other)
|
||||
* is monitored either for read or for write, but never for both.
|
||||
*/
|
||||
void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
|
||||
struct select_info fd_info = {0};
|
||||
fd_set readfds, writefds; /* working read and write fd sets */
|
||||
struct timeval tv;
|
||||
int max_fd, i, j, res;
|
||||
int in_socket = 0;
|
||||
struct connection *cnx;
|
||||
int num_cnx; /* Number of connections in *cnx */
|
||||
int num_probing = 0; /* Number of connections currently probing
|
||||
* We use this to know if we need to time out of
|
||||
* select() */
|
||||
int i, res;
|
||||
|
||||
FD_ZERO(&fds_r);
|
||||
FD_ZERO(&fds_w);
|
||||
fd_info.num_probing = 0;
|
||||
FD_ZERO(&fd_info.fds_r);
|
||||
FD_ZERO(&fd_info.fds_w);
|
||||
fd_info.probing_list = gap_init(0);
|
||||
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
FD_SET(listen_sockets[i], &fds_r);
|
||||
set_nonblock(listen_sockets[i]);
|
||||
FD_SET(listen_sockets[i].socketfd, &fd_info.fds_r);
|
||||
set_nonblock(listen_sockets[i].socketfd);
|
||||
}
|
||||
max_fd = listen_sockets[num_addr_listen-1] + 1;
|
||||
fd_info.max_fd = listen_sockets[num_addr_listen-1].socketfd + 1;
|
||||
|
||||
cnx_num_alloc = getpagesize() / sizeof(struct connection);
|
||||
|
||||
num_cnx = cnx_num_alloc; /* Start with a set pool of slots */
|
||||
cnx = malloc(num_cnx * sizeof(struct connection));
|
||||
CHECK_ALLOC(cnx, "malloc");
|
||||
for (i = 0; i < num_cnx; i++)
|
||||
init_cnx(&cnx[i]);
|
||||
fd_info.collection = collection_init(fd_info.max_fd);
|
||||
|
||||
while (1)
|
||||
{
|
||||
memset(&tv, 0, sizeof(tv));
|
||||
tv.tv_sec = cfg.timeout;
|
||||
|
||||
memcpy(&readfds, &fds_r, sizeof(readfds));
|
||||
memcpy(&writefds, &fds_w, sizeof(writefds));
|
||||
memcpy(&readfds, &fd_info.fds_r, sizeof(readfds));
|
||||
memcpy(&writefds, &fd_info.fds_w, sizeof(writefds));
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", max_fd, num_probing);
|
||||
res = select(max_fd, &readfds, &writefds, NULL, num_probing ? &tv : NULL);
|
||||
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n",
|
||||
fd_info.max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.max_fd, &readfds, &writefds,
|
||||
NULL, fd_info.num_probing ? &tv : NULL);
|
||||
if (res < 0)
|
||||
perror("select");
|
||||
|
||||
|
||||
/* UDP timeouts: clear out connections after some idle time */
|
||||
udp_timeouts(&fd_info);
|
||||
|
||||
/* Check main socket for new connections */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
if (FD_ISSET(listen_sockets[i], &readfds)) {
|
||||
in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx);
|
||||
if (in_socket > 0) {
|
||||
num_probing++;
|
||||
FD_SET(in_socket, &fds_r);
|
||||
if (in_socket >= max_fd)
|
||||
max_fd = in_socket + 1;;
|
||||
}
|
||||
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
|
||||
cnx_accept_process(&fd_info, &listen_sockets[i]);
|
||||
|
||||
/* don't also process it as a read socket */
|
||||
FD_CLR(listen_sockets[i].socketfd, &readfds);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all sockets for write activity */
|
||||
for (i = 0; i < num_cnx; i++) {
|
||||
if (cnx[i].q[0].fd != -1) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
if (is_fd_active(cnx[i].q[j].fd, &writefds)) {
|
||||
res = flush_deferred(&cnx[i].q[j]);
|
||||
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
|
||||
if (cnx[i].state == ST_PROBING) num_probing--;
|
||||
tidy_connection(&cnx[i], &fds_r, &fds_w);
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "closed slot %d\n", i);
|
||||
} else {
|
||||
/* If no deferred data is left, stop monitoring the fd
|
||||
* for write, and restart monitoring the other one for reads*/
|
||||
if (!cnx[i].q[j].deferred_data_size) {
|
||||
FD_CLR(cnx[i].q[j].fd, &fds_w);
|
||||
FD_SET(cnx[i].q[1-j].fd, &fds_r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < fd_info.max_fd; i++) {
|
||||
if (FD_ISSET(i, &writefds)) {
|
||||
cnx_write_process(&fd_info, i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check sockets in probing state for timeouts */
|
||||
for (i = 0; i < fd_info.num_probing; i++) {
|
||||
struct connection* cnx = gap_get(fd_info.probing_list, i);
|
||||
if (!cnx || cnx->state != ST_PROBING) {
|
||||
log_message(LOG_ERR, "Inconsistent probing: cnx=%0xp\n", cnx);
|
||||
if (cnx)
|
||||
log_message(LOG_ERR, "Inconsistent probing: state=%d\n", cnx);
|
||||
exit(1);
|
||||
}
|
||||
if (cnx->probe_timeout < time(NULL)) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "timeout slot %d\n", i);
|
||||
probing_read_process(cnx, &fd_info);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all sockets for read activity */
|
||||
for (i = 0; i < num_cnx; i++) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
if (is_fd_active(cnx[i].q[j].fd, &readfds) ||
|
||||
((cnx[i].state == ST_PROBING) && (cnx[i].probe_timeout < time(NULL)))) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "processing fd%d slot %d\n", j, i);
|
||||
|
||||
switch (cnx[i].state) {
|
||||
|
||||
case ST_PROBING:
|
||||
if (j == 1) {
|
||||
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
|
||||
dump_connection(&cnx[i]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* If timed out it's SSH, otherwise the client sent
|
||||
* data so probe the protocol */
|
||||
if ((cnx[i].probe_timeout < time(NULL))) {
|
||||
cnx[i].proto = timeout_protocol();
|
||||
if (cfg.verbose)
|
||||
log_message(LOG_INFO,
|
||||
"timed out, connect to %s\n",
|
||||
cnx[i].proto->name);
|
||||
} else {
|
||||
res = probe_client_protocol(&cnx[i]);
|
||||
if (res == PROBE_AGAIN)
|
||||
continue;
|
||||
}
|
||||
|
||||
num_probing--;
|
||||
cnx[i].state = ST_SHOVELING;
|
||||
|
||||
/* libwrap check if required for this protocol */
|
||||
if (cnx[i].proto->service &&
|
||||
check_access_rights(in_socket, cnx[i].proto->service)) {
|
||||
tidy_connection(&cnx[i], &fds_r, &fds_w);
|
||||
res = -1;
|
||||
} else if (cnx[i].proto->fork) {
|
||||
struct connection *pcnx_i = &cnx[i];
|
||||
struct connection cnx_i = *pcnx_i;
|
||||
switch (fork()) {
|
||||
case 0: /* child */
|
||||
for (i = 0; i < num_addr_listen; i++)
|
||||
close(listen_sockets[i]);
|
||||
for (i = 0; i < num_cnx; i++)
|
||||
if (&cnx[i] != pcnx_i)
|
||||
for (j = 0; j < 2; j++)
|
||||
if (cnx[i].q[j].fd != -1)
|
||||
close(cnx[i].q[j].fd);
|
||||
free(cnx);
|
||||
connect_proxy(&cnx_i);
|
||||
exit(0);
|
||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
default: /* parent */
|
||||
break;
|
||||
}
|
||||
tidy_connection(&cnx[i], &fds_r, &fds_w);
|
||||
res = -1;
|
||||
} else {
|
||||
res = connect_queue(&cnx[i], &fds_r, &fds_w);
|
||||
}
|
||||
|
||||
if (res >= max_fd)
|
||||
max_fd = res + 1;;
|
||||
break;
|
||||
|
||||
case ST_SHOVELING:
|
||||
shovel(&cnx[i], j, &fds_r, &fds_w);
|
||||
break;
|
||||
|
||||
default: /* illegal */
|
||||
log_message(LOG_ERR, "Illegal connection state %d\n", cnx[i].state);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < fd_info.max_fd; i++) {
|
||||
/* Check if it's active AND currently monitored (if a connection
|
||||
* died, it gets tidied, which closes both sockets, but readfs does
|
||||
* not know about that */
|
||||
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.fds_r)) {
|
||||
cnx_read_process(&fd_info, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<--ssl> I<target address for SSL>] [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>] [-v] [-i] [-V] [-f] [-n]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@@ -84,9 +84,6 @@ Uses I<filename> as configuration file. If other
|
||||
command-line options are specified, they will override the
|
||||
configuration file's settings.
|
||||
|
||||
When using the shorthand version, make sure there should be
|
||||
no space between B<-F> and the I<filename>.
|
||||
|
||||
=item B<-t> I<num>, B<--timeout> I<num>
|
||||
|
||||
Timeout before forwarding the connection to the timeout
|
||||
@@ -117,8 +114,6 @@ IP address on which the Internet connection ends up).
|
||||
This can be specified several times to bind B<sslh> to
|
||||
several addresses.
|
||||
|
||||
=item B<--ssl> I<target address>
|
||||
|
||||
=item B<--tls> I<target address>
|
||||
|
||||
Interface and port on which to forward SSL connection,
|
||||
|
||||
+47
-2
@@ -9,7 +9,7 @@ parser: "sslh-conf.c";
|
||||
|
||||
printer: true;
|
||||
|
||||
conffile_option: ("F", "conffile");
|
||||
conffile_option: ("F", "config");
|
||||
|
||||
# List of includes to define runtime types
|
||||
# (bug in libconfig? if swallows the brackets if they start
|
||||
@@ -26,6 +26,9 @@ config: {
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "verbose"; type: "int"; default: 0; short: "v"; },
|
||||
{ name: "version"; type: "bool"; default: false;
|
||||
short: "V";
|
||||
description: "Print version information and exit"; },
|
||||
{ name: "foreground"; type: "bool"; default: false;
|
||||
short: "f";
|
||||
description: "Run in foreground instead of as a daemon"; },
|
||||
@@ -55,12 +58,15 @@ config: {
|
||||
{ name: "on-timeout"; type: "string"; default: "ssh";
|
||||
description: "Target to connect to when timing out"; },
|
||||
|
||||
{ name: "prefix"; type: "string"; default: ""; description: "Reserved for testing" }, # For echosrv only, not sslh
|
||||
|
||||
{ name: "listen",
|
||||
no_cl_accessors: true; # disable generation of individual cl options for each group element (we create a specific --listen option further below)
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "keepalive"; type: "bool"; default: false; }
|
||||
)
|
||||
},
|
||||
@@ -73,9 +79,13 @@ config: {
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "service"; type: "string"; optional: true; },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "udp_timeout"; type: "int"; default: 60 },
|
||||
{ name: "fork"; type: "bool"; default: false },
|
||||
{ name: "tfo_ok"; type: "bool"; default: false;
|
||||
description: "Set to true if this protocol supports TCP FAST OPEN" },
|
||||
{ name: "transparent"; type: "bool"; default: false;
|
||||
description: "Set to proxy this protocol transparently" },
|
||||
{ name: "log_level"; type: "int"; default: 1 },
|
||||
{ name: "keepalive"; type: "bool"; default: false },
|
||||
{ name: "sni_hostnames",
|
||||
@@ -145,6 +155,19 @@ cl_groups: (
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
# Redundant with the --tls setting before, for backwards compatibility
|
||||
{ name: "ssl"; pattern: "(.+):(\w+)"; description: "Set up TLS/SSL target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "tls" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "openvpn"; pattern: "(.+):(\w+)"; description: "Set up OpenVPN target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
@@ -162,7 +185,7 @@ cl_groups: (
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "openvpn" },
|
||||
{ path: "name"; value: "tinc" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
@@ -212,5 +235,27 @@ cl_groups: (
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "syslog"; pattern: "(.+):(\w+)"; description: "Set up syslog target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "syslog" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "anyprot"; pattern: "(.+):(\w+)"; description: "Set up default target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "anyprot" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
}
|
||||
)
|
||||
@@ -21,21 +21,23 @@ my $sslh_port = $conf->fetch_array("listen")->[0]->{port};
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
|
||||
# Which tests do we run
|
||||
my $SSH_SHY_CNX = 1;
|
||||
my $SSH_SHY_CNX = 0;
|
||||
my $PROBES_NOFRAG = 1;
|
||||
my $PROBES_AGAIN = 1;
|
||||
my $SSL_MIX_SSH = 1;
|
||||
my $SSH_MIX_SSL = 1;
|
||||
my $DROP_CNX = 1;
|
||||
|
||||
# Robustness tests. These are mostly to achieve full test
|
||||
# coverage, but do not necessarily result in an actual test
|
||||
# (e.g. some tests need to be run with valgrind to check all
|
||||
# memory management code).
|
||||
my $RB_CNX_NOSERVER = 1;
|
||||
my $RB_PARAM_NOHOST = 1;
|
||||
my $RB_WRONG_USERNAME = 1;
|
||||
my $RB_OPEN_PID_FILE = 1;
|
||||
my $RB_RESOLVE_ADDRESS = 1;
|
||||
my $RB_CNX_NOSERVER = 0;
|
||||
my $RB_PARAM_NOHOST = 0;
|
||||
my $RB_WRONG_USERNAME = 0;
|
||||
my $RB_OPEN_PID_FILE = 0;
|
||||
my $RB_RESOLVE_ADDRESS = 0;
|
||||
my $RB_CL_PARAMS = 0;
|
||||
|
||||
`lcov --directory . --zerocounters`;
|
||||
|
||||
@@ -125,9 +127,6 @@ sub test_probes {
|
||||
'http' => {
|
||||
data => "GET index.html HTTP/1.1",
|
||||
no_frag => 1 },
|
||||
'ssl' => {
|
||||
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello ssl alone"
|
||||
},
|
||||
'tls' => {
|
||||
# Packet with SNI and ALPN (`openssl s_client -connect localhost:443 -alpn alpn1 -servername sni1`)
|
||||
data_sni_alpn => "\x16\x03\x01\x00\xc4\x01\x00\x00\xc0\x03\x03\x03\x19\x01\x00\x40\x14\x13\xcc\x1b\x94\xad\x20\x5d\x13\x1a\x8d\xd2\x65\x23\x70\xde\xd1\x3c\x5d\x05\x19\xcb\x27\x0d\x7c\x2c\x89\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x5f\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello sni/alpn",
|
||||
@@ -139,6 +138,7 @@ sub test_probes {
|
||||
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone"
|
||||
},
|
||||
'openvpn' => { data => "\x00\x00" },
|
||||
'syslog' => { data => "<42> My syslog message" },
|
||||
'tinc' => { data => "0 hello" },
|
||||
'xmpp' => {data => "I should get a real jabber connection initialisation here" },
|
||||
'adb' => { data => "CNXN....................host:..." },
|
||||
@@ -207,7 +207,8 @@ foreach my $s (@{$conf->fetch_array("protocols")}) {
|
||||
}
|
||||
|
||||
|
||||
my @binaries = ('sslh-select', 'sslh-fork');
|
||||
#my @binaries = ('sslh-select', 'sslh-fork');
|
||||
my @binaries = ('sslh-select');
|
||||
|
||||
for my $binary (@binaries) {
|
||||
warn "Testing $binary\n";
|
||||
@@ -216,13 +217,14 @@ for my $binary (@binaries) {
|
||||
my ($sslh_pid, $valgrind);
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
my $cmd = "./$binary -v 4 -f -u $user -Ftest.cfg";
|
||||
verbose_exec $cmd;
|
||||
my $cmd = "./$binary -v 4 -f -u $user -F test.cfg";
|
||||
#$valgrind = 1;
|
||||
#exec "valgrind --leak-check=full ./$binary -v 3 -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
|
||||
#$cmd = "valgrind --leak-check=full $cmd";
|
||||
verbose_exec $cmd;
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
sleep 1; # Give everyone some time to start
|
||||
sleep 5 if $valgrind; # valgrind can be heavy -- wait 5 seconds
|
||||
|
||||
|
||||
@@ -284,6 +286,19 @@ for my $binary (@binaries) {
|
||||
}
|
||||
}
|
||||
|
||||
# Test: Drop connection without writing anything
|
||||
if ($DROP_CNX) {
|
||||
print "***Test: Connect but don't write anything\n";
|
||||
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_h;
|
||||
if ($cnx_h) {
|
||||
close $cnx_h;
|
||||
my_is(1, "$binary: Connect and write nothing");
|
||||
# The goal of the test is to check sslh doesn't
|
||||
# crash
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($PROBES_NOFRAG) {
|
||||
test_probes(no_frag => 1, binary => $binary);
|
||||
@@ -343,7 +358,7 @@ if ($RB_PARAM_NOHOST) {
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 1, "Exit status on illegal option");
|
||||
my_is($code, 6, "Exit status on illegal option");
|
||||
}
|
||||
|
||||
# Robustness: User does not exist
|
||||
@@ -390,6 +405,55 @@ if ($RB_RESOLVE_ADDRESS) {
|
||||
my_is($code, 4, "Exit status if can't resolve address");
|
||||
}
|
||||
|
||||
# Robustness: verify all command line options work
|
||||
if ($RB_CL_PARAMS) {
|
||||
print "***Test: Command line parameters\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
# This doesn't test --inetd
|
||||
exec "./sslh-select -v 3 -f -u $user -P $pidfile".
|
||||
" -n --transparent --timeout 10 -C /tmp".
|
||||
" --syslog-facility auth --on-timeout ssh".
|
||||
" --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address".
|
||||
" --openvpn localhost:$no_listen".
|
||||
" --tinc localhost:$no_listen".
|
||||
" --xmpp localhost:$no_listen".
|
||||
" --http localhost:$no_listen".
|
||||
" --adb localhost:$no_listen".
|
||||
" --socks5 localhost:$no_listen".
|
||||
" --anyprot localhost:$no_listen";
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
# It will die soon because $user cannot chroot (you
|
||||
# don't test as root, do you?)
|
||||
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 1, "Command line arguments");
|
||||
|
||||
|
||||
print "***Test: Bad command line parameters\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
# This doesn't test --inetd
|
||||
exec "./sslh-select -v 3 -f -u $user -P $pidfile".
|
||||
" -n --transparent --timeout 10 -C /tmp".
|
||||
" --fakeoption".
|
||||
" --anyprot localhost:$no_listen";
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 6, "Bad command line parameters");
|
||||
}
|
||||
|
||||
`lcov --directory . --capture --output-file sslh_cov.info`;
|
||||
`genhtml sslh_cov.info`;
|
||||
|
||||
|
||||
@@ -14,114 +14,264 @@
|
||||
use strict;
|
||||
use IO::Socket::INET6;
|
||||
use Data::Dumper;
|
||||
use Conf::Libconfig;
|
||||
|
||||
## BEGIN TEST CONFIG
|
||||
|
||||
# Do we test sslh-select or sslh-fork?
|
||||
my $sslh_binary = "./sslh-select";
|
||||
|
||||
# How many clients to we start for each protocol?
|
||||
my $NUM_CNX = 30;
|
||||
# How many total clients to we start? Each client will pick
|
||||
# a new protocol among what's in test.cfg.
|
||||
my $NUM_CNX = 50;
|
||||
|
||||
# Delay between starting new processes when starting up. If
|
||||
# you start 200 processes in under a second, things go wrong
|
||||
# and it's not sslh's fault (typically the echosrv won't be
|
||||
# forking fast enough).
|
||||
my $start_time_delay = 1;
|
||||
|
||||
# If you test 4 protocols, you'll start $NUM_CNX * 4 clients
|
||||
# (e.g. 40), starting one every $start_time_delay seconds.
|
||||
my $start_time_delay = .5;
|
||||
|
||||
# Max times we repeat the test string: allows to test for
|
||||
# large messages.
|
||||
my $block_rpt = 4096;
|
||||
my $block_rpt = 5;
|
||||
|
||||
# Probability to stop a client after a message (e.g. with
|
||||
# .01 a client will send an average of 100 messages before
|
||||
# disconnecting).
|
||||
my $stop_client_probability = .001;
|
||||
|
||||
# What protocols we test, and on what ports
|
||||
# Just comment out protocols you don't want to use.
|
||||
my %protocols = (
|
||||
"ssh" => { address => "localhost:9001", client => client("ssh") },
|
||||
"ssl" => { address => "localhost:9002", client => client("ssl") },
|
||||
"openvpn" => {address => "localhost:9003", client => client("openvpn") },
|
||||
"tinc" => {address => "localhost:9004", client => client("tinc") },
|
||||
);
|
||||
|
||||
##END CONFIG
|
||||
|
||||
my $conf = new Conf::Libconfig;
|
||||
$conf->read_file("test.cfg");
|
||||
|
||||
|
||||
# Pick one address for TCP and one for UDP
|
||||
my @listen = @{$conf->fetch_array("listen")};
|
||||
my ($sslh_tcp_address, $sslh_udp_address);
|
||||
|
||||
foreach my $l (@listen) {
|
||||
if ($l->{is_udp}) {
|
||||
$sslh_udp_address //= "$l->{host}:$l->{port}";
|
||||
} else {
|
||||
$sslh_tcp_address //= "$l->{host}:$l->{port}";
|
||||
}
|
||||
last if defined $sslh_tcp_address and defined $sslh_udp_address;
|
||||
}
|
||||
|
||||
|
||||
# code snippets to connect to each protocol
|
||||
my %connect_params = (
|
||||
regex => {
|
||||
is_udp => 1,
|
||||
sleep => 0,
|
||||
test_data => "foo bar",
|
||||
resp_len => 12,
|
||||
},
|
||||
ssh => {
|
||||
sleep => 20, # So it times out 50% of connections
|
||||
test_data => "SSH-2.0 hello",
|
||||
resp_len => 18, # length "ssh: SSH-2.0 hello" => 18
|
||||
},
|
||||
tinc => {
|
||||
sleep => 0,
|
||||
test_data => "0 ",
|
||||
resp_len => 8, # length "tinc: 0 " => 10
|
||||
},
|
||||
openvpn => {
|
||||
sleep => 0,
|
||||
test_data => "\x00\x00",
|
||||
resp_len => 11, # length "openvpn: \x0\x0" => 11
|
||||
},
|
||||
);
|
||||
|
||||
sub connect_service {
|
||||
my ($cnx, $service) = @_;
|
||||
|
||||
my $params = $connect_params{$service};
|
||||
|
||||
sleep rand $params->{sleep};
|
||||
my $test_data = $params->{test_data};
|
||||
syswrite $cnx, $test_data;
|
||||
sleep 1;
|
||||
sysread $cnx, my $r, $params->{resp_len};
|
||||
|
||||
my $expected = "$service: $test_data";
|
||||
return ($r eq $expected);
|
||||
}
|
||||
|
||||
# We use ports 9000, 9001 and 9002 -- hope that won't clash
|
||||
# with anything...
|
||||
my $sslh_address = "localhost:9000";
|
||||
my $pidfile = "/tmp/sslh_test.pid";
|
||||
|
||||
sub client {
|
||||
my ($service) = @_;
|
||||
my ($protocol, $client_id, $fd_out) = @_;
|
||||
|
||||
return sub {
|
||||
while (1) {
|
||||
my $cnx = new IO::Socket::INET(PeerHost => $sslh_address);
|
||||
my $test_data = "$service testing " x int(rand($block_rpt)+1) . "\n";
|
||||
my $service = $protocol->{name};
|
||||
|
||||
sleep 5 if $service eq "ssh";
|
||||
if ($service eq "openvpn") {
|
||||
syswrite $cnx, "\x00\x0F\x38\n";
|
||||
my $msg;
|
||||
sysread $cnx, $msg, 14; # length "openvpn: \x0\xF\x38\n" => 14
|
||||
}
|
||||
if ($service eq "tinc") {
|
||||
syswrite $cnx, "0 \n";
|
||||
my $msg;
|
||||
sysread $cnx, $msg, 10; # length "tinc: 0 \n" => 10
|
||||
}
|
||||
while (1) {
|
||||
print $cnx $test_data;
|
||||
my $r = <$cnx>;
|
||||
($? = 1, die "$service got [$r]\n") if ($r ne "$service: $test_data");
|
||||
last if rand(1) < $stop_client_probability;
|
||||
}
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
while (1) {
|
||||
my $r;
|
||||
#warn "$client_id: connect $sslh_tcp_address\n";
|
||||
my $cnx = new IO::Socket::INET(PeerHost => $sslh_tcp_address);
|
||||
die "$@\n" if (!$cnx);
|
||||
|
||||
foreach my $p (keys %protocols) {
|
||||
if (!fork) {
|
||||
exec "./echosrv --listen $protocols{$p}->{address} --prefix '$p: '";
|
||||
}
|
||||
}
|
||||
my $cnt = 0;
|
||||
|
||||
# Start sslh with the right plumbing
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
my $prots = join " ", map "--$_ $protocols{$_}->{address}", keys %protocols;
|
||||
my $cmd = "$sslh_binary -f -t 3 -u $user --listen $sslh_address $prots -P $pidfile";
|
||||
print "$cmd\n";
|
||||
exec $cmd;
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
sleep 2; # valgrind can be heavy -- wait 5 seconds
|
||||
#warn "$client_id: connecting $service\n";
|
||||
|
||||
|
||||
for (1 .. $NUM_CNX) {
|
||||
foreach my $p (keys %protocols) {
|
||||
if (!fork) {
|
||||
warn "starting $p\n";
|
||||
&{$protocols{$p}->{client}};
|
||||
if (not connect_service($cnx, $service)) {
|
||||
print $fd_out "$client_id\t0\tC\n";
|
||||
warn "$client_id: connecting failed\n";
|
||||
exit;
|
||||
}
|
||||
# Give a little time so we don't overrun the
|
||||
# listen(2) backlog.
|
||||
select undef, undef, undef, $start_time_delay;
|
||||
|
||||
#warn "$client_id: shoveling $service\n";
|
||||
|
||||
while (1) {
|
||||
my $test_data = "$service $cnt" x int(rand($block_rpt)+1) . "\n";
|
||||
print $cnx $test_data;
|
||||
$r = <$cnx>;
|
||||
my $expected= "$test_data";
|
||||
my $r_l = length $r;
|
||||
my $e_l = length $expected;
|
||||
$fd_out->autoflush;
|
||||
my $error = "";
|
||||
$error = "E" if $r ne $expected;
|
||||
print $fd_out ("$client_id\t$r_l\t$error\n");
|
||||
($? = 1, die "$service got [$r] expected [$expected]\n") if ($r ne $expected);
|
||||
if (rand(1) < $stop_client_probability) {
|
||||
print $fd_out ("$client_id\t$r_l\tD\n");
|
||||
last;
|
||||
}
|
||||
$cnt++;
|
||||
}
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
# For now, a simple regex client
|
||||
sub udp_client {
|
||||
my ($protocol, $client_id, $fd_out) = @_;
|
||||
|
||||
warn "UDP client starts\n";
|
||||
|
||||
while (1) {
|
||||
my $cnx = new IO::Socket::INET(Proto => 'udp', PeerHost => $sslh_udp_address);
|
||||
# my $cnx; socket $cnx, PF_INET, SOCK_DGRAM, 0 or die "socket: $!\n";
|
||||
die "$@\n" if (!$cnx);
|
||||
my $cnt = 0;
|
||||
|
||||
while (1) {
|
||||
my $test_data = "foo udp $cnt"x int(rand($block_rpt)+1). "\n";
|
||||
|
||||
my $ipaddr = inet_aton("localhost");
|
||||
my $portaddr = sockaddr_in(8086, $ipaddr);
|
||||
my $res = send($cnx, $test_data, 0, $portaddr);
|
||||
if ($res != length($test_data)) {
|
||||
die "cannot sendto: $!";
|
||||
}
|
||||
|
||||
my $expected= "$protocol->{name}: $test_data";
|
||||
|
||||
my $r;
|
||||
defined(recv($cnx, $r, length $expected, 0)) or die "recv: $!\n";
|
||||
|
||||
my $r_l = length $r;
|
||||
my $e_l = length $expected;
|
||||
$fd_out->autoflush;
|
||||
my $error = "";
|
||||
$error = "M" if $r ne $expected;
|
||||
print $fd_out ("$client_id\t$r_l\t$error\n");
|
||||
($? = 1, die "udp got [$r] expected [$expected]\n") if ($r ne $expected);
|
||||
if (rand(1) < $stop_client_probability) {
|
||||
print $fd_out ("$client_id\t$r_l\tD\n");
|
||||
last;
|
||||
}
|
||||
$cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $p (@{$conf->fetch_array("protocols")}) {
|
||||
if (!fork) {
|
||||
my $udp = $p->{is_udp} ? "--udp" : "";
|
||||
my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: ' 2> /dev/null";
|
||||
warn "$cmd\n";
|
||||
exec $cmd;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
warn "Don't forget to run sslh -F test.cfg!\n";
|
||||
|
||||
sleep 2; # Let echosrv's and sslh start
|
||||
|
||||
my ($c_in, $c_out);
|
||||
pipe $c_in, $c_out;
|
||||
|
||||
my @protocols = @{$conf->fetch_array("protocols")};
|
||||
|
||||
if (!fork) {
|
||||
# Process that starts all the clients
|
||||
for my $client_num (1 .. $NUM_CNX) {
|
||||
if (!fork) {
|
||||
my @supported_protocols = keys %connect_params;
|
||||
my $p_name = $supported_protocols[rand @supported_protocols];
|
||||
my @p = grep { $_->{name} eq $p_name } @protocols;
|
||||
my $p = shift @p;
|
||||
if ($p->{is_udp}) {
|
||||
udp_client($p, "$p->{name}$client_num", $c_out);
|
||||
} else {
|
||||
client($p, "$p->{name}$client_num", $c_out);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
# Give a little time so we don't overrun the
|
||||
# listen(2) backlog.
|
||||
select undef, undef, undef, $start_time_delay;
|
||||
}
|
||||
exit;
|
||||
} else {
|
||||
my %data;
|
||||
|
||||
# The condition here selects between pretty output or
|
||||
# raw output
|
||||
if (1) {
|
||||
my $CLEAR_LINE = "\033[2K";
|
||||
my $CURSOR_HOME = "\033[1;1H";
|
||||
my $CLEAR_SCREEN = "\033[2J";
|
||||
|
||||
# Process that retrieves client output to pretty print
|
||||
print $CLEAR_SCREEN; # Clear screen
|
||||
while (<$c_in>) {
|
||||
chop;
|
||||
my ($client_id, $r_l, $error, @rest) = split /\t/, $_;
|
||||
|
||||
$data{$client_id} = [ 0, ""] if not exists $data{$client_id};
|
||||
my ($curr_rcv) = ${$data{$client_id}}[0] + $r_l;;
|
||||
$error //= "";
|
||||
my ($curr_error) = "${$data{$client_id}}[1]$error";
|
||||
# Consolidate into... roman numerals!
|
||||
$curr_error =~ s/D{10}/X/;
|
||||
$curr_error =~ s/X{10}/C/;
|
||||
$curr_error =~ s/C{10}/M/;
|
||||
|
||||
$data{$client_id} = [ $r_l + $curr_rcv, "$curr_error$error" ];
|
||||
|
||||
|
||||
$client_id =~ /(\d+)/;
|
||||
my $i = $1;
|
||||
# print $CURSOR_HOME;
|
||||
print "\033[$i;1H$CLEAR_LINE$client_id\t$curr_rcv\t$curr_error\n";
|
||||
#foreach my $i (sort keys %data) {
|
||||
# ($r_l, $error) = @{$data{$i}};
|
||||
# print "$CLEAR_LINE$i\t$r_l\t$error\n";
|
||||
}
|
||||
} else {
|
||||
# Just print the client outputs
|
||||
while (<$c_in>) {
|
||||
print;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warn "waiting\n";
|
||||
wait;
|
||||
warn "finished waiting\n";
|
||||
|
||||
|
||||
`killall echosrv`;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Configuration file for testing (use both by sslh under
|
||||
# test and the test script `t`)
|
||||
|
||||
verbose: 2;
|
||||
verbose: 4;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
numeric: true;
|
||||
transparent: false;
|
||||
timeout: 10; # Probe test writes slowly
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
@@ -17,23 +17,29 @@ syslog_facility: "auth";
|
||||
listen:
|
||||
(
|
||||
{ host: "localhost"; port: "8080"; keepalive: true; },
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; }
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; },
|
||||
{ host: "ip4-localhost"; is_udp: true; port: "8086"; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; },
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "http"; host: "localhost"; port: "9002"; },
|
||||
{ name: "tinc"; host: "localhost"; port: "9003"; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "9004"; },
|
||||
{ name: "xmpp"; host: "localhost"; port: "9009"; },
|
||||
{ name: "adb"; host: "localhost"; port: "9010"; },
|
||||
{ name: "syslog"; host: "localhost"; port: "9013"; },
|
||||
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
|
||||
udp_timeout: 30;
|
||||
regex_patterns: [ "^foo" ];
|
||||
},
|
||||
{ name: "regex"; host: "localhost"; port: "9011";
|
||||
regex_patterns: [ "^foo", "^bar" ];
|
||||
minlength: 4;
|
||||
test_patterns: (
|
||||
test_patterns: ( # this is used by the test script, not by sslh
|
||||
{ pattern: "foo"; result: "ssh"; }, # After timeout
|
||||
{ pattern: "fooo"; result: "regex"; },
|
||||
{ pattern: "bar"; result: "ssh"; },
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
udp-listener.c: handles demultplexing UDP protocols
|
||||
|
||||
# Copyright (C) 2020-2021 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
|
||||
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "udp-listener.h"
|
||||
|
||||
|
||||
/* Find if the specified source has been seen before. -1 if not found
|
||||
*
|
||||
* TODO This is linear search and needs to be changed to something better for
|
||||
* production if we have more than a dozen sources
|
||||
* Also, this assumes src_addr from recvfrom() are repeatable for a specific
|
||||
* source...
|
||||
* */
|
||||
static int known_source(cnx_collection* collection, int max_fd, struct sockaddr* addr, socklen_t addrlen)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < max_fd; i++) {
|
||||
struct connection* cnx = collection_get_cnx_from_fd(collection, i);
|
||||
if (cnx && (cnx->type == SOCK_DGRAM) && cnx->target_sock) {
|
||||
if (!memcmp(&cnx->client_addr, addr, addrlen)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Process UDP coming from outside (client towards server)
|
||||
* If it's a new source, probe; otherwise, forward to previous target
|
||||
* Returns: >= 0 sockfd of newly allocated socket, for new connections
|
||||
* -1 otherwise
|
||||
* */
|
||||
int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd)
|
||||
{
|
||||
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
struct sockaddr src_addr;
|
||||
struct addrinfo addrinfo;
|
||||
struct sslhcfg_protocols_item* proto;
|
||||
struct connection* cnx;
|
||||
ssize_t len;
|
||||
socklen_t addrlen;
|
||||
int res, target, out = -1;
|
||||
char data[65536]; /* Theoritical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
|
||||
This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply
|
||||
malloc/free overhead for each packet, when really 64K is not that much */
|
||||
|
||||
addrlen = sizeof(src_addr);
|
||||
len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen);
|
||||
if (len < 0) {
|
||||
perror("recvfrom");
|
||||
return -1;
|
||||
}
|
||||
target = known_source(collection, max_fd, &src_addr, addrlen);
|
||||
addrinfo.ai_addr = &src_addr;
|
||||
addrinfo.ai_addrlen = addrlen;
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "received %ld UDP from %d:%s\n", len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
||||
|
||||
if (target == -1) {
|
||||
res = probe_buffer(data, len, &proto);
|
||||
/* First version: if we can't work out the protocol from the first
|
||||
* packet, drop it. Conceivably, we could store several packets to
|
||||
* run probes on packet sets */
|
||||
if (cfg.verbose) fprintf(stderr, "UDP probed: %d\n", res);
|
||||
if (res != PROBE_MATCH) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
||||
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
|
||||
if (!cnx) return -1;
|
||||
target = out;
|
||||
cnx->target_sock = out;
|
||||
cnx->proto = proto;
|
||||
cnx->type = SOCK_DGRAM;
|
||||
cnx->client_addr = src_addr;
|
||||
cnx->addrlen = addrlen;
|
||||
cnx->local_endpoint = sockfd;
|
||||
}
|
||||
cnx = collection_get_cnx_from_fd(collection, target);
|
||||
|
||||
/* at this point src is the UDP connection */
|
||||
res = sendto(cnx->target_sock, data, len, 0,
|
||||
cnx->proto->saddr->ai_addr, cnx->proto->saddr->ai_addrlen);
|
||||
cnx->last_active = time(NULL);
|
||||
fprintf(stderr, "sending %d to %s\n",
|
||||
res, sprintaddr(data, sizeof(data), cnx->proto->saddr));
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void udp_s2c_forward(struct connection* cnx)
|
||||
{
|
||||
int sockfd = cnx->target_sock;
|
||||
char data[65536];
|
||||
int res;
|
||||
|
||||
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
||||
fprintf(stderr, "recvfrom %d\n", res);
|
||||
CHECK_RES_DIE(res, "udp_listener/recvfrom");
|
||||
res = sendto(cnx->local_endpoint, data, res, 0,
|
||||
&cnx->client_addr, cnx->addrlen);
|
||||
cnx->last_active = time(NULL);
|
||||
fprintf(stderr, "sendto %d to\n", res);
|
||||
}
|
||||
|
||||
|
||||
/* returns date at which this socket times out. */
|
||||
int udp_timeout(struct connection* cnx)
|
||||
{
|
||||
if (cnx->type != SOCK_DGRAM) return 0; /* Not a UDP connection */
|
||||
|
||||
return cnx->proto->udp_timeout + cnx->last_active;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef UDPLISTENER_H
|
||||
#define UDPLISTENER_H
|
||||
|
||||
#include "collection.h"
|
||||
|
||||
/* UDP listener: upon incoming packet, find where it should go
|
||||
* This is run in its own process and never returns.
|
||||
*/
|
||||
void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint);
|
||||
|
||||
|
||||
/* Process UDP coming from outside (client towards server)
|
||||
* If it's a new source, probe; otherwise, forward to previous target
|
||||
* Returns: >= 0 sockfd of newly allocated socket, for new connections
|
||||
* -1 otherwise
|
||||
* */
|
||||
int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd);
|
||||
|
||||
/* Process UDP coming from inside (server towards client) */
|
||||
void udp_s2c_forward(struct connection* cnx);
|
||||
|
||||
|
||||
/* returns how many seconds before socket times out. Negative if timed out
|
||||
* already.
|
||||
*/
|
||||
int udp_timeout(struct connection* cnx);
|
||||
|
||||
#endif /* UDPLISTENER_H */
|
||||
@@ -0,0 +1,31 @@
|
||||
# Example for UDP protocols
|
||||
|
||||
# Listen sockets get a `is_udp : true` option
|
||||
# Protocol entries work exactly the same as TCP, there is no
|
||||
# difference. Currently, if mix up TCP and UDP probes, sslh will
|
||||
# try them in order and forward a UDP packet to a TCP probe
|
||||
# that matches (but using UDP; meaning, sslh will happily
|
||||
# forward a UDP packet to udp:localhost:80 if the HTTP
|
||||
# probe is specified, and matches)
|
||||
|
||||
verbose: 3;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
timeout: 10;
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen: (
|
||||
{ host: "localhost"; is_udp: true; port: "4443"; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "regex"; regex_patterns: [ "rutschle.net" ]; host: "ns1.rutschle.net"; port: "53"; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
Reference in new issue
Block a user