Using tcpserver

Vincent Danen

March 25, 2008

If you've ever used qmail or djbdns, you've used tcpserver. tcpserver is the program that waits for incoming connections and will run a particular program; when dealing with qmail and djbdns, tcpserver hands off the TCP connection to the particular program to perform email reception or DNS queries. In other words, tcpserver is very similar to other programs that have been used with Mandrakelinux: inetd, which is no longer used, and xinetd, which is the current "super server" in use. tcpserver competes with these products, and is written by the same author of qmail and djbdns. Right there, one word comes to mind: security. Wait, there's one more: non-standard. At least, non-standard in terms of what you might be used to; it doesn't use flat configuration files like xinetd or inetd do, and operates in a very different manner.

This tutorial is here to help you make use of tcpserver. tcpserver is a secure replacement for inetd. I won't argue the benefits of using either tcpserver or xinetd; both are equally efficient programs in my opinion. xinetd is more secure than inetd, but it has also had a somewhat checkered past as well (see MDKSA-2001:076); however, the new release fixing those vulnerabilities (version 2.3.0) was due to Solar Designer's hard work, and as a result one can feel relatively confident that xinetd is more secure than other choices.

tcpserver comes in the ucspi-tcp package, which also provides the tcpclient program, which makes a TCP connection and runs a program of your choice. Using tcpserver and tcpclient, you can have programs not meant to talk to each other via TCP/IP, do exactly that. It also comes with a lot of other little programs and tools that can make life easier.

Installing tcpserver

