summaryrefslogtreecommitdiff
path: root/libexec/popa3d
diff options
context:
space:
mode:
authorCamiel Dobbelaar <camield@cvs.openbsd.org>2001-08-13 20:03:22 +0000
committerCamiel Dobbelaar <camield@cvs.openbsd.org>2001-08-13 20:03:22 +0000
commit9b80930a98b45e288345a7e869d320fab4f0a698 (patch)
tree1fd86ad990f07f8f8868ecdca6c0e0d38d845fca /libexec/popa3d
parentef62c6d658b92504a3b38095b540e66cefb1d83f (diff)
Solar Designer's popa3d POP3 daemon, version 0.4.9.1
Changes so far: - removed auth_pam.c - removed auth_shadow.c - add BSD makefile - remove md5, in favour of libc md5 - params.h: AUTH_PASSWD and MAIL_SPOOL_PATH
Diffstat (limited to 'libexec/popa3d')
-rw-r--r--libexec/popa3d/DESIGN204
-rw-r--r--libexec/popa3d/LICENSE9
-rw-r--r--libexec/popa3d/Makefile10
-rw-r--r--libexec/popa3d/VIRTUAL5
-rw-r--r--libexec/popa3d/auth_passwd.c39
-rw-r--r--libexec/popa3d/database.c86
-rw-r--r--libexec/popa3d/database.h53
-rw-r--r--libexec/popa3d/virtual.h34
8 files changed, 440 insertions, 0 deletions
diff --git a/libexec/popa3d/DESIGN b/libexec/popa3d/DESIGN
new file mode 100644
index 00000000000..1d03aad76aa
--- /dev/null
+++ b/libexec/popa3d/DESIGN
@@ -0,0 +1,204 @@
+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 are 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
+LF's, 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@openwall.com>
diff --git a/libexec/popa3d/LICENSE b/libexec/popa3d/LICENSE
new file mode 100644
index 00000000000..dc50971f05d
--- /dev/null
+++ b/libexec/popa3d/LICENSE
@@ -0,0 +1,9 @@
+You're allowed to do whatever you like with this software (including
+re-distribution in source and/or binary form, with or without
+modification), provided that credit is given where it is due and any
+modified versions are marked as such. There's absolutely no warranty.
+
+Note that you don't have to re-distribute this software under these
+same relaxed terms. In particular, you're free to place modified
+versions under (L)GPL, thus disallowing further re-distribution in
+binary-only form.
diff --git a/libexec/popa3d/Makefile b/libexec/popa3d/Makefile
new file mode 100644
index 00000000000..7f9f429fa8c
--- /dev/null
+++ b/libexec/popa3d/Makefile
@@ -0,0 +1,10 @@
+# $OpenBSD: Makefile,v 1.1 2001/08/13 20:03:21 camield Exp $
+
+PROG= popa3d
+
+SRCS= auth_passwd.c database.c mailbox.c misc.c pop_auth.c pop_root.c \
+ pop_trans.c protocol.c standalone.c virtual.c
+
+NOMAN=
+
+.include <bsd.prog.mk>
diff --git a/libexec/popa3d/VIRTUAL b/libexec/popa3d/VIRTUAL
new file mode 100644
index 00000000000..23341277b81
--- /dev/null
+++ b/libexec/popa3d/VIRTUAL
@@ -0,0 +1,5 @@
+The virtual domain support in popa3d is in development and is currently
+undocumented.
+
+Please only use it if you know what you are doing, -- it is too easy to
+misconfigure it in dangerous ways.
diff --git a/libexec/popa3d/auth_passwd.c b/libexec/popa3d/auth_passwd.c
new file mode 100644
index 00000000000..40642a2d8b2
--- /dev/null
+++ b/libexec/popa3d/auth_passwd.c
@@ -0,0 +1,39 @@
+/*
+ * The /etc/passwd authentication routine.
+ */
+
+#include "params.h"
+
+#if AUTH_PASSWD && !VIRTUAL_ONLY
+
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 4
+#define _XPG4_2
+#include <unistd.h>
+#include <string.h>
+#include <pwd.h>
+#include <sys/types.h>
+
+struct passwd *auth_userpass(char *user, char *pass, char **mailbox)
+{
+ struct passwd *pw, *result;
+
+ if ((pw = getpwnam(user))) *mailbox = user;
+ endpwent();
+ result = NULL;
+
+ if (!pw || !*pw->pw_passwd ||
+ *pw->pw_passwd == '*' || *pw->pw_passwd == '!')
+ crypt(pass, AUTH_DUMMY_SALT);
+ else
+ if (!strcmp(crypt(pass, pw->pw_passwd), pw->pw_passwd))
+ result = pw;
+
+ if (pw)
+ memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+
+ return result;
+}
+
+#endif
diff --git a/libexec/popa3d/database.c b/libexec/popa3d/database.c
new file mode 100644
index 00000000000..1d6d5b76c42
--- /dev/null
+++ b/libexec/popa3d/database.c
@@ -0,0 +1,86 @@
+/*
+ * Message database management.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "params.h"
+#include "database.h"
+
+struct db_main db;
+
+void db_init(void)
+{
+ db.head = db.tail = NULL;
+ db.total_count = 0;
+ db.total_size = 0;
+ db.flags = 0;
+#if POP_SUPPORT_LAST
+ db.last = 0;
+#endif
+}
+
+int db_add(struct db_message *msg)
+{
+ struct db_message *entry;
+
+ if (db.total_count >= MAX_MAILBOX_MESSAGES) return 1;
+
+ entry = malloc(sizeof(struct db_message));
+ if (!entry) return 1;
+
+ memcpy(entry, msg, sizeof(struct db_message));
+ entry->next = NULL;
+ entry->flags = 0;
+
+ if (db.tail)
+ db.tail = db.tail->next = entry;
+ else
+ db.tail = db.head = entry;
+
+ if (++db.total_count <= 0) return 1;
+ if ((db.total_size += entry->size) < 0 || entry->size < 0) return 1;
+
+ return 0;
+}
+
+int db_delete(struct db_message *msg)
+{
+ if (msg->flags & MSG_DELETED) return 1;
+
+ msg->flags |= MSG_DELETED;
+
+ db.visible_count--;
+ db.visible_size -= msg->size;
+ db.flags |= DB_DIRTY;
+
+ return 0;
+}
+
+int db_fix(void)
+{
+ int size;
+ struct db_message *entry;
+ int index;
+
+ db.visible_count = db.total_count;
+ db.visible_size = db.total_size;
+
+ if (!db.total_count) return 0;
+
+ size = sizeof(struct db_message *) * db.total_count;
+ if (size <= 0) return 1;
+ if (size / sizeof(struct db_message *) != db.total_count) return 1;
+
+ db.array = malloc(size);
+ if (!db.array) return 1;
+
+ entry = db.head;
+ index = 0;
+ do {
+ db.array[index++] = entry;
+ } while ((entry = entry->next));
+
+ return 0;
+}
diff --git a/libexec/popa3d/database.h b/libexec/popa3d/database.h
new file mode 100644
index 00000000000..e35c382d0a6
--- /dev/null
+++ b/libexec/popa3d/database.h
@@ -0,0 +1,53 @@
+/*
+ * Message database management.
+ */
+
+#ifndef _POP_DATABASE_H
+#define _POP_DATABASE_H
+
+#include <md5.h>
+
+#include "params.h"
+
+/*
+ * Message flags.
+ */
+/* Marked for deletion */
+#define MSG_DELETED 0x00000001
+
+/*
+ * Database flags.
+ */
+/* Some messages are marked for deletion, mailbox update is needed */
+#define DB_DIRTY 0x00000001
+/* Another MUA has modified our part of the mailbox */
+#define DB_STALE 0x00000002
+
+struct db_message {
+ struct db_message *next;
+ long size; /* Size as reported via POP */
+ int flags; /* MSG_* flags defined above */
+ long raw_offset, raw_size; /* Raw, with the "From " line */
+ long data_offset, data_size; /* Just the message itself */
+ unsigned char hash[16]; /* MD5 hash, to be used for UIDL */
+};
+
+struct db_main {
+ struct db_message *head, *tail; /* Messages in a linked list */
+ struct db_message **array; /* Direct access to messages */
+ int total_count, visible_count; /* Total and not DELEted counts */
+ long total_size, visible_size; /* To be reported via POP */
+ int flags; /* DB_* flags defined above */
+#if POP_SUPPORT_LAST
+ int last; /* Last message touched */
+#endif
+};
+
+extern struct db_main db;
+
+extern void db_init(void);
+extern int db_add(struct db_message *msg);
+extern int db_delete(struct db_message *msg);
+extern int db_fix(void);
+
+#endif
diff --git a/libexec/popa3d/virtual.h b/libexec/popa3d/virtual.h
new file mode 100644
index 00000000000..92173a32bc9
--- /dev/null
+++ b/libexec/popa3d/virtual.h
@@ -0,0 +1,34 @@
+/*
+ * Virtual domain support.
+ */
+
+#ifndef _POP_VIRTUAL_H
+#define _POP_VIRTUAL_H
+
+#include <pwd.h>
+#include <sys/types.h>
+
+/*
+ * These are set by the authentication routine, below.
+ */
+extern char *virtual_domain;
+extern char *virtual_spool;
+
+/*
+ * Initializes the virtual domain support at startup. Note that this will
+ * only be called once in standalone mode, so don't expect an open socket
+ * here. Returns a non-zero value on error.
+ */
+extern int virtual_startup(void);
+
+/*
+ * Tries to authenticate a username/password pair for the virtual domain
+ * indicated either by the connected IP address (the socket is available
+ * on fd 0), or as a part of the username. If the virtual domain is known,
+ * virtual_domain and virtual_spool are set appropriately. If the username
+ * is known as well, mailbox is set to the username. Returns the template
+ * user to run as if the authentication is successful, or NULL otherwise.
+ */
+extern struct passwd *virtual_userpass(char *user, char *pass, char **mailbox);
+
+#endif