summaryrefslogtreecommitdiff
path: root/usr.sbin/popa3d/DESIGN
blob: e1466b9b251361e70425d3f14448180219caec4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
This file describes the design goals and the reasoning behind some of
the design decisions for my tiny POP3 daemon, popa3d.


	Why popa3d?

There're 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're
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're no other artificial limits
on the mailbox contents.  In particular, there're 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>