An Unofficial Xinetd Tutorial
by curator, The Shmoo Group.

Contents:


What xinetd does

xinetd is a secure replacement for inetd, and a more efficient replacement for inetd and tcp_wrappers. It sports a number of features that make it a good choice for securing a server. These include access control (based on source address, destination address, and time), extensive logging, and the ability to bind services to specific interfaces. This tutorial will attempt to give an administrator the necessary tools to install, configure, and maintain xinetd.

Being a "secure replacement for inetd", xinetd attempts to do everything that inetd does, only securely. This means that, like inetd, it is a super-server. Both xinetd and inetd read in their configuration files which are basically a list of IP services to listen to. The super-servers "listen" on the ports defined by those listed in configuration files for connection attemps on those ports. When it receives a connection on a port it thinks it has a service for, it attempts to start the requisite server. There are exceptions to this scheme, mostly for single-threaded servers, where the super-servers simply start the server, which then takes care of service requests until the server dies.

Where inetd and xinetd begin to differ is xinetd's support for RPC services; it isn't great. The author of xinetd suggests that an admin running an rpc service do so from inetd. xinetd and inetd can cohabitate quite peacefully. Another thing that differs is the configuration files; the two are mutually incompatible. xinetd's conf file contains more information than inetd does in order to handle the additional security parameters.


Installation

First you need to download the latest source from xinetd.org to some convenient directory (ie., /usr/local/src). Once downloaded, expand the archive and change to its directory. (If running Mac OS X(S), there is a section later in this tutorial that has additional to assist you in this process.)

It is possible to compile xinetd with libwrap support by adding the --with-libwrap flag to ./configure. This allows xinetd to use the hosts.{allow | deny} mechanism. To do so, you'll need to have tcp_wrappers installed, and the requisite libraries in place. The decision to do so is up to the individual admin. This option exists mostly to help those more comfortable with the wrapper mechanism to more easily configure xinetd. We suggest that you do not compile the software with libwrap support unless you have the need; it is best and most flexible to do without it.

Further options are available (such as install paths, ipv6 support, etc) and you should read the install docs to determince which of these settings is correct for you.

Once you've run ./configure with the options you need, run "make" followed by "make install" as root. Assuming xinetd makes and installs with no errors, the next thing to do is configure it. If it doesn't, you may wish to subscribe to the xinetd maillist by sending a message to majordomo@synack.net with a body of "subscribe xinetd".


Configuration file basics

xinetd ships with a perl script (installed in the same directory as the xinetd binary) that conveniently converts an inetd.conf into a xinetd.conf. It may be invoked as "/usr/sbin/xconv.pl < /etc/inetd.conf > /tmp/xinetd.conf", where "/usr/sbin" is your path to the xinetd executable.

xconv.pl will try to make a xinetd.conf from your original inetd.conf as best it can, but most admins will want (read: need) to modify the xinetd.conf it generates. For instance, many BSD's (including Mac OS X [Server]) require that each service have a "groups = yes" setting.

The defaults section

xconv.pl by default makes a defaults section that looks something like this:

	defaults
	{
	        #The maximum number of requests a particular service may handle
	        # at once.
	        instances   = 25

	        # The type of logging.  This logs to a file that is specified.
	        # Another option is: SYSLOG syslog_facility [syslog_level]
	        log_type    = FILE /var/log/servicelog

	        # What to log when the connection succeeds.
	        # PID logs the pid of the server processing the request.
	        # HOST logs the remote host's ip address.
	        # USERID logs the remote user (using RFC 1413)
	        # EXIT logs the exit status of the server.
	        # DURATION logs the duration of the session.
	        log_on_success = HOST PID

	        # What to log when the connection fails.  Same options as above
	        log_on_failure = HOST RECORD

	        # The maximum number of connections a specific IP address can
	        # have to a specific service.  
	        per_source  = 5
	}

Here we can begin to see some of the basic characteristics of a conf file. Sections have the general form:

	sectiontypeorname
	{
	   <attribute> <assign_op> <value><value> ...
	   <anotherattribute> <assign_op> <value><value> ...
	   ...
	}

Lines beginning with "#" are comments. Whitespace lines are ignored. There can be only one defaults section in a xinetd.conf file. In the defaults section the assign_op is only a "=".

