Compare commits

...
24 Commits
Author SHA1 Message Date
Yves Rutschle e831b732db update history 2024-05-16 21:34:45 +02:00
Yves Rutschle 8166be1a09 Fix inetd mode (fix #399) 2024-05-16 21:30:11 +02:00
Yves Rutschle 684374f353 avoid useless strcpy (fix #440) 2024-05-11 17:01:48 +02:00
Yves Rutschle de7351fd84 Make basic.cfg more useful (fix #438) 2024-04-21 18:20:54 +02:00
yrutschle d2ca706f86 document use of glob for SNI matching (fix #373) 2024-04-13 23:38:32 +02:00
Sergey Ponomarev c859e341aa .gitignore generated files by ./configure 2024-04-13 20:25:18 +02:00
Sergey Ponomarev 717fe8fae5 INSTALL.md: reformat
Add ./configure step
Fix libpcre-dev to libpcre2-dev
2024-04-13 20:25:18 +02:00
Sergey Ponomarev 1ddf45bf52 tcp_protocol_list_init(): allocate once instead of realloc each time 2024-04-13 20:25:18 +02:00
Sergey Ponomarev ae7530e33f Fix Narrowing conversion from 'ssize_t' to signed type 'int' is implementation-defined 2024-04-13 20:25:18 +02:00
Sergey Ponomarev d0a016221c probe.c: Fix Function 'memcmp' is called without explicitly comparing result 2024-04-13 20:25:18 +02:00
Sergey Ponomarev 9286b55702 echosrv.c: Fix Narrowing conversion from 'ssize_t' (aka 'long') to signed type 'int' is implementation-defined 2024-04-13 20:25:18 +02:00
Sergey Ponomarev 31c9e19abb echosrv.c: fix Narrowing conversion from ssize_t to int 2024-04-13 20:25:18 +02:00
Sergey Ponomarev ef8f3d1419 INSTALL.md fix typo 2024-04-13 20:25:18 +02:00
Sergey Ponomarev 2759c223be README.Windows.md fix link 2024-04-13 20:25:18 +02:00
Sergey Ponomarev b3c770898a Ignore opts (#436)
* Be less strict about args at startup

Ignore unknown options.

* Don't fail if pid file is accessible
2024-04-13 11:03:54 +02:00
yrutschle fee8491a8e remove useless capabilities and use standard environment in systemd 2024-04-10 18:39:07 +02:00
Yves Rűtschlé a80d79fd40 Merge multiple sslh systemd unit configurations 2024-04-10 18:07:28 +02:00
Yves Rűtschlé 4b921be69d Merge systemd unit multiple configurations 2024-04-10 18:06:56 +02:00
Yves Rutschle 1799a81079 fix off-by-one error that would sometime ignore the latest connection 2024-03-31 22:18:58 +02:00
Sergio C 5f1c1b1b61 Update README.md (#431)
Updated tproxy.md location
2024-03-24 08:33:34 +01:00
Yves Rutschle 58783af410 For MacOS, do not have multiple identicat cases in switch 2024-03-23 09:53:01 +01:00
Yves Rutschle 1957be1dc3 v.2.1.0 2024-03-22 16:41:50 +01:00
Yves Rűtschlé ecca78bde7 for MacOS, define unknown symbols to equivalent values 2024-03-21 18:54:03 +01:00
Yves Rűtschlé b94060ad76 undef FD_SETSIZE to avoid compiler warning upon redefine 2024-03-15 15:09:39 +01:00
21 changed files with 433 additions and 220 deletions

No files matched your search

+5
View File
@@ -7,6 +7,11 @@ libsslh.a
sslh-fork
sslh-select
sslh-ev
systemd-sslh-generator
sslh.8.gz
tags
version.h
/config.status
/config.log
/config.h
/Makefile
+7 -1
View File
@@ -1,4 +1,10 @@
vNEXT:
v2.1.2:
Fix inetd mode.
v2.1.1:
Various minor fixes.
v2.1.0:
Support for the Landlock LSM. After initial setup,
sslh gives up all local file access rights.
+1 -1
View File
@@ -47,7 +47,7 @@ no known generic approach, and if you do not find directions
for your exact setup, you will probably need an extensive
knowledge of network management and iptables setup".
It is described in its own [document](tproxy.md).
It is described in its own [document](doc/tproxy.md).
Docker image
+5 -3
View File
@@ -11,9 +11,13 @@ pidfile: "/var/run/sslh.pid";
# Change hostname with your external address name, or the IP
# of the interface that receives connections
# Default is to bind all interfaces. httpd can be started
# first to bind on localhost, in which case sslh will bind
# only other interfaces.
listen:
(
{ host: "thelonious"; port: "443"; }
{ host: "0.0.0.0"; port: "443"; },
{ host: "[::]"; port: "443"; }
);
@@ -24,8 +28,6 @@ protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
{ name: "openvpn"; host: "localhost"; port: "1194"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "tls"; host: "localhost"; port: "443"; log_level: 0; },
{ name: "anyprot"; host: "localhost"; port: "443"; }
);
+15 -14
View File
@@ -391,7 +391,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
}
/* Store some data to write to the queue later */
int defer_write(struct queue *q, void* data, int data_size)
int defer_write(struct queue *q, void* data, ssize_t data_size)
{
char *p;
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
@@ -403,7 +403,7 @@ int defer_write(struct queue *q, void* data, int data_size)
q->begin_deferred_data = p;
q->deferred_data = p + data_offset;
p += data_offset + q->deferred_data_size;
q->deferred_data_size += data_size;
q->deferred_data_size += (int)data_size;
memcpy(p, data, data_size);
return 0;
@@ -415,13 +415,13 @@ int defer_write(struct queue *q, void* data, int data_size)
* */
int flush_deferred(struct queue *q)
{
int n;
ssize_t n;
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
n = write(q->fd, q->deferred_data, q->deferred_data_size);
if (n == -1)
return n;
return (int)n;
if (n == q->deferred_data_size) {
/* All has been written -- release the memory */
@@ -432,10 +432,10 @@ int flush_deferred(struct queue *q)
} else {
/* There is data left */
q->deferred_data += n;
q->deferred_data_size -= n;
q->deferred_data_size -= (int)n;
}
return n;
return (int)n;
}
@@ -470,7 +470,8 @@ void dump_connection(struct connection *cnx)
int fd2fd(struct queue *target_q, struct queue *from_q)
{
char buffer[BUFSIZ];
int target, from, size_r, size_w;
int target, from;
ssize_t size_r, size_w;
target = target_q->fd;
from = from_q->fd;
@@ -515,7 +516,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED);
return size_w;
return (int)size_w;
}
/* returns a string that prints the IP and port of the sockaddr */
@@ -853,19 +854,19 @@ void write_pid_file(const char* pidfile)
f = fopen(pidfile, "w");
if (!f) {
print_message(msg_system_error, "write_pid_file:%s:%s", pidfile, strerror(errno));
exit(3);
print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno));
return;
}
res = fprintf(f, "%d\n", getpid());
if (res < 0) {
print_message(msg_system_error, "write_pid_file:fprintf:%s", strerror(errno));
exit(3);
print_message(msg_system_error, "write_pid_file: fprintf: %s\n", strerror(errno));
return;
}
res = fclose(f);
if (res == EOF) {
print_message(msg_system_error, "write_pid_file:fclose:%s", strerror(errno));
exit(3);
print_message(msg_system_error, "write_pid_file: fclose: %s\n", strerror(errno));
return;
}
}
+2 -1
View File
@@ -5,6 +5,7 @@
* enough for the macros to adapt (http://support.microsoft.com/kb/111855)
*/
#ifdef __CYGWIN__
#undef FD_SETSIZE
#define FD_SETSIZE 4096
#endif
@@ -171,7 +172,7 @@ int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
int start_listen_sockets(struct listen_endpoint *sockfd[]);
int defer_write(struct queue *q, void* data, int data_size);
int defer_write(struct queue *q, void* data, ssize_t data_size);
int flush_deferred(struct queue *q);
extern struct sslhcfg_item cfg;
+76 -84
View File
@@ -4,9 +4,10 @@ Pre-built binaries
Docker images of `master` and of the tagged versions are
available directly from [Github](https://github.com/yrutschle/sslh/pkgs/container/sslh).
Windows binaries for Cygwin are graciouly produced by
Windows binaries for Cygwin are graciously produced by
nono303 on his [repository](https://github.com/nono303/sslh).
Compile and install
===================
@@ -15,139 +16,130 @@ Dependencies
`sslh` uses:
* [libconfig](http://www.hyperrealm.com/libconfig/). For
Debian this is contained in package `libconfig-dev`. You
can compile with or without it using USELIBCONFIG in the
Makefile.
* [libconfig](http://www.hyperrealm.com/libconfig/).
For Debian this is contained in package `libconfig-dev`.
You can compile with or without it using USELIBCONFIG in the Makefile.
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
For Debian, this is contained in packages
`libwrap0-dev`. You
can compile with or without it using USELIBWRAP in the
Makefile.
For Debian, this is contained in packages `libwrap0-dev`.
You can compile with or without it using USELIBWRAP in the Makefile.
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`. You
can compile with or without it using USESYSTEMD in the
Makefile.
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`.
You can compile with or without it using USESYSTEMD in the Makefile.
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`. You can compile with or without it using USELIBCAP in the Makefile
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`.
You can compile with or without it using USELIBCAP in the Makefile
* libbsd, to enable to change the process name (as shown in
`ps`, so each forked process shows what protocol and what
connection it is serving),
which requires `libbsd` at runtime, and `libbsd-dev` at
compile-time.
* libbsd, to enable to change the process name (as shown in `ps`,
so each forked process shows what protocol and what connection it is serving),
which requires `libbsd` at runtime, and `libbsd-dev` at compile-time.
* libpcre2, in package `libpcre-dev`. You can compile
with or without it using ENABLE_REGEX in the Makefile.
* libpcre2, in package `libpcre2-dev`.
You can compile with or without it using ENABLE_REGEX in the Makefile.
* libev-dev, in package `libev-dev`. If you build a binary
specifically and do not build `sslh-ev`, you don't need
this.
* libev-dev, in package `libev-dev`.
If you build a binary specifically and do not build `sslh-ev`, you don't need this.
For OpenSUSE, these are contained in packages libconfig9 and
libconfig-dev in repository
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
For Fedora, you'll need packages `libconfig` and
`libconfig-devel`:
For Fedora, you'll need packages `libconfig` and `libconfig-devel`:
yum install libconfig libconfig-devel
yum install libconfig libconfig-devel
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)
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.5) to your path.
The test scripts are written in Perl, and will require
IO::Socket::INET6 (libio-socket-inet6-perl in Debian).
`IO::Socket::INET6` (`libio-socket-inet6-perl` in Debian).
Compilation
-----------
After this, the Makefile should work:
make install
make install
There are a couple of configuration options at the beginning
of the Makefile:
There are a couple of configuration options at the beginning of the Makefile:
* `USELIBWRAP` compiles support for host access control (see
`hosts_access(3)`), you will need `libwrap` headers and
library to compile (`libwrap0-dev` in Debian).
* `USELIBWRAP` compiles support for host access control (see `hosts_access(3)`),
you will need `libwrap` headers and library to compile (`libwrap0-dev` in Debian).
* `USELIBCONFIG` compiles support for the configuration
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
* `USELIBCONFIG` compiles support for the configuration file.
You will need `libconfig` headers to compile (`libconfig8-dev` in Debian).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
* `USELIBBSD` compiles support for updating the process name (as shown by `ps`).
* `USELIBBSD` compiles support for updating the process name (as shown
by `ps`).
Generating the configuration parser
-----------------------------------
The configuration file and command line parser is generated
by `conf2struct`, from `sslhconf.cfg`, which generates
`sslh-conf.c` and `sslh-conf.h`. The resulting files are
included in the source so `sslh` can be built without
`conf2struct` installed.
The configuration file and command line parser is generated by `conf2struct`,
from `sslhconf.cfg`, which generates `sslh-conf.c` and `sslh-conf.h`.
The resulting files are included in the source
so `sslh` can be built without `conf2struct` installed.
Further, to prevent build issues,
`sslh-conf.[ch]` has no dependency to `sslhconf.cfg` in the Makefile.
In the event of adding configuration settings,
they need to be regenerated using `make c2s`.
Further, to prevent build issues, `sslh-conf.[ch]` has no
dependency to `sslhconf.cfg` in the Makefile. In the event
of adding configuration settings, they need to be
regenerated using `make c2s`.
Binaries
--------
The Makefile produces three different executables: `sslh-fork`,
`sslh-select` and `sslh-ev`:
The Makefile produces three different executables:
`sslh-fork`, `sslh-select` and `sslh-ev`:
* `sslh-fork` forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead
of many processes.
If you are going to use `sslh` for a "small" setup (less than
a dozen ssh connections and a low-traffic https server) then
`sslh-fork` is probably more suited for you.
It is well-tested and very reliable, but incurs the overhead of many processes.
If you are going to use `sslh` for a "small" setup
(less than a dozen ssh connections and a low-traffic https server)
then `sslh-fork` is probably more suited for you.
* `sslh-select` uses only one thread, which monitors all
connections at once. It only incurs a 16 byte overhead per
connection. Also, if it stops, you'll lose all connections,
which means you can't upgrade it remotely. If you are going
to use `sslh` on a "medium" setup (a few hundreds of
connections), or if you are on a system where forking is
expensive (e.g. Windows), `sslh-select` will be better.
* `sslh-select` uses only one thread, which monitors all connections at once.
It only incurs a 16 byte overhead per connection.
Also, if it stops, you'll lose all connections,
which means you can't upgrade it remotely.
If you are going to use `sslh` on a "medium" setup (a few hundreds of connections),
or if you are on a system where forking is expensive (e.g. Windows),
`sslh-select` will be better.
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as a backend.
This allows using specific kernel APIs that
allow to manage thousands of connections concurrently.
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as
a backend. This allows using specific kernel APIs that
allow to manage thousands of connections concurrently.
Installation
------------
* In general:
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
```sh
./configure
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
```
* For Debian:
cp scripts/etc.init.d.sslh /etc/init.d/sslh
```sh
cp scripts/etc.init.d.sslh /etc/init.d/sslh
```
* For CentOS:
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
```sh
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
```
You might need to create links in /etc/rc<x>.d so that the server
start automatically at boot-up, e.g. under Debian:
update-rc.d sslh defaults
```sh
update-rc.d sslh defaults
```
+1 -1
View File
@@ -7,7 +7,7 @@ should be avoided as it is very inefficient on Windows, but
The following script downloads the latest cygwin, the latest version of sslh, and then compiles and copies the binaries with dependancies to an output folder.
It may be needed to correct it from time to time, but it works. I use it in a virtual machine.
Just retrieve WGET.EXE from [https://eternallybored.org/misc/wget/](url) or git binaries.
Just retrieve WGET.EXE from https://eternallybored.org/misc/wget/ or git binaries.
Copy the 3 files
+5 -4
View File
@@ -62,9 +62,10 @@ void check_res_dump(int res, struct addrinfo *addr, char* syscall)
void start_echo(int fd)
{
int res;
ssize_t res;
char buffer[1 << 20];
int ret, prefix_len;
ssize_t ret;
size_t prefix_len;
int first = 1;
prefix_len = strlen(cfg.prefix);
@@ -151,7 +152,7 @@ void udp_echo(struct listen_endpoint* listen_socket)
while (1) {
addrlen = sizeof(src_addr);
size_t len = recvfrom(listen_socket->socketfd,
ssize_t len = recvfrom(listen_socket->socketfd,
data + prefix_len,
sizeof(data) - prefix_len,
0,
@@ -166,7 +167,7 @@ void udp_echo(struct listen_endpoint* listen_socket)
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
int res = sendto(listen_socket->socketfd,
ssize_t res = sendto(listen_socket->socketfd,
data,
len + prefix_len,
0,
+3 -1
View File
@@ -78,7 +78,9 @@ listen:
# connect to the first that matches.)
#
# tls:
# sni_hostnames: list of FQDN for that target
# sni_hostnames: list of FQDN for that target. Each name can
# include wildcard following glob(7) rules.
# alpn_protocols: list of ALPN protocols for that target, see:
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
#
+1 -1
View File
@@ -323,7 +323,7 @@ static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
if (len < min_data_packet_size + sizeof(empty_message))
return PROBE_AGAIN;
if (memcmp(&p[0], empty_message, sizeof(empty_message)))
if (memcmp(&p[0], empty_message, sizeof(empty_message)) != 0)
return PROBE_NEXT;
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
+27
View File
@@ -0,0 +1,27 @@
[Unit]
Description=SSL/SSH multiplexer (select mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target
@@ -1,10 +1,10 @@
[Unit]
Description=SSL/SSH multiplexer
Description=SSL/SSH multiplexer (fork mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
+5 -2
View File
@@ -2239,10 +2239,13 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
/* Parse command line */
nerrors = arg_parse(argc, argv, argtable);
if (nerrors) {
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
// print bad args
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
// print usage
arg_print_syntax(stdout, argtable, "\n");
// print options
arg_print_glossary(stdout, argtable, " %-25s\t%s\n");
return -1;
fprintf(stderr, "Invalid args are ignored, please fix them\n");
}
+7
View File
@@ -164,6 +164,13 @@ void set_listen_procname(struct listen_endpoint *listen_socket)
}
/* At least MacOS does not know these two options, so define them to something
* equivalent for our use case */
#ifndef ENONET
#define ENONET EWOULDBLOCK
#endif
/* /MacOS kludge */
/* TCP listener: connections, fork a child for each new connection
* IN:
* endpoint: array of listening endpoint objects
+6 -3
View File
@@ -37,6 +37,7 @@
#include "common.h"
#include "probe.h"
#include "log.h"
#include "tcp-probe.h"
/* Constants for options that have no one-character shorthand */
#define OPT_ONTIMEOUT 257
@@ -69,7 +70,7 @@ static void printsettings(void)
strcpy(buf, "resolve on forward");
if (!p->resolve_on_forward) {
sprintaddr(buf, sizeof(buf), p->saddr);
int len = strlen(buf);
size_t len = strlen(buf);
sprintf(buf+len, " family %d %d",
p->saddr->ai_family,
p->saddr->ai_addr->sa_family);
@@ -98,7 +99,8 @@ static void printsettings(void)
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
#ifdef ENABLE_REGEX
{
int num_patterns, i, error;
size_t num_patterns, i;
int error;
pcre2_code** pattern_list;
PCRE2_SIZE error_offset;
PCRE2_UCHAR8 err_str[120];
@@ -186,7 +188,7 @@ void config_sanity_check(struct sslhcfg_item* cfg)
#endif
for (i = 0; i < cfg->protocols_len; ++i) {
if (strcmp(cfg->protocols[i].name, "tls")) {
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
if (cfg->protocols[i].sni_hostnames_len) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option sni_hostnames is only applicable for tls\n",
@@ -245,6 +247,7 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.inetd)
{
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
start_shoveler(0);
exit(0);
}
+2 -2
View File
@@ -67,7 +67,7 @@ static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
void watchers_add_read(watchers* w, int fd)
{
FD_SET(fd, &w->fds_r);
if (fd > w->max_fd)
if (fd + 1 > w->max_fd)
w->max_fd = fd + 1;
}
@@ -148,7 +148,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
fd_info.watchers->max_fd, fd_info.num_probing);
res = select(fd_info.watchers->max_fd + 1, &readfds, &writefds,
res = select(fd_info.watchers->max_fd, &readfds, &writefds,
NULL, fd_info.num_probing ? &tv : NULL);
if (res < 0)
perror("select");
+251 -89
View File
@@ -1,39 +1,72 @@
#include <libconfig.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <sys/stat.h>
#include "common.h"
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
static char* resolve_listen(const char *hostname, const char *port) {
/* Need room in the strcat for \0 and :
* the format in the socket unit file is hostname:port */
char *conn = malloc(strlen(hostname)+strlen(port)+2);
CHECK_ALLOC(conn, "malloc");
typedef struct FileList FileList;
struct FileList {
char *name;
struct FileList *prev;
};
static void free_file_list(FileList *fl) {
while (fl != NULL) {
FileList *prev = fl->prev;
free(fl->name);
free(fl);
fl = prev;
}
}
static FILE *err_log;
static bool systemd_invoked = false;
static const char
*socket_ext = ".socket",
*dropin_ext = ".conf",
*service_ext = ".service.d/",
*fork_unit_file = "/sslh@",
*select_unit_file = "/sslh-select@",
*fork_unit = "# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Requires=sslh@%s.socket\n"
"Conflicts=sslh-select@%s.service\n"
"PartOf=sslh@%s.socket\n",
*select_unit = "# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Requires=sslh@%s.socket\n"
"Conflicts=sslh@%s.service\n"
"PartOf=sslh@%s.socket\n";
static char *resolve_listen(const char *hostname, const char *port) {
char *conn = calloc(1, strlen(hostname) + strlen(port) + 2);
CHECK_ALLOC(conn, "malloc")
strcpy(conn, hostname);
strcat(conn, ":");
strcat(conn, port);
return conn;
}
static int get_listen_from_conf(const char *filename, char **listen[]) {
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
fprintf(err_log,
"systemd-sslh-generator: %s%d%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
@@ -42,118 +75,247 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
} else {
setting = config_lookup(&config, "listen");
if (setting) {
int i;
len = config_setting_length(setting);
*listen = malloc(len * sizeof(**listen));
CHECK_ALLOC(*listen, "malloc");
for (i = 0; i < len; i++) {
CHECK_ALLOC(*listen, "malloc")
for (int i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
if (!(config_setting_lookup_string(addr, "host", &hostname) && config_setting_lookup_string(addr, "port", &port))) {
fprintf(err_log,
"systemd-sslh-generator: line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
} else {
char *resolved_listen = resolve_listen(hostname, port);
(*listen)[i] = malloc(strlen(resolved_listen));
CHECK_ALLOC((*listen)[i], "malloc");
strcpy((*listen)[i], resolved_listen);
free(resolved_listen);
(*listen)[i] = resolve_listen(hostname, port);
}
}
}
}
return len;
}
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
int i;
static void write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *cfg, const char *source) {
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"Before=sslh@%s.service\n"
"SourcePath=%s\n"
"PartOf=sslh@%s.service\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
for (i = 0; i < num_addr; i++) {
cfg,
source,
cfg);
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
fprintf(socket,
"\n[Install]\n"
"WantedBy=sockets.target\n");
}
static int write_unit_dropin(const char *runtime_unit_dir, const bool is_fork, const char *cfg) {
const char
*unit_file = is_fork ? fork_unit_file : select_unit_file,
*unit_string = is_fork ? fork_unit : select_unit;
FILE *dropin_fd = stdout;
if (systemd_invoked) {
//Systemd drop-in configuration for the base select service sslh@%I.service
const size_t runtime_len = strlen(runtime_unit_dir);
size_t len = strlen(unit_file) + strlen(cfg) + strlen(service_ext) + strlen(dropin_ext);
char dropin_dir[runtime_len + len + 1];
strcpy(dropin_dir, runtime_unit_dir);
strcat(dropin_dir, unit_file);
strcat(dropin_dir, cfg);
strcat(dropin_dir, service_ext);
if (mkdir(dropin_dir, S_IRWXU | S_IRWXG | S_IROTH)) {
fprintf(err_log,
"systemd-sslh-generator: Could not create directory '%s' to generate drop-in configuration: %s\n",
dropin_dir,
strerror(errno));
return errno;
}
len = len + strlen(cfg) + strlen(dropin_ext);
char dropin_path[len + 1];
strcpy(dropin_path, dropin_dir);
strcat(dropin_path, cfg);
strcat(dropin_path, dropin_ext);
dropin_fd = fopen(dropin_path, "w");
if (!dropin_fd) {
fprintf(err_log,
"systemd-sslh-generator: Could not open '%s' to generate drop-in configuration: %s\n",
dropin_path,
strerror(errno));
return errno;
}
}
fprintf(dropin_fd, unit_string, cfg, cfg, cfg);
if (systemd_invoked) {
fclose(dropin_fd);
}
return 0;
}
static int gen_sslh_config(char *runtime_unit_dir) {
char *sslh_conf;
int num_addr;
FILE *config;
int status = 0;
const char *config_dir = "/etc/sslh/";
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
DIR *d = opendir(config_dir);
FileList *fa = NULL;
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
if (d) {
struct dirent *dir;
while ((dir = readdir(d)) != NULL) {
if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) {
continue;
}
FileList *lfa = malloc(sizeof(FileList));
CHECK_ALLOC(lfa, "malloc")
lfa->name = malloc(strlen(dir->d_name) + 1);
CHECK_ALLOC(lfa->name, "malloc")
strcpy(lfa->name, dir->d_name);
lfa->prev = NULL;
if (fa) {
lfa->prev = fa;
}
fa = lfa;
}
closedir(d);
} else {
fprintf(err_log,
"systemd-sslh-generator: Configuration directory '/etc/sslh/' does not exist! No units generated.\n");
//Config directory /etc/sslh/ does not exist
return 0;
}
fclose(config);
num_addr = get_listen_from_conf(sslh_conf, &listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir && *runtime_unit_dir) {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
CHECK_ALLOC(runtime_conf, "malloc");
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
free(runtime_conf);
if (!fa) {
fprintf(err_log,
"systemd-sslh-generator: Configuration directory '/etc/sslh/' is empty! No units generated.\n");
//No configuration files in /etc/sslh/
return 0;
}
FileList *fa_ref = fa;
// size_t num_listen_addresses = 0;
// char **listen_addresses = NULL;
//Process all config files
do {
char *end = strstr(fa->name, ".cfg");
if (!end) {
continue;
}
//Current sslh config name
const size_t end_count = end - fa->name;
char config_name[end_count + 1];
memcpy(config_name, fa->name, end_count);
config_name[end_count] = '\0';
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
//Full path to current sslh config
char full_path[strlen(config_dir) + strlen(fa->name) + 1];
strcpy(full_path, config_dir);
strcat(full_path, fa->name);
FILE *config = fopen(full_path, "r");
if (!config) {
fprintf(err_log,
"systemd-sslh-generator: Could not open config file '%s': %s\n",
full_path,
strerror(errno));
return errno;
} else {
fclose(config);
int num_addr = get_listen_from_conf(full_path, &listen);
if (num_addr <= 0) {
fprintf(err_log,
"systemd-sslh-generator: sslh config '%s' contains no valid listen configurations!\n",
fa->name);
status |= -1;
continue;
}
FILE *socket_fd = stdout;
if (systemd_invoked) {
//Systemd socket for the current sslh config
const size_t len = strlen(runtime_unit_dir) + strlen(fork_unit_file) + strlen(config_name) + strlen(socket_ext);
char socket_path[len + 1];
strcpy(socket_path, runtime_unit_dir);
strcat(socket_path, fork_unit_file);
strcat(socket_path, config_name);
strcat(socket_path, socket_ext);
socket_fd = fopen(socket_path, "w");
if (!socket_fd) {
fprintf(err_log,
"systemd-sslh-generator: Could not open '%s' to generate socket configuration: %s\n",
socket_path,
strerror(errno));
status |= errno;
continue;
}
}
//Write socket unit
write_socket_unit(socket_fd, listen, num_addr, config_name, full_path);
//Write forking drop-in config
status |= write_unit_dropin(runtime_unit_dir, false, config_name);
//Write select drop-in config
status |= write_unit_dropin(runtime_unit_dir, true, config_name);
if (systemd_invoked) {
fclose(socket_fd);
}
// if (listen_addresses) {
// //Check for overlapping addresses
// for (size_t i = 0; i < num_listen_addresses; i++) {
// for (size_t j = 0; j < num_addr; j++) {
// if (strcmp(*(listen_addresses + i), *(listen + j)) == 0) {
// fprintf(err_log, "systemd-sslh-generator: Overlapping listen addresses across sslh configurations!");
// return -1;
// }
// }
// }
// }
// char *listen_addresses_copy[num_listen_addresses + num_addr];
// if (listen_addresses) {
// //Copy previous configurations' listen addresses into temp
// memcpy(listen_addresses_copy, listen_addresses, num_listen_addresses);
// //Free global list of listen addresses
// free(listen_addresses);
// }
// //Copy listen addresses from current configuration into temp
// memcpy(listen_addresses_copy + num_listen_addresses, listen, num_addr);
// num_listen_addresses += num_addr;
// listen_addresses = malloc(num_listen_addresses * sizeof(char *));
// CHECK_ALLOC(listen_addresses, "malloc")
// //Append all addresses to global list
// memcpy(listen_addresses, listen_addresses_copy, num_listen_addresses);
//Free all allocated listen strings
for (size_t i = 0; i < num_addr; i++) {
free(*(listen + i));
}
}
} while((fa = fa->prev));
free_file_list(fa_ref);
return status;
}
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
int main(int argc, char *argv[]) {
if (argc == 1 || argc == 4) {
systemd_invoked = argc == 4;
if (systemd_invoked) {
err_log = fopen("/dev/kmsg", "w");
if (!err_log) {
return -1;
}
} else {
err_log = stderr;
}
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
if (systemd_invoked) {
fclose(err_log);
}
if (!r) {
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
}
return r < 0 ? -1 : 0;
} else {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1)
runtime_unit_dest = argv[1];
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
}
+3 -3
View File
@@ -60,13 +60,13 @@ int probe_client_protocol(struct connection *cnx)
static void tcp_protocol_list_init(void)
{
tcp_protocols = calloc(cfg.protocols_len, sizeof(tcp_protocols));
CHECK_ALLOC(tcp_protocols, "tcp_protocols");
for (int i = 0; i < cfg.protocols_len; i++) {
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
if (!p->is_udp) {
tcp_protocols[tcp_protocols_len] = p;
tcp_protocols_len++;
tcp_protocols = realloc(tcp_protocols, tcp_protocols_len * sizeof(*tcp_protocols));
CHECK_ALLOC(tcp_protocols, "realloc");
tcp_protocols[tcp_protocols_len-1] = p;
}
}
}
+4 -4
View File
@@ -224,7 +224,7 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
switch (data[pos]) { /* name type */
case 0x00: /* host_name */
if(has_match(tls_data->sni_hostname_list, tls_data->sni_list_len, data + pos + 3, len)) {
return len;
return (int)len;
} else {
return TLS_ENOEXT;
}
@@ -253,7 +253,7 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
return TLS_EPROTOCOL;
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
return len;
return (int)len;
} else if (len > 0) {
print_message(msg_probe_error, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
}
@@ -301,11 +301,11 @@ struct TLSProtocol *
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, const char** list, size_t list_len) {
if (alpn) {
tls_data->alpn_protocol_list = list;
tls_data->alpn_list_len = list_len;
tls_data->alpn_list_len = (int)list_len;
tls_data->match_mode.tls_match_alpn = 1;
} else {
tls_data->sni_hostname_list = list;
tls_data->sni_list_len = list_len;
tls_data->sni_list_len = (int)list_len;
tls_data->match_mode.tls_match_sni = 1;
}
+4 -3
View File
@@ -259,7 +259,8 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
struct connection* cnx;
ssize_t len;
socklen_t addrlen;
int res, target, out = -1;
ssize_t res;
int target, out = -1;
char data[65536]; /* Theoretical 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 */
@@ -280,7 +281,7 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
if (target == -1) {
res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto);
res = probe_buffer(data, (int)len, udp_protocols, udp_protocols_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 */
@@ -324,7 +325,7 @@ void udp_s2c_forward(struct connection* cnx)
{
int sockfd = cnx->target_sock;
char data[65536];
int res;
ssize_t res;
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;