Running a simple SSL/TLS forwarder

My router should run some extra duty. Essentially that's just running this, or an equivalent of this:

/usr/bin/openssl s_client -connect mail.example.com:995

On CLI this allows to talk to the mail server manually. When put as a service into xinetd, it forwards incoming connections to this mail server.

On OpenWRT 23.05 I used Image Builder to build an image with openssl-util included. With OpenWRT 24.10 this no longer works, the built image is too big for the available flash memory. I tried using package gnutls-utils (> gnutls-cli -p 995 mail.example.com), which makes the image even bigger.

Is there a way to forward connections without OpenSSL? For example, with the built-in MbedTLS? I googled many hours, can't find a low-disk-footprint solution.

busybox have s_client too, but it's not enabled, meaning you'd have to enable it, and compile your own image.

1 Like

How big is your router? ubus call system board

stunnel or nginx socket module can do the SSL wrapping of connections. If your router is too small you can run those on your "backend" and forward extra ports on your router.

If you want STARTTLS in pop3 then look at nginx pop3 module - yes - you add starttls capability and it bolts that on to connection.

# ubus call system board
{
	"kernel": "5.15.137",
	"hostname": "gate",
	"system": "Atheros AR9344 rev 2",
	"model": "TP-Link TL-WDR3600 v1",
	"board_name": "tplink,tl-wdr3600-v1",
	"rootfs_type": "squashfs",
	"release": {
		"distribution": "OpenWrt",
		"version": "23.05.2",
		"revision": "r23630-842932a63d",
		"target": "ath79/generic",
		"description": "OpenWrt 23.05.2 r23630-842932a63d"
	}
}

Wiki says flash is 8 MiB, Image Builder refuses to build images bigger than 5 MiB.

Well, dont bother, you might be able to squeeze Openssl or stunnel via imagebuilder but not nginx+starttls.
You are left with port forwarding and terminating SSL on the backend

First organisational thing would be to upgrade to supported release. that is 24.10.5

Install luci-app-attendedsysupgrade and click through 2 upgrades.

See here https://firmware-selector.immortalwrt.org/?version=24.10.4&target=ath79%2Fgeneric&id=tplink_tl-wdr3600-v1 (not openwrt)
they package OpenSSL by default, absence of sysupgrade image highlights you can not have OpenSSL on OpenWrt either no matter how hard you would try.

You device appear to have one or more USB ports.

Upgrade to whatever version, configure extroot by using a small flash drive, install the additional packages you need once set up.

Make sure all the other SSL dependent packages (e.g. wpad) are also using openssl so you don't have to have another SSL library in flash. Also of course cut out things you never use such as ppp and opkg (no point in being able to install packages at runtime when there is no space).

Thanks for all the replies, everybody!

This appears to be the solution, at least until packages get bigger again. I got it working on Debian and Image Builder builds a small enough image (4.87 Mbytes) with it. It still has libopenssl3 (and libatomic1 and zlib) as dependency, but removes the need for xinetd and openssl-util, so it fits.

How does it work on Debian 13?

stunnel is a typical Unix tool: gazillions of options, but once one finds the few right ones, it just works:

sudo apt-get install stunnel4
sudo rm -f /etc/stunnel/stunnel.conf
(
  echo '[pop2pops]'
  echo 'accept = 100'
  echo 'client = yes'
  echo 'connect = mail.example.com:995'
) | sudo tee /etc/stunnel/stunnel.conf > /dev/null
sudo stunnel      # goes to the background by default

That's all. No need for inetd, xinetd, systemd or whatever, just these 9 lines above.

With these two successes, it shouldn't be too hard to port this to OpenWRT.

did you change uclient and wpad to use openssl (and get rid of default mbedtls)?

No. I'm not aware Image Builder allows to configure or edit packages. I built the image like this:

PACKAGES="luci luci-ssl -ppp -ppp-mod-pppoe -luci-proto-ppp"
PACKAGES+=" stunnel ca-certificates"
make image PROFILE=tplink_tl-wdr3600-v1 PACKAGES="${PACKAGES}"

You can use owut or luci-attended-sysupgrade.

With Image Builder put a minus sign on the name of the default package (-wpad-basic-mbedtls) and then replace it with the openssl one. If you removed all of the mbedtls packages it should automatically omit libmbedtls as well.

