Follow @Openwall on Twitter for new release announcements and other news

This file describes the design goals and the reasoning behind some of the design decisions for my tiny POP3 daemon, popa3d.

Why popa3d?

There are lots of different POP3 servers -- with different feature sets, performance, and reliability. However, as far as I know, before I started the work on popa3d, there had been only one with security as one of its primary design goals: qmail-pop3d. Unfortunately, it would only work with qmail, and only with its new maildir format. While both qmail and maildirs do indeed have some advantages, a lot of people continue running other MTAs, and/or use the older mailbox format, for various reasons. Many of them need a POP3 server.

The design goals.

Well, the goals themselves are obvious; they're probably the same for most other POP3 servers as well. It's their priority that differs. For popa3d, the goals are:

1. Security (to the extent that is possible with POP3 at all, of course).

2. Reliability (again, as limited by the mailbox format and the protocol).

3. RFC compliance (slightly relaxed to work with real-world POP3 clients).

4. Performance (limited by the more important goals, above).

Obviously, just like the comments indicate, none of the goals can be met completely, and balanced decisions need to be made.

Security.

First, it is important that none of the popa3d users get a false sense of security just because it was the primary design goal. The POP3 protocol transmits passwords in plaintext and thus, if you care about the security of your individual user accounts, should only be used either in trusted networks or tunneled over encrypted channels. There exist extensions to the protocol that are supposed to fix this problem. I am not supporting them yet, partly because this isn't going to fully fix the problem. In fact, APOP and the weaker defined SASL mechanisms such as CRAM-MD5 may potentially be even less secure than transmission of plaintext passwords because of the requirement that plaintext equivalents be stored on the server.

It is also important to understand that nothing can be perfectly secure. I can make mistakes. While the design of popa3d makes it harder for those to turn into security holes, this is nevertheless still possible.

Having that said, let's get to the security-critical design decisions.

Privilege management.

Initially, popa3d is started as root to handle a connection. However, it does very little work as root: switching to less privileged UIDs, communication with child processes, and authentication information checks (which often involve accessing shadow or master.passwd files).

The following privilege switches happen during a successful POP3 session, with /etc/shadow authentication:

			 startup as root
				|
			-----------------
			|child		|parent
			v		v
	drop to user popa3d,		still as root,
	handle the AUTHORIZATION	wait for and
	state, write the results, - - >	read the authentication
	and exit			information
					|
			-----------------
			|child		|parent
			v		v
	getspnam(3), crypt(3),		wait for and
	check, write the result, - - >	read the authentication
	and exit (to clean up)		result
					|
					v
					drop to the authenticated user,
					handle the TRANSACTION state,
					possibly UPDATE the mailbox,
					and exit

Trust.

No part of popa3d trusts any information obtained from external sources (that is, the data is never assumed to be of the expected format, and is treated as subject to authorization checks). This includes POP3 commands, mailbox contents, and even popa3d's own less-privileged child process for the AUTHORIZATION state handling.

DoS attacks.

Just like with most other software, there exist ways to cause a Denial of Service, by supplying popa3d with an enormous amount of otherwise valid input. I am aware of the following attacks on popa3d itself:

1. Connection flood. When running in the standalone mode, popa3d does quite a few checks to significantly reduce the impact of such attacks by limiting resource consumption (child processes and logging rate), while still providing full service for other source IP addresses and logging everything that might be important. However, when running from an inetd clone, the handling of these attacks is left up to your inetd and the kernel.

2. Huge mailbox sizes, either in message count or bytes. There are limits in popa3d (see params.h) that are intended to prevent this attack from stopping the entire service. Depending on your disk and other quotas, it may still be possible to stop individual users from getting their mail.

Reliability.

Quoting Dan Bernstein, "the mbox format ... is inherently unreliable".

While popa3d, just like other mail software that deals with mailboxes, doesn't guarantee reliability over system crashes, it still makes sense to talk about its operation on an otherwise stable system.

Interaction with other MUAs.

Similarly to cucipop (but unlike qpopper), popa3d works on the original mailbox file, without copying. However, unlike cucipop, popa3d is able to ensure that the mailbox doesn't get corrupted if another MUA modifies it during the POP session. Before each mailbox access, popa3d checks its timestamp and, if that has changed, determines if that is due to new mail that has just been delivered, or other changes made to the mailbox. In the latter case, the POP session is silently aborted (which doesn't violate the RFC). popa3d is careful to make sure the timestamp will change if the mailbox is written to, by keeping the lock for up to a second if necessary.

Mailbox access.

Except for the total size and message count limits mentioned above (and you can disable even those), there are no other artificial limits on the mailbox contents. In particular, there are no line length limits; unlike with qmail-pop3d, lines don't even need to fit in the available memory. NUL bytes are allowed in messages as well.

Locking.

Because of dropping to the user "completely" (that is, not even keeping a GID of mail like some other POP3 servers do), popa3d only uses fcntl(2) or flock(2) for locking. As a result, it may not be safe over NFS. This is where I choose security over either functionality or reliability.

RFC compliance.

I tried to make popa3d as strictly RFC 1939 compliant as possible. Most other POP3 servers have extra "features" that violate the RFC. Examples include: wrapping long commands (no matter if they're valid or not) and thus generating multiple -ERR responses (if not even worse: processing something from the middle of the line as a command) to a single command, processing "LIST 4294967297" as "LIST 1" instead of reporting the error, ignoring past a NUL byte till end of line and thus misinterpreting the command. While these are mostly harmless, they can theoretically cause a POP3 client not to detect the unavailability of a protocol extension.

There's however one place where popa3d's RFC compliance is deliberately relaxed: popa3d accepts commands terminated by single LFs, even though the RFC says the commands are terminated by a CRLF pair.

Performance.

Despite the two extra "security" fork(2) calls, popa3d seems to behave fairly efficiently: the efficient mailbox parsing code and the lack of mailbox copying compensate for the extra fork's.

Here's some real performance data that I've collected (popa3d running via inetd; larger sites would use the standalone mode instead):

	24864   295.50re   16.92cp  popa3d*
	12749  4578.88re   15.50cp  popa3d

That is, 12749 POP3 sessions took 32.42 minutes of CPU time (on a 350 MHz Pentium II); of those, more than a half was spent in the temporary child processes. It's not that bad though, as this system was running an (intentionally) expensive crypt(3) that got accounted to the child /etc/shadow authentication processes.

Before upgrading to popa3d, the same machine was running qpopper (out of inetd, too):

	12025  3169.38re   35.56cp  popper

It used to take a bit more CPU for less POP3 sessions.

--
Solar Designer <solar at openwall.com>

$Owl: Owl/packages/popa3d/popa3d/DESIGN,v 1.6 2006/05/23 00:20:19 solar Exp $