In order to use tcpserver, you must install the ucspi-tcp package. If you have djbdns or qmail installed, it is a pre-requisite and will already be installed. If not, you can install it from source (very easy to do), or grab an RPM from rpmhelp (if you're using Mandrakelinux).

To take full advantage of everything this tutorial will show, and to make the most of using tcpserver, you should also install the daemontools and supervise-scripts packages. These are also available on rpmhelp.net. If you install djbsupport, you can simply execute on the command line, as root:

# urpmi --auto-select ucspi-tcp daemontools supervise-scripts

This will install all three packages.

Using tcpserver and supervise

The first step to using tcpserver, and supervise to control the entire operation, is to understand how it is all laid out. There are a few important directories to keep in mind, as each plays an important function. The first is the /service directory. This directory is the primary directory that supervise will look at to see what services it will control. Each service is contained in it's own directory. For example, if you were running vsftpd via tcpserver and supervise, you would have a directory structure like this:

/service/vsftpd
/service/vsftpd/log

Within each directory is a run file. This file tells supervise how to start the service. Below each primary service is a log/ sub-directory; this directory also contains a run file, which is used to log the information tcpserver generates (connection information, etc). For instance, the contents of /service/vsftpd/run would look something like this:

#!/bin/sh
PATH="/sbin:/usr/sbin:/bin:/usr/bin:$PATH"

exec /usr/bin/tcpserver -c30 -HRXv -llocalhost -x /etc/tcprules.d/vsftpd.cdb \
0 ftp /usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf 2>&1

And the contents of /service/vsftpd/log/run would be something like this:

#!/bin/sh

exec /usr/bin/setuidgid nobody /usr/bin/multilog t /var/log/supervise/vsftpd

As you can see, the first run file calls tcpserver to handle the connections for vsftpd, while the second logs all of tcpserver's output to the directory /var/log/supervise/vsftpd. The multilog program is part of the daemontools package and basically takes tcpserver's output as input and writes it to a file called "current" in the specified directory. multilog handles it's own log rotation, so you never have to worry about rotating logfiles. For more information on how multilog handles logging, view the multilog manpage.

/service is a very special directory and should not be used directly. In fact, there should be nothing other than symbolic links to directories in other locations in this directory. The /var/service directory is a convenient place to put your supervise directories. As soon as you create a symbolic link in /service to a directory in /var/service (or any other place you feel like having the actual directory), svscan will notice the new link and start the specified server within seconds. svscan is the scanning daemon that supervises the /service directory.

Another important directory is the /etc/tcprules.d directory. This contains all of the rules for various applications, much like /etc/hosts.allow and /etc/hosts.deny being used by tcp_wrappers. You can still use tcp_wrappers to provide access control, but since tcpserver provides the same capabilities, there isn't much point. In the /etc/tcprules.d directory, you will have two files for each service (or none if you do not want to use access controls on a particular service): the plaintext file and it's cdb binary equivalent. More on this a little later.

The tcpserver program has a number of command line arguments you can provide to make it more customizable. The run file in the primary service directory will call tcpserver directly, with a number of arguments, in order to execute the service you want controlled. Think of the run file as a line in /etc/inetd.conf or as a file in /etc/xinetd.d. This is where you will call the service in question, and wrap it with some tcpserver arguments.

The syntax to use tcpserver is:

tcpserver opts host port prog

where opts is a series of tcpserver arguments or options, host is a single argument to determine the host IP to listen to, port is the port number to listen to, and prog is the program to call with one or more arguments when a connection is received. Note that you can also use a port name, which should correspond to an entry in the /etc/services file. If the port is "0", tcpserver will select any free TCP port. If the host is "0", tcpserver will allow connections on any local IP address. It can also be an IP address to strictly listen for connections coming in on that IP, or it can be a hostname, which allows connections to the first IP address corresponding to that hostname.

tcpserver also has a number of arguments that can be used. For the full list, look in the tcpserver manpage, but here are some of the more commonly used arguments:

  • -c [n]: do not handle more than [n] simultaneous connections (good for rate-limiting and preventing DoS attacks)
  • -x [cdb]: follow the rules in the cdb file named [cdb].
  • -X: in conjunction with -x, allow connections even if the cdb file does not exist; normally tcpserver will drop the connection if the cdb file doesn't exist
  • -g [gid]: switch the effective group ID to [gid] to receive connections
  • -u [uid]: switch the effective user ID to [uid] to receive connections
  • -h: look up the remote host name in DNS to set the environment variable $TCPREMOTEHOST (this is the default)
  • -H: do not look up the remote host name
  • -p: after looking up the remote host name in DNS, look up the IP address in DNS for that host name, and remove the $TCPREMOTEHOST variable if none of the addresses match the client's IP address (aka paranoid mode)

So, if we were to look again at the run file for vsftpd, we would see that it limits the number of simultaneous connections to 30, does not look up the remote host name (simply to make the connection faster by not querying DNS), does not attempt to get $TCPREMOTEINFO from the remote host (the -R argument), allows connections if the cdb file does not exist, and is verbose in it's logging. It specifies to use the file /etc/tcprules.d/vsftpd.cdb to obtain the tcprules, binds to every IP address on the system, and listens on the FTP port (port 21). The program to call is /usr/sbin/vsftpd and the argument passed to vsftpd is /etc/vsftpd/vsftpd.conf, which is vsftpd's configuration file.

On a side note, this is an excellent way to run programs that do not understand virtual hosting, such as vsftpd. While proftpd understands virtual hosting (IP-based) for FTP, vsftpd does not. Using tcpserver, you can run multiple instances of vsftpd, each with their own configuration file and cdb tcprules file, and each bound to a distinct IP address. This is accomplished by having each entry in /service distinguished with a unique name (ie. /service/vsftpd-server1, /service/vsftpd-server2, etc.), each pointing to a unique configuration and cdb file, and each bound to an IP (ie. instead of specifying "0" for the host, specify the IP address that belongs to that instance of vsftpd).

Now, if you look at the log/run file, you'll see that it executes the setuidgid program, which sets the effective user ID and group ID to that specified (in this case nobody), and runs the program multilog with the "t" parameter (tells it to use precise timestamping), to write logs to the /var/log/supervise/vsftpd directory. This directory must exist prior to supervise starting this service.

log/ is a special sub-directory and basically the program specified in the run file in this directory will take the standard output of tcpserver as standard input. multilog will take this data and write it to a log file. If you are not interested in this data, you can run tcpserver with the "-q" parameter instead of "-v" and omit the log directory. You should do one or the other because tcpserver writes to standard output (unless you want a console that will constantly receive tcpserver messages).

Access controls with tcprules

The tcprules program generates cdb binary files that are read by tcpserver. tcprules will convert your plaintext file to this special format. To use tcprules, follow the convention:

tcprules cdb tmpfile

In other words, if you wanted to create a vsftpd.cdb from the file /etc/tcprules.d/vsftpd, you would use this command:

# tcprules.d /etc/tcprules.d/vsftpd.cdb /etc/tcprules.d/vsftpd.tmp \
  < /etc/tcprules.d/vsftpd

Notice the redirection. This is because tcprules takes standard input and doesn't actually read a file, so you need to redirect the contents of vsftpd into the program. When this is done, you will have a /etc/tcprules.d/vsftpd.cdb file which tcpserver will use to limit access to the vsftpd service, if so configured.

The syntax of the tcprules rules are fairly simple, but it can also be very powerful. tcp_wrappers is not nearly as extensible as tcprules. For instance, the following rule:

192.168.5.25:deny

will deny all connections coming from the host 192.168.5.25. There are a number of different address rules you can use:

  • $TCPREMOTEINFO@$TCPREMOTEIP, if $TCPREMOTEINFO is set
  • $TCPREMOTEINFO@=$TCPREMOTEHOST, if $TCPREMOTEINFO is set and $TCPREMOTEHOST is set
  • $TCPREMOTEIP
  • =$TCPREMOTEHOST, if $TCPREMOTEHOST is set
  • shorter and shorter prefixes of $TCPREMOTEIP ending with a dot
  • shorter and shorter suffixes of $TCPREMOTEHOST starting with a dot, preceded by =, if $TCPREMOTEHOST is set
  • =, if $TCPREMOTEHOST is set
  • an empty string

tcpserver will always use the first matching rule that it finds. For instance, here are a few other examples:

mdkuser@202.38.12.128:allow
202.38.12.128:deny
127.:deny
:allow

In this example, if $TCPREMOTEINFO is "mdkuser" and $TCPREMOTEIP is 202.38.12.128, the connection will be allowed. If $TCPREMOTEIP is 202.38.12.128, the connection will be refused. This allows connections from mdkuser@202.38.12.128, but no other connections (ie. any connections that do not pass $TCPREMOTEINFO as "mdkuser" will be refused). Finally, all other connections from anywhere will be allowed, except connections from 127.0.0.1 which are denied. As you can see, the global commands that would apply to every system (like :allow or :deny) should be placed last.

You can use some shortcuts for address ranges as well. For instance, 10.0.0.5-38:accept will accept connections for any IP from 10.0.0.5 to 10.0.0.38. A range like 10.0-5.:accept will accept all connections from IPs in the 10.0.x.x, 10.1.x.x, 10.2.x.x, and up to 10.5.x.x. address ranges.

Finally, you can also enhance your instructions beyond just allow and deny. One of these access controls must be first, but behind them you can place environment variables which will be passed on to the tcpserver session and subsequently to the program being called. For instance:

10.0.5.:allow,RELAYCLIENT=""

will set the environment variable $RELAYCLIENT to an empty string on all connections from 10.0.5.x; it will also allow the connection. If this were a tcprules file for the qmail SMTP daemon, this would tell qmail to allow relaying for this address range. You can add as many variables as you like, separated with a comma, like:

10.0.5:allow,RELAYCLIENT="",SOMEOTHERVAR="foo"

This would set $RELAYCLIENT to an empty string and $SOMEOTHERVAR to "foo".

You can also use the tcprulescheck program to check your rules. You must set the environment variables that tcpserver would set based on the incoming connection. For example, let's assume that your vsftpd.cdb file contained the rule "10.0.5.25:deny" and you wanted to make sure that tcpserver would in fact deny this connection. You would use:

# TCPREMOTEIP="10.0.5.25" tcprulescheck /etc/tcprules.d/vsftpd.cdb
rule 10.0.5.25:
deny connection
# TCPREMOTEIP="10.0.5.24" tcprulescheck /etc/tcprules.d/vsftpd.cdb
default:
allow connection

As you can see, two IPs were tested: the first was one explicitly denied and tcprulescheck showed us that tcpserver would deny the connection, and allow one from the IP address 10.0.5.24. Now, assume that you had a default rule of ":accept,SOMEVAR="foo"" at the end of the same file. If you now executed the same test, you would instead get:

# TCPREMOTEIP="10.0.5.25" tcprulescheck /etc/tcprules.d/vsftpd.cdb
rule 10.0.5.25:
deny connection
# TCPREMOTEIP="10.0.5.24" tcprulescheck /etc/tcprules.d/vsftpd.cdb
rule :
set environment variable SOMEVAR=foo
allow connection

Controlling supervised services

To start the svscan daemon, which will start all of the services in /service, use the supervise initscript, ie:

# service supervise start

This will start all of your supervised services. Likewise, issuing the stop argument will stop every supervised service. In this respect, supervise works just like xinetd or inetd; if you stop the super server, you stop everything. This works slightly different than the intended use for supervise-scripts. The author, Bruce Guenter, wrote supervise-scripts to run out of init, so they would start before any other service. Instead, with the Mandrake-specific packages on rpmhelp.net, supervise is started with the supervise initscript, instead of being called from init. It is called early in the boot sequence, but not as early as if it were called via init. This is to allow the network and firewall rules to be established prior to starting supervise.

supervise offers far more flexibility than either xinetd or inetd. If you want to keep all services running but take one down for maintenance, you can do so. You do not have to stop everything in order to stop one service. To do this, you will use some of the scripts in the supervise-scripts package. For instance, let's assume you are running vsftpd, cvspserver, and rsync under supervise and you want to shut down the rsync service. You would use:

# svc-stop rsync

provided that the directory name is /service/rsync. To restart the service, use:

# svc-start rsync

You can also determine the status of a running service very quickly. The two scripts, svc-isup and svc-isdown will return an errorlevel depending on whether or not the specified service is running. svc-isup will return an errorlevel of 0 if the service is running, and 1 if it is not. svc-isdown will do the opposite: 0 if the service is not running, 1 if it is.

Now, to add and remove services from /service, you will want to use the svc-add and svc-remove scripts. There is no reason that you need to manually create symlinks in this directory with these two scripts available. svc-add will add any service to /service. By default, if the service to be installed resides in /var/service, you can just use the service name; for example if you want to install /var/service/rsync you can just use:

# svc-add rsync

However, if the service you want to install resides in, for example, /var/qmail/supervise, you will need to provide the full path:

# svc-add /var/qmail/supervise/qmail-pop3d

If you want to install a service without starting it immediately, you can use the "-d" option with svc-add. This is similar to using "chkconfig --add". For instance:

# svc-add -d rsync

You could then use "svc-start rsync" when you were ready to start the rsync service.

To remove a service from supervise control, use the svc-remove script. This will first remove the symbolic link from /service and then stop any supervise tasks that are running on the service. For example:

# svc-remove rsync

Final Notes

So why would you want to use tcpserver instead of xinetd? For one, it is written by D. J. Bernstein, the same author of qmail and djbdns, so this means careful attention was paid to security. tcpserver also offers a number of features that can give you full control over your running services. By running the services via a run script, instead of a single command line, you can customize the service's environment to your heart's content. Using the tcprules, you have full flexibility with your environment by being able to pass environment variables to the run script based on the connecting IP address. The ability to do DNS and reverse-DNS lookups will certainly ease the minds of those paranoid or very security-conscious.

All in all, Bernstein's tools have yet to fail me, but this is a personal opinion. Having used qmail and djbdns, each making use of tcpserver, in production for a few years now, I am confident to say that the overheard incurred by using tcpserver is negligible, and the flexibility provided is immense. And, of course, there is the security aspect. While I don't think it's feasible to run all services via tcpserver (ie. running Apache via tcpserver is probably just as efficient as running it via xinetd; you're better off running it standalone), a number of services that are currently being used can effectively run under tcpserver. I cite again the vsftpd example mentioned earlier... using tcpserver allowed me to use a very secure FTP server, while maintaining the virtual host support that previously was only available in ProFTPD. Not to knock ProFTPD, as it is a very good FTP server, but it has also had a checkered past. With tcpserver, I achieved the best of both worlds.

As well, for my latest project (Annvix), I am using supervise to exclusively handle all services rather than initscripts, and tcpserver rather than xinetd or inetd. So far, the reliability has been top-notch.

Resources