Linking to pages and resources on this site is encouraged, but the links MUST be placed on a publically-accessible page. Placing links behind any form of login or access restriction is strictly forbidden.

Running dovecot from inetd: overcoming weird-ass bug

Warning: this page is not the whole story. Developments are ongoing - see Debian bug report #377154. See also here. Use this info at your own risk!

NOW it is fixed! Patch applied to tcp-wrappers and incorporated into Debian... the "bug" was in the documentation; both the dovecot wiki and generic tcp-wrappers documentation are broken, and the only way to get the desired behaviour is to patch tcp-wrappers as described. Skip the waffle

I have just been struggling like a bastard to get a dovecot IMAP server (currently at version 0.99.14-1sarge0) to run from inetd. I want to run it from inetd so that I can take advantage of tcpwrappers and control access through /etc/hosts.allow and /etc/hosts.deny. The control available through dovecot's native configuration is too coarse: your only options are to allow access from only one IP, or allow access from every IP.

According to the dovecot wiki this ought to be a simple process, simply add the following lines to /etc/inetd.conf (edited to reflect the file locations in Debian):

imap           stream  tcp     nowait  root    /usr/sbin/tcpd /usr/lib/dovecot/imap-login
imaps          stream  tcp     nowait  root    /usr/sbin/tcpd /usr/lib/dovecot/imap-login --ssl

Unfortunately this doesn't work. I can telnet to port 143, telnet connects OK, and ps axf on the server shows that /usr/lib/dovecot/imap-login has been started by inetd. I then see a line like localhost imap-login[2820]: refused connect from ( appear in /var/log/syslog, and the telnet session dies with Connection closed by foreign host. Fuck knows why. /etc/hosts.{allow,deny} are set to allow connections from, and they work as expected where other services are concerned.

On further experimentation, I found that replacing /usr/lib/dovecot/imap-login with /usr/lib/dovecot/imap in /etc/inetd.conf did allow me to log in. But since this bypasses the login management part of the process, the IMAP session is logged in automatically, without needing to issue a login command - and it's logged in as root, no matter what. This is not exactly the desired behaviour.

My next experiment was to build the dovecot packages from source, having edited the code for the login process to spew out all sorts of "It got to here!" markers. I installed the hacked package and to my amazement none of the markers made an appearance. Apparently not even the first line of main() was being executed.

Well, this was bloody daft. I decided to try a real sledgehammer of an experiment. I deleted the /usr/lib/dovecot/imap-login binary entirely. It didn't make a blind bugger of difference. I still got exactly the same response on the client telnet session, and ps axf on the server still showed that /usr/lib/dovecot/imap-login had been started by inetd. Even though the fucking file didn't exist any more. How the flying fuck can that happen?

Well, my guess is that there's a bug - or maybe it's a feature - in tcpd/inetd that the writer of the dovecot wiki page didn't know about, that makes it behave in a really weird fashion when the name of the executable has a - in it. (Wrong - see below.) Unfortunately, it's not possible to simply rename the executable to something without a - in it. For one thing, the program logic depends on the executable name starting with imap-; more importantly, renaming the executable would not result in the desired behaviour when upgrading the package.

Fortunately, there does exist a reasonably robust workaround. This is to create a directory /usr/local/lib/dovecot and then create a symlink named imap in it to /usr/lib/dovecot/imap-login:

ln -s /usr/lib/dovecot/imap-login /usr/local/lib/dovecot/imap

and edit the entries in /etc/inetd.conf accordingly:

imap           stream  tcp     nowait  root    /usr/sbin/tcpd /usr/local/lib/dovecot/imap
imaps          stream  tcp     nowait  root    /usr/sbin/tcpd /usr/local/lib/dovecot/imap --ssl

And hey presto, it now all works as it should do. Well, nearly. There is one small problem. We are now no longer the IMAP servers who say "Ni!"... er, hang on... ah, yes. The imap entry in /etc/hosts.{allow,deny} sets the access control for BOTH imap and imap-ssl services. The imaps entry is ignored. Which is very strange.

See also Debian bug report #377154.

Perhaps the author of this wishlist bug report has been having a similar problem?