The defaults section, as its name implies, specifies default settings for the services specified elsewhere in the file. The defaults section can contain a number of attributes: log_type, log_on_success, log_on_failure, only_from, no_access, passenv, instances, disabled, and enabled. Of these, only log_type and instances do not display a "cumulative effect". The cumulative effect is the ability to specify an attribute multiple times within the section.

The first attribute we see here is instances (instances = 25). It specifies the maximum number of requests any service may handle at one once. This setting says that for any service that doesn't specify it's own instances attribute, that service will be limited to 25 connections. The next attribute is log_type (FILE /var/log/servicelog), which specifies the log type (either FILE or SYSLOG) and where specifically to log to. For the FILE log type, this means the full path to the log file, and for the SYSLOG log type it syslog facility and optionally the syslog level.

The next two attributes, log_on_success and log_on_failure, deal with what is to be logged when a server is started and exited. The log_on_success attribute accepts five different values: PID (log of the pid xinetd uses to spawn the server), HOST (logs the remote host's IP address), USERID (logs the userid of the remote user as returned by remote identd service), EXIT (logs the exit status of the server when it exits), and DURATION ( logs the duration of the server session). Here, only the host's address and the server's pid are logged by default (log_on_success = HOST PID). The log_on_failure attribute comes into play when either the server could not be started due to lack of resources, or access was denied via the rules in the conf file. It has four valid values: HOST (again, the remote host's IP address), USERID (same as log_on_success), ATTEMPT (simple acknowledge that a failed attempt was made), and RECORD (grabs as much info about the remote end as is possible). In this default xinetd.conf, the remote machine's address as well as any other information it can garner are logged (log_on_failure = HOST RECORD).

The last attribute shown in the default configuration is the per_source attribute. This specifies the maximum number of connections for any one remote address to a service. It can either be an integer, or the special value "UNLIMITED" which is at is says, an unlimited number of connections. Here it defaults to a maximum of 5 connections per server per IP address (per_source = 5).

As you can see, there are a number of attributes not accounted for in the default configuration. I'll briefly discuss the missing attributes here, as most also apply to the individual service sections we'll get into later. To explicitly allow and deny addresses and networks, xinetd provides two attributes called only_from, and no_access. There are a number of ways to specify an ip address or range of addresses, including dotted decimal quads, CIDR notation, and factorized quads.

The disabled and enabled attributes are meant to be lists of service names (see the next section) that are enabled and disabled. If enabled is specified, then any services not listed as values are considered. The same is true with the disabled attribute, with the unlisted services being considered enabled. Should a service be listed as a value for both enabled and disabled, the disabled attribute overrides. These two attributes are only available in the defaults section.

The remaining attribute available in the defaults section is passenv. The values for passenv are items in a list of environment variables from xinetd's environment to send to the server when it gets instantiated.

With this brief intro to the defaults section complete, we'll move on to the service sections. But never fear, we'll return to the defaults section and its attributes later with the sample configurations.

Services sections

The services section define the individual services to be started by xinetd and how they are to be started. Their general form is

	service <servicename>
	{
	   <attribute> <assign_op> <value> <value> ...
	   <anotherattribute> <assign_op> <value> <value> ...
	   	...
	}

Like the defaults section, the services sections have a number of attributes that can be specified: type, flags, socket_type, protocol, wait, user, group, instances, nice, server, server_args, only_from, no_access, access_times, log_type, log_on_success, log_on_failure, rpc_version, rpc_number, env, passenv, port, redirect, bind, interface, banner, banner_success, banner_fail, per_source, cps, max_load, and groups. That may seem like a lot, but realize that you'll need about 7 of them to setup a basic service.

xconv.pl makes services sections that look like this:

	service ftp
	{
	        flags       = REUSE NAMEINARGS
	        socket_type = stream
	        protocol    = tcp
	        wait        = no
	        user        = root
	        server      = /usr/libexec/ftpd
	        server_args = ftpd -l 
	}

	service telnet
	{
	        flags       = REUSE NAMEINARGS
	        socket_type = stream
	        protocol    = tcp
	        wait        = no
	        user        = root
	        server      = /usr/libexec/telnetd
	        server_args = telnetd
	}

xconv.pl will only translate those services in the inetd.conf that are uncommented, and those it does translate are set to their most basic and compatible mode.

