the djb way

services, services!


ftpd + ftp-proxy


Protocol: TCP
Standard Port: 21

We recently needed to put up a limited ftp service on a gateway server running OpenBSD. We wanted authentication limited to just a couple users, no anonymous ftp, access restricted by IP address to a few specific external clients, and a chroot(8) security model.

Since the publicfile ftpd server provides anonymous ftp only, it wouldn't work for us in this instance. So, instead, we decided to put up a daemontools service for the ftpd server that ships as standard equipment with OpenBSD. (Many other ftp servers also exist; they may be adapted to daemontools similarly.)

As usual for an OpenBSD packet-filter setup, we also wanted to run ftp-proxy for out-bound ftp connections originating from the local network. Although the ftp-proxy service is unrelated to the ftpd service running on the same server, the redirect rules configured in /etc/pf.conf need to be set up in such a way that allows both services to peacefully co-exist.

Both ftpd and ftp-proxy are usually run by inetd, so they are very easy to convert to a daemontools /service. If these services are already being run by inetd, comment them out of /etc/inetd.conf, then give the inetd process a SIGHUP with kill -HUP.

ftpd service

We'll start with the ftpd service. First, make the local service directories for the OpenBSD ftpd service:

# mkdir -p /var/svc.d/open-ftpd/log

The name "open-ftpd" here is only for the purpose of distinguishing this OpenBSD-based ftp service from others that we may also use.

Then, install a daemontools "run" script in /var/svc.d/open-ftpd/run:


#!/bin/sh
# open-ftpd/run
# ===
CONLIMIT=29
exec 2>&1

echo "*** Starting openbsd ftpd service..."
exec softlimit -m 3500000 \
    tcpserver -v -RH -l0 \
    -c ${CONLIMIT} \
    -x /etc/tcprules/ftp.cdb \
    0 ftp \
      /usr/libexec/ftpd -A -S -U -4

### that's all, folks!

Make sure the run script is executable, chmod 755. The familiar softlimit utility is used to limit resources.

As for the ftpd executable itself, the -A option is used for the purpose of restricting access only to user accounts we define in /etc/ftpchroot, and so that no fully priveleged accounts are enabled. Although the mnemonic for this option is "anonymous", anonymous ftp will not be enabled as long as we don't define an "anonymous" account.

The -S and -U options affect logging, see the ftpd(8) man page for more information. We also use the -4 option here, using IPv4 addresses only, since the stock ucspi-tcp doesn't handle IPv6 anyway. (Comment: nor is IPv6 yet supported by the OpenBSD ftp-proxy described below.)

The logging service is the familiar run script for multilog in /var/svc.d/open-ftpd/log/run:


#!/bin/sh
# open-ftpd/log/run
exec setuidgid multilog multilog t /var/multilog/open-ftpd

### that's all, folks!

Make sure the script is executable, then set up the log directory in /var/multilog:

# mkdir -p /var/multilog/open-ftpd
# chown multilog  /var/multilog/open-ftpd

Now set up whatever access-control rules you want to define for the service in /etc/tcprules/ftp.rules:


# ftp.rules
127.0.:allow
# permit from local network:
192.168.0.:allow
# permit from specific remote clients:
9.9.9.:allow
:deny

Then compile the rules:

# (cd /etc/tcprules; make ftp.cdb)

The service is now ready to bring up:

# ln -s /var/svc.d/open-ftpd /service/open-ftpd

The way this service is defined assumes a limited number of accounts will be defined in /etc/ftpchroot. In our specific application, these particular user accounts are set up in /etc/passwd only for the purposes of ftp. The ftpd daemon does require that accounts have a login shell defined in /etc/shells; so we add /sbin/nologin to /etc/shells.

ftp-proxy service

[Not running OpenBSD? See http://www.ftpproxy.org/.]

The ftp-proxy(8) service is usually set up in conjunction with packet filter rules. These rules redirect out-bound ftp requests coming from the local network to the proxy. The proxy service then securely manages the ftp request on behalf of the local client.

The standard way of setting up ftp-proxy on OpenBSD is to bind it to the loopback address 127.0.0.1, listening on port 8021. The highlighted rdr line in /etc/pf.conf shown below is then used to redirect outbound ftp requests to the proxy:


# /etc/pf.conf
#
## macros for our interfaces:
ext_if = "xl0"
ext_ip = "1.2.3.4"
int_if = "xl1"
int_ip = "192.168.0.254"
int_net = "192.168.0.0/24"

## scrub:
scrub in on $ext_if all fragment reassemble

## nat:
nat on $ext_if from $int_net to any -> ($ext_if)

## rdr for ftp-proxy:
rdr on $int_if proto tcp from any to any port 21 \
  -> 127.0.0.1 port 8021

## filter rules continue ...
...

The problem here is that ftp requests from the local network destined for the ftpd server running on this host, will also be redirected to the proxy. The effect is an oroborus; requests from local clients are caught in a packet-filter loop. In practice, clients do get a connection to the ftpd server anyway, [why?], but you will notice error messages generated by the proxy.

A no rdr line added into /etc/pf.conf fixes the problem:


# /etc/pf.conf
#
## macros for our interfaces:
ext_if = "xl0"
ext_ip = "1.2.3.4"
int_if = "xl1"
int_ip = "192.168.0.254"
int_net = "192.168.0.0/24"

## scrub:
scrub in on $ext_if all fragment reassemble

## nat:
nat on $ext_if from $int_net to any -> ($ext_if)

## rdr for ftp-proxy:
## _first_ matching rule wins:
no rdr on $int_if proto tcp from any to $int_ip port 21
rdr on $int_if proto tcp from any to any port 21 \
  -> 127.0.0.1 port 8021

## filter rules continue ...
...

Now we can show the setup of ftp-proxy as a daemontools service. First, make the local service directories:

# mkdir -p /var/svc.d/ftp-proxy/log

Then, install the daemontools "run" script in /var/svc.d/ftp-proxy/run:


#!/bin/sh
# ftp-proxy/run
CONLIMIT=91
PORT=8021
exec 2>&1

echo "*** Starting ftp-proxy service..."
exec softlimit -m 3500000 \
  tcpserver -v -RH -l0 \
  -c ${CONLIMIT} \
  127.0.0.1 ${PORT} \
    /usr/libexec/ftp-proxy -u ftpproxy -t 240

### that's all, folks!

Make sure the script is executable, chmod 755. This is the usual daemontools stuff. The ftp-proxy executable initially needs root priveleges for making pf(4) lookups. After that, it drops priveleges to the user specified with the -u option; here we use "ftpproxy".

The main thing here is that the service is bound specifically to IP 127.0.0.1, port 8021, coinciding with the setup in /etc/pf.conf. The packet filter rules will then redirect outbound ftp requests to this service.

The multilog service:


#!/bin/sh
# ftp-proxy/log/run
setuidgid multilog multilog t /var/multilog/ftp-proxy

Make the script executable, chmod 755. Then setup the log directory:

# mkdir -p /var/multilog/ftp-proxy
# chown multilog /var/multilog/ftp-proxy

Bring up the service:

# ln -s /var/svc.d/ftp-proxy /service/ftp-proxy

Now your network has good clean proxy protection for local ftp clients, as well as a pretty secure ftpd server running on the gateway itself.


Copyright © 2004 Wayne Marshall.
All rights reserved.

Last edit 2004.03.08, wcm.