2 Likes

All right, all done here, the service uses STunnel and is running fine. Time to tell how I've done it.

As one can see by the discussion here it's by no means the only working solution, and probably not even the best one. It's good enough for me and should work for others as well.

1. Get required packages

Required packages are stunnel and ca-certificates. Other than the previous solution, packages xinetd and openssl-util are not needed.

One can install these two required packages with Opkg:

opkg update
opkg install stunnel ca-certificates

If flash space is tight and Opkg runs out of space, Image Builder allows to build a custom image. Like

PACKAGES="luci luci-ssl -ppp -ppp-mod-pppoe -luci-proto-ppp"
PACKAGES+=" stunnel ca-certificates"
make image PROFILE=tplink_tl-wdr3600-v1 PACKAGES="${PACKAGES}"

The result of this build needs to be flashed onto the router.

2. Create configuration

Other than the STunnel coming with Debian, OpenWRT's package is adapted to UCI, its Unified Configuration Interface. Which means, many of the HowTos one can find in the internet don't work without modification. Googling also finds an utterly outdated STunnel man page.

UCI means you don't write a configuration file in /etc/stunnel/stunnel.conf directly. The package installs such a file, but that gets ignored. Instead one writes a file /etc/config/stunnel, in UCI syntax. Like this:

config globals globals
	# Drop privileges.
	option setuid nobody
	option setgid nogroup
	# This one gets ignored:
	#option foreground yes
	# Don't log to stderr, use syslog.
	option syslog yes

# Services
config service pop2pops
	option client yes
	option accept_host 10.0.0.1
	option accept_port 100
	list connect mail.example.com:995
	option verifyChain yes
	option CApath /etc/ssl/certs
	option checkHost mail.example.com
	option OCSPaia yes

A few remarks:

  • STunnel's defaults are unsafe. Better uses see here.
  • STunnel is a bit unusual as it has no command line options. Not even global temporary things like e.g. --foreground.
  • If you want to run multiple forwarders, simply add multiple service sections, each with a unique name.
  • Keyword accept_host is crucial and should be the LAN interface of the router. For me that's 10.0.0.1, more typical would be 192.168.0.1. Without that keyword, STunnel listens on all interfaces, including the public/WAN one. This would open unencrypted public access to that mailserver.
  • Keywords verifyChain and CApath (or CAfile) are also crucial. Without them, STunnel doesn't verify these SSL connections and simply accepts every offered certificate.
  • Keywords checkHost and OCSPaia add checking of certificate revocations.

3. Start with the new configuration

That's easy:

/etc/init.d/stunnel restart

4. Tests

As always, one should test the work.

a) Verify the used configuration. UCI creates a configuration file for STunnel, in STunnel's systax, on the fly. With the service started, one can find it in /tmp/etc/stunnel.conf. It should match the intention above:

; STunnel configuration file generated by uci
; Written Wed Jan  7 18:00:53 2026

foreground = quiet
pid = /var/run/stunnel/stunnel.pid
syslog = yes
setgid = nogroup
setuid = nobody

[pop2pops]
CApath = /etc/ssl/certs
client = yes
OCSPaia = yes
verifyChain = yes
checkHost = mail.example.com
connect = mail.example.com:995
accept = 10.0.0.1:100

b) Check Syslog. Browse to Luci -> Menu -> Status -> System Log and filter for stunnel. There should be notifications about STunnel's startup and no errors.

c) Establish a manual connection. As this needs no TLS/SSL, a simple Telnet from any computer in the LAN should say hello to the intended mail server (type QUIT to end the connection):

~$ telnet gate 100
Trying 10.0.0.1...
Connected to gate.
Escape character is '^]'.
+OK POP server ready
QUIT
+OK POP server signing off
Connection closed by foreign host.

d) Test public access. Do the same as above, but with your router's public IP address. The result should be different:

~$ telnet 172.217.208.102 100
Trying 172.217.208.102...
telnet: Unable to connect to remote host: Connection refused

5. References

In addition to the pages linked above, these pages were helpful and should help in case of trouble:

https://www.stunnel.org/static/stunnel.html
https://www.stunnel.org/auth.html

Many thanks to everybody helping here. Much appreciated.

3 Likes

Foreground mode means log goes to console output then via procd to syslog.
Chroot should work too, by default there is no storage needed