The first thing you'll probably notice here are that the services sections are split into individual service configurations. The servicename is a unique name for a service you wish to configure in the following section. This servicename is what is used to look up the service information in /etc/services (or equivalent).

The flags attribute can generally be left as is. The REUSE value is generally a good thing and should be left unless you have a specific reason to remove it. NAMEINARGS specifies that the first value in the server_args attribute will be used as the first argument when starting the service specified. This is most useful when using tcpd; you would specify tcpd in the server attribute, and give it the service ("ftpd -l") in the server_args attribute. The flags attribute is optional. The default configurations should work fine in most cases.

The attributes socket_type, protocol, wait, and user attributes are all synonymous with their inetd counter parts. In all cases I've seen, these can be left alone. Of these, protocol is optional.

We've already talked about server and server_args a bit. The server attribute is simply the full path to the server's executable, very much a required field. Server_args is a list arguments to be passed to the above executable. Though the xconv.pl application lists the server's executable name in this attribute, it is not necessary, nor particularly desirable (as stated by the xinetd man pages). However, there doesn't seem to be any specific problems with leaving the attribute as xconv.pl sets it. If you wish to delete the name from the attributes, remember to remove the NAMEINARGS flag from the flags attribute as well. The server_args attribute must included in a service definition, even if it is left blank.

A minimally configured version of the example ftp service would look as follows:

	service ftp
	{
	        socket_type = stream
	        wait        = no
	        user        = root
	        server      = /usr/libexec/ftpd
	        server_args = -l 
	}