Furthermore, I don't think the bug is necessarily confined to Debian. My Google searches in search of a solution revealed very little, but I did find a few people asking "how do you run dovecot from inetd? PLEASE how do you run dovecot from inetd?", not getting a working answer, and eventually giving up and using other methods of access control, such as iptables. (Which is fair enough, but I prefer the belt-and-braces approach of having tcp-wrappers as a second line of defence behind iptables.) These people seemed to be using a variety of different distributions. However, all my machines are running Debian, so I can't confirm this suspicion without a lot more hassle than I'm prepared to go through :-)

Update: I've been poking through the source code of tcpwrappers, and I've figured out what's going on, I think...

hosts_access.c matches the entries in /etc/hosts.{allow,deny} against the name of the daemon running the service. This daemon name is derived in tcpd.c by truncating argv[0] at the final / .

In the case of dovecot, the daemon responding to the incoming imap (or pop3) connection is named imap-login (or pop3-login). Therefore, unless the entries in /etc/hosts.{allow,deny} use the daemon name imap-login instead of the obvious imap or imap2 that one might deduce from reading /etc/services, they will not match the daemon name provided by tcpd.c.

My symlink workaround works because it effectively renames the daemon executable to imap and therefore it does match the entries in /etc/hosts.{allow,deny}. However, as I later found, it is still not a complete solution as it does not produce the required result where IMAP-over-SSL is concerned. dovecot uses the same daemon to handle both IMAP and IMAP-over-SSL connections, passing it the --ssl parameter in the case of an IMAP-over-SSL connection. Since the daemon name is the same in both cases, permissions for both services are set by the entry for the plain IMAP service. It is not possible to, for instance, set more restrictive permissions for IMAP and less restrictive ones for IMAP-over-SSL.

Similar considerations apply to dovecot's secure and insecure pop3 services, or indeed to any package which uses the same daemon to provide more than one service according to a command-line parameter or the port of the incoming connection or whatever.

It is not possible to extend my symlink workaround to cater properly for IMAP-over-SSL by creating another symlink named imaps without modifying dovecot, because dovecot works out what service to provide based on the stem of the daemon name, and only recognises imap and pop3. The workaround also completely fails to address the problem of what happens if any other packages behave similarly to dovecot. IIRC the courier-imap daemon has a not dissimilar problem.

Now, there is much generic Linux documentation on the net that says that the daemon names in /etc/hosts.{allow,deny} should correspond to those in /etc/services. Certainly the dovecot wiki entry referred to above seems to be written on that assumption, and there are many other documents written similarly. However, none of the Debian-specific documentation links the daemon names in /etc/hosts.{allow,deny} and /etc/services, and grepping the tcp-wrappers source code reveals no references to /etc/services, or to getservbyport() or its relations. But there is nothing in the Debian changelogs to indicate that support for /etc/services has been removed from the Debian package for any reason - no indication as to why the Debian package doesn't behave as the generic, non-Debian-specific information suggests it might. Indeed, it seems that the upstream tcp-wrappers has never supported /etc/services, and all this generic documentation is simply continuing to promote a widespread fallacy.

Since I couldn't find any reason for it not to be there, and if it was there it would fix the problem, I decided to add support for /etc/services to tcp-wrappers. I also added support for matching against port numbers as opposed to service names. I submitted my patch to the developer, but he reckoned the /etc/services support might break other packages and recommended I stripped it down to just the port-number matching, which I did. The patch was accepted and incorporated into tcp-wrappers from version 7.6.dbs-10. The 7.6.dbs-11 version is here: match_port_7.6.dbs-11.gz

Debian users of course can simply install a suitably recent version of tcpd. Users of lesser distributions :-) will need to edit the paths in the patch to conform to their particular build tree. It only hacks one source file and one man page, so it won't be too hard to figure out what to do :-)

With this patch applied the appropriate entries in /etc/inetd.conf are:

imap    stream  tcp     nowait  root    /usr/sbin/tcpd  /usr/lib/dovecot/imap-login
imaps   stream  tcp     nowait  root    /usr/sbin/tcpd  /usr/lib/dovecot/imap-login --ssl

with the entries in /etc/hosts.{allow,deny} along the lines of this excerpt from /etc/hosts.allow:

# imap
143:, 192.168.1.
# imaps
993: ALL

This allows dovecot to run from inetd in the manner that the broken documentation suggests, with access limited according to the entries in /etc/hosts.{allow,deny}, separate control over imap and imaps connections, and with the login process not running as root.

Did you find this page helpful?

Back to sarge page

Back to Pigeon's Nest

Be kind to pigeons

Valid HTML 4.01!