If you happen to be using rpc services, protocol, rpc_version, and rpc_number (if it's not listed in /etc/rpc or your equivalent) attributes are also mandatory.

For the most part these are the attributes that are required to properly configure a service under xinetd. There are, however, two exceptions: if you're running a BSD, or if the service you're configuring isn't listed in /etc/services (or equivalent). Most BSD's seem to require the addition of the groups (groups = yes) attribute to the service configuration. If you happen to be configuring a service that isn't in /etc/servicesport = 22, for sshd). There are some additional variations when dealing with xinetd's internal's services, but we'll discuss those later.

This is, of course, not the end of the story; we want to not only have functioning services, we want to control access to these services. To this end, xinetd provides us with a number of pertinent service attributes: instances, nice, only_from, no_access, access_times, per_source, cps, and max_load.

Instances accepts an integer as it's value, and as noted in the previous defaults section, specifies the maximum number of simultaneous connections to the particular service. As with any of these values, setting this attribute in the service definition should override whatever is in the defaults section. Nice is related to the unix nice command. It takes an integer that specifies the services process priority. The max_load attribute accepts a floating point value, and specifies the load at which the server will stop accepting connections, based on a one-minute cpu load average. Due to it's OS dependency, this only works on Linux and Solaris right now. Then there's the cps attribute, which also takes an integer and is used to rate limit (in connections per second) a service. The last of these quantitative limiters is the per_source attribute. It takes an integer and set the limit on the maximum number of connections a single host may have to the specified service.

The attributes only_from and no_access are very much related too each other, in as much as they take the same values, and are of supplimentary function. The accept a list of ip addresses, network names (via /etc/networks or equivalent), host/domain names (via reverse lookup), or networks (in CIDR notation). If both only_from and no_access are specified for a service, the best fit match is used (ie, a host is a better match than a network, is better than a network with a larger subnet). The no_access attribute takes precedence over only_from in case of a complete duplication. If you include either attribute, but leave either blank, they disallow all addresses. The last access control attribute is access_times. This accepts time intervals in HH:MM-HH:MM 24-hour notation. Access is granted during these intervals.

The remaining attributes have to do with logging (and have already been discussed in the defaults section), and some nuances of service configuration. Some are covered in a later section, but for more information on these handfull of attributes, I would recommend reading the man pages.


A Simple Configuration with little access control

# This is a modified and cleaned up version of a xinetd.conf 
# originally created by xconv.pl.
# The defaults section sets some information for all services
defaults
{
        #The maximum number of requests a particular service may handle
        # at once.
        instances   = 25

        # The type of logging.  This logs to a file that is specified.

        # Another option is: SYSLOG syslog_facility [syslog_level]
        log_type    = FILE /var/log/servicelog

        # What to log when the connection succeeds.
        # PID logs the pid of the server processing the request.
        # HOST logs the remote host's ip address.
        # USERID logs the remote user (using RFC 1413)
        # EXIT logs the exit status of the server.
        # DURATION logs the duration of the session.
        log_on_success = HOST PID

        # What to log when the connection fails.  Same options as above
        log_on_failure = HOST RECORD

        # The maximum number of connections a specific IP address can
        # have to a specific service.  
        per_source  = 25
}

service ftp
{
        socket_type = stream
        wait        = no
        user        = root
        server      = /usr/libexec/ftpd
        server_args = -l 
}

service nntp
{
        socket_type = stream
        wait        = no
        user        = usenet
        server      = /usr/libexec/nntpd
        server_args =  
}

service telnet
{
        socket_type = stream
        wait        = no
        user        = root
        server      = /usr/libexec/telnetd
        server_args =  
}


A More Complicated Configuration with Access Control

# This is a modified and cleaned up version of a xinetd.conf  
# originally created by xconv.pl.
# The defaults section sets some information for all services
defaults
{
        #The maximum number of requests a particular service may handle
        # at once.
        instances   = 25

        # The type of logging.  This logs to a file that is specified.
        # Another option is: SYSLOG syslog_facility [syslog_level]
        log_type    = FILE /var/log/servicelog

        # What to log when the connection succeeds.
        # PID logs the pid of the server processing the request.
        # HOST logs the remote host's ip address.
        # USERID logs the remote user (using RFC 1413)
        # EXIT logs the exit status of the server.
        # DURATION logs the duration of the session.
        log_on_success = HOST PID

        # What to log when the connection fails.  Same options as above
        log_on_failure = HOST RECORD

        # The maximum number of connections a specific IP address can
        # have to a specific service.  
        per_source  = 25
        
        # Disabled nntp 
        disabled = nntp  
        
        #Whatever you do, don't let the evil bastards in
        no_access = .evilbastards.com
}

service ftp
{
        socket_type = stream
        wait        = no
        user        = root
        server      = /usr/libexec/ftpd
        server_args = -l
        #Allow access from the local network (ie, 192.168.0.0/24)
        only_from   = 192.168.0.0/24
        #And from two remote locations
		only_from   = 10.1.1.2 roadwarrior.sampleconfig.com
		#But not from the evil HR director's subnet
		no_access   = 192.168.0.128/27
		log_on_success = PID HOST EXIT DURATION
		log_on_failure = ATTEMPT HOST RECORD
		
}

service nntp
{
        socket_type = stream
        wait        = no
        user        = usenet
        server      = /usr/libexec/nntpd
        server_args =  
        #Allow access from the local network (ie, 192.168.0.0/24)
        only_from   = 192.168.0.0/24
        #And only during working hours
        access_times = 08:00-17:00
        #And only 5 people at a time
        instances	= 5
        #And only 5 connections per second
        cps 		= 5
        #And not under load
        max_load 	= 2.9
        #And be low priority
        nice 		= 15
 
}

service ssh
{
        socket_type = stream
        wait        = no
        user        = root
        server      = /usr/libexec/sshd
		#It's not listed in my /etc/services
        port = 22
        server_args =  -i
        #Allow access from the local network (ie, 192.168.0.0/24)
        only_from   = 192.168.0.0/24
        #And from two remote locations
		only_from   = 10.1.1.2 roadwarrior.sampleconfig.com
		log_on_failure = ATTEMPT HOST RECORD

}


Day to day use of xinetd (or updating the xinetd.conf)

In the life of every service comes a day when it must be reconfigured for some reason. Sometimes it's because you've installed another service, or wish to enable one that's been previously disabled, or may you've got an evil bastard trying to beat your imap server to dust. Whatever the reason, it is generally disadvantageous to have to kill all the processes that xinetd has started to bring up a new configuration, especially on a busy server. Fortunately, the xinetd coders gave us a way around this. To bring up a new configuration, make your modifications to xinetd.conf (after making a backup copy of it, of course). Then find the PID for xinetd (ps -aux | grep xinetd). With the xinetd PID in hand, use the kill command to send it a SIGUSR1 or SIGUSR2 signal (kill -USR1 <pid>). Sending it SIGUSR1 will cause a soft reconfiguration, meaning that it reads the new config and all new connections are applied against it. If a SIGUSR2 is sent (kill -USR2 <pid>), a hard reconfiguration occurs. This means that not only will the new config be read in, it will be applied immediately; any access controls are applied instantly, as well as any connection throttles. This allows you to immediately kick an evil bastard off one of your servers, or disable a flagging service. One should note, however, if current connections are beyond the bounds of connection throttling settings in the new config when a SIGUSR2 is sent, random connections will be kicked to bring the rate/instances within acceptable parameters.


Port redirection

Amazingly enough, the folks that coded xinetd, have added the ability to redirect ports. That is, you can accept a connection on a port, and redirect that connection to another port, even on another host. This is especially useful in a situation where the machine running xinetd is a firewall, or router. Usually this means the machine has multiple nics in it, each with it's own IP address and subnet. Using port redirection, an machine could accept a telnet session on port 23 of the router/firewall machine and redirect it to port 23 of one of the internal hosts. The service definition to do this would look like the following:

service telnet       
{       
        flags       = REUSE
        socket_type = stream
        protocol    = tcp  
        wait        = no    
        user        = root
        groups      = yes 
        bind		= 10.1.1.1
        redirect    = 192.168.0.3 23
}       

There are a number of things to notice here. There are two new attributes: bind, and redirect. The redirect attribute accepts an ip address (as it is here), or a hostname, followed by a port number. The port number specified need not be the same as that of the service (in other words you could just as easily accept connections via telnet and redirect them to port 80, being http, on some host). One can also redirect to a port on the same machine as that it is being redirected from. The other new attribute is bind. This tells xinetd that this service/rule is bound to the interface with the specified ip. In the above scenario, it is likely that there are two or more nics in the machine, one of which has a configured ip of 10.1.1.1, and the other with an address on the same network (or has some access to) as the host 192.168.0.3. The bind attribute is by no means required; it is only shown here because this is a common configuration and use for the redirect attribute.


Additional info for Mac OS X (Server)

What follows are instruction specifically for users of Mac OS X (Server and Client). As the OS is new, and currently in a state of flux, consider these instrustions as a helpful guide which may be subtley flawed for now. As the OS continues towards release, and we become more familiar with it, we will update this section accordingly. If you have suggestions, or find errors, please feel free to share .

There are differences in the installation of xinetd on Mac OS X Server 1.x and Mac OS X. When these differences are apparent, it will be so noted. Other unix installs will be similar to MOSX(S), except the startup methods, and their mileage will vary.

Everything that follows needs to be done as root to function as indicated.

Some filenames and paths may need to be changed to protect the innocent.

The compile and install

Download the source to a convenient directory (ie /usr/local/src/).

Decompress the archive and copy the necessary config files from /usr/libexec:

	tar xzvf xinetd-N.N.N.N.tar.gz
	cd xinetd-N.N.N.N/
	cp /usr/libexec/config.* ./ 

Then download the necessary patch from Macsecurity and place it in the xinetd-N.N.N.N directory.

Then do the following:

	cd /path/to/xinetd-N.N.N.N
	tar zxvf xinetd-N.N.N.N-osx.patch.tar.gz
	patch -p 1 -l < xinetd-N.N.N.N-osx.patch
The next thing to do is run ./configure with whatever options you'd like (libwrap support, different command paths). If compiling with libwrap support, see the section of tutorial called "Compiling with libwrap support". Typical for MOSX(S):

	./configure --sbindir=/usr/sbin --mandir=/usr/share/man 

Hopefully this will proceed with no errors. Assuming this is so, you need to make two minor modifications to two Makefiles. You'll want to do these modifications with vi, as pico has a tendency to chop long lines in a way that breaks the Makefiles.

In /path/to/xinetd-N.N.N.N/Makefile find a line that starts out "CFLAGS =" and insert "-traditional-cpp" into the list of arguments. Then, find a section that begins with makelibs: Append a line after that which contains ; done. Type in $(RANLIB) libs/lib/*.a

This section should now look as follows:

makelibs: 

for lib in $(MANDATORY_LIBS) ; do \
	( cd libs/src/$$lib ; make install "INSTALL=$(INSTALL_CMD)"
	"DEFS=$(LIB_DEFS)" RANLIB=true "CC=$(CC)" DEBUG=-O ) \
	; done
	$(RANLIB) libs/lib/*.a

In /path/to/xinetd-N.N.N.N/xinetd/Makefile again find the "CFLAGS =" line, and add "-traditional-cpp" to the list of arguments.

Follow this up with the following in /path/to/xinetd-N.N.N.N/:

make
make install

xinetd is now installed. All that remains is to configure it, and that has already been discussed at length in this document. Once you think you have a good xinetd.conf file created and in place, it is suggested that you run xinetd from the command line with the '-d' debug flag. This will help you greatly in solving any problems that may arise. Also, remember to add the "groups = yes" attribute to each service definition.

Starting the daemon at startup

Most admins will want to start xinetd from startup. Here again, there is a difference between Mac OS X Server, and Mac OS X.

In Mac OS X Server, there are a few ways to do this. One way is to edit the /etc/1700_IPServices file and add a line for xinetd (look at the lines for inetd for a clue on how to proceed). It is possible to run both inetd and xinetd at the same time, though both may not run the same services (FIFO).

In Mac OS X, the way to do it, is to create a StartupItem for xinetd. To do this follow these directions:

cd /System/Library/StartupItems
cp -R IPServices SecureIPServices
cd SecureIPServices
mv IPServices SecureIPServices

Edit the file SecureIPServices. Delete everything after 'ConsoleMessage "Starting TCP/IP services"'. Modify the remaining text to appear as follows, taking care that the stated paths are the actual paths to the specified file(s).

##
# Run the Internet super-server.
##

. /etc/rc.common

ConsoleMessage "Starting Secure TCP/IP services"

/usr/local/sbin/xinetd

Edit StartupParameters.plist to look like the following:

{
Description     = "Secure Internet services";
Provides        = ("SecureSuperServer"
		);
Requires        = ("Resolver");
Uses            = ("NetworkTime");
OrderPreference = "None";
Messages =
{
start = "Starting secure internet services";
stop  = "Stopping secure internet services";
};
}

That's it. Xinetd will now startup at boot time.

Compiling with libwrap support

If you're compiling with libwrap (./configure --with-libwrap) support, then you'll need to make sure the necessary libraries are installed in there proper place(s). Look for tcpd.h in /usr/include or /usr/local/include, and libwrap.a in /usr/lib or /usr/local/lib; if they're not there, you'll need to get them there. How you do this differs between the two versions of Mac OS X.

For Mac OS X Server, follow these directions (excerpted from Joshua Marker's article on installing SSH1 on Mac OS X server on Stepwise):

cd /System/Developer/Source/Commands/tcp_wrappers/
make RC_ARCHS=ppc install
mkdir -p /usr/local/lib
mkdir -p /usr/local/include
cp /tmp/tcp_wrappers/Release/usr/local/lib/libwrap.a \
/usr/local/lib/libwrap.a
ranlib /usr/local/lib/libwrap.a
cp /System/Developer/Source/Commands/tcp_wrappers/tcp_wrappers/tcpd.h \
/usr/local/include/

For Mac OS X, these are the directions (excerpted from Joshua Marker's article on installing SSH1 on Mac OS X on Stepwise):

wget ftp://ftp.stepwise.com/pub/macosx/unix/tcp_wrappers-7.6-4.tar.gz
gnutar -xzf tcp_wrappers-7.6-4.tar.gz
cd tcp_wrappers-4

make RC_ARCHS=ppc install
mkdir -p /usr/local/lib
mkdir -p /usr/local/include
cp /tmp/tcp_wrappers/Release/usr/local/lib/libwrap.a \
/usr/local/lib/libwrap.a
ranlib /usr/local/lib/libwrap.a
cp tcp_wrappers/tcpd.h \
/usr/local/include/

Beware that if the xinetd configure scripts do not find the files, and you've compiled with libwrap support, xinetd will not function properly. Remember to test functionality before relying upon the new daemon.


Parting words

While we have gone through many of the available options, configurations, and features of xinetd, not all options are represented, or fully explained. For further information, see "man xinetd.conf" and "man xinetd". If you need further help, seek out the authors of xinetd, and join the xinetd mailing list.