summaryrefslogtreecommitdiff
path: root/usr.sbin/popa3d
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/popa3d')
-rw-r--r--usr.sbin/popa3d/DESIGN204
-rw-r--r--usr.sbin/popa3d/LICENSE9
-rw-r--r--usr.sbin/popa3d/Makefile10
-rw-r--r--usr.sbin/popa3d/VIRTUAL5
-rw-r--r--usr.sbin/popa3d/auth_passwd.c41
-rw-r--r--usr.sbin/popa3d/database.c88
-rw-r--r--usr.sbin/popa3d/database.h55
-rw-r--r--usr.sbin/popa3d/mailbox.c453
-rw-r--r--usr.sbin/popa3d/mailbox.h32
-rw-r--r--usr.sbin/popa3d/misc.c89
-rw-r--r--usr.sbin/popa3d/misc.h29
-rw-r--r--usr.sbin/popa3d/params.h197
-rw-r--r--usr.sbin/popa3d/pop_auth.c89
-rw-r--r--usr.sbin/popa3d/pop_auth.h29
-rw-r--r--usr.sbin/popa3d/pop_root.c220
-rw-r--r--usr.sbin/popa3d/pop_trans.c248
-rw-r--r--usr.sbin/popa3d/pop_trans.h12
-rw-r--r--usr.sbin/popa3d/popa3d.838
-rw-r--r--usr.sbin/popa3d/protocol.c276
-rw-r--r--usr.sbin/popa3d/protocol.h105
-rw-r--r--usr.sbin/popa3d/standalone.c211
-rw-r--r--usr.sbin/popa3d/virtual.c185
-rw-r--r--usr.sbin/popa3d/virtual.h36
23 files changed, 2661 insertions, 0 deletions
diff --git a/usr.sbin/popa3d/DESIGN b/usr.sbin/popa3d/DESIGN
new file mode 100644
index 00000000000..1d03aad76aa
--- /dev/null
+++ b/usr.sbin/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/usr.sbin/popa3d/LICENSE b/usr.sbin/popa3d/LICENSE
new file mode 100644
index 00000000000..dc50971f05d
--- /dev/null
+++ b/usr.sbin/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/usr.sbin/popa3d/Makefile b/usr.sbin/popa3d/Makefile
new file mode 100644
index 00000000000..f1e0a6da594
--- /dev/null
+++ b/usr.sbin/popa3d/Makefile
@@ -0,0 +1,10 @@
+# $OpenBSD: Makefile,v 1.1 2001/08/19 13:05:57 deraadt 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
+
+MAN= popa3d.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/popa3d/VIRTUAL b/usr.sbin/popa3d/VIRTUAL
new file mode 100644
index 00000000000..23341277b81
--- /dev/null
+++ b/usr.sbin/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/usr.sbin/popa3d/auth_passwd.c b/usr.sbin/popa3d/auth_passwd.c
new file mode 100644
index 00000000000..924fc03981d
--- /dev/null
+++ b/usr.sbin/popa3d/auth_passwd.c
@@ -0,0 +1,41 @@
+/* $OpenBSD: auth_passwd.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * 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/usr.sbin/popa3d/database.c b/usr.sbin/popa3d/database.c
new file mode 100644
index 00000000000..c342cca54d2
--- /dev/null
+++ b/usr.sbin/popa3d/database.c
@@ -0,0 +1,88 @@
+/* $OpenBSD: database.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * 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/usr.sbin/popa3d/database.h b/usr.sbin/popa3d/database.h
new file mode 100644
index 00000000000..9050f0489b9
--- /dev/null
+++ b/usr.sbin/popa3d/database.h
@@ -0,0 +1,55 @@
+/* $OpenBSD: database.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * 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/usr.sbin/popa3d/mailbox.c b/usr.sbin/popa3d/mailbox.c
new file mode 100644
index 00000000000..3316ed26b4a
--- /dev/null
+++ b/usr.sbin/popa3d/mailbox.c
@@ -0,0 +1,453 @@
+/* $OpenBSD: mailbox.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Mailbox access.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <errno.h>
+
+#include <md5.h>
+
+#include "misc.h"
+#include "params.h"
+#include "protocol.h"
+#include "database.h"
+
+static int mailbox_fd; /* fd for the mailbox, or -1 */
+static time_t mailbox_mtime; /* mtime, as of the last check */
+static long mailbox_size; /* Its original size */
+
+static struct db_message *cmp;
+
+/*
+ * If a message has changed since the database was filled in, then we
+ * consider the database stale. This is called for every message when
+ * the mailbox is being re-parsed (because of its mtime change).
+ */
+static int db_compare(struct db_message *msg)
+{
+ if (!cmp) return 1;
+
+ if (msg->raw_size != cmp->raw_size || msg->size != cmp->size ||
+ memcmp(msg->hash, cmp->hash, sizeof(msg->hash))) {
+ db.flags |= DB_STALE;
+ return 1;
+ }
+
+ cmp = cmp->next;
+
+ return 0;
+}
+
+/*
+ * Checks if the buffer pointed to by s1, of n1 chars, starts with the
+ * string s2.
+ */
+static int linecmp(char *s1, char *s2, int n1)
+{
+ int n2;
+
+ if (s1[0] != s2[0]) return 1;
+ if (n1 < (n2 = strlen(s2))) return 1;
+ return memcmp(s1, s2, n2);
+}
+
+/*
+ * The mailbox parsing routine: first called to fill the database in,
+ * then to check if the database is still up to date. We implement a
+ * state machine at the line fragment level (that is, full or partial
+ * lines). This is faster than dealing with individual characters (we
+ * leave that job for libc), and doesn't require ever loading entire
+ * lines into memory.
+ */
+static int mailbox_parse(int init)
+{
+ struct stat stat; /* File information */
+ struct db_message msg; /* Message being parsed */
+ MD5_CTX hash; /* Its hash being computed */
+ int (*db_op)(struct db_message *msg); /* db_add or db_compare */
+ char *file_buffer, *line_buffer; /* Our internal buffers */
+ long file_offset, line_offset, offset; /* Their offsets in the file */
+ char *current, *next, *line; /* Line pointers */
+ int block, saved, extra, length; /* Internal block sizes */
+ int done, start, end; /* Various boolean flags: */
+ int blank, header, body, fixed; /* the state information */
+
+ if (fstat(mailbox_fd, &stat)) return 1;
+
+ if (init) {
+/* Prepare for the database initialization */
+ if (!S_ISREG(stat.st_mode)) return 1;
+ mailbox_mtime = stat.st_mtime;
+ mailbox_size = stat.st_size;
+ if (mailbox_size < 0) return 1;
+ if (!mailbox_size) return 0;
+ if (mailbox_size > MAX_MAILBOX_BYTES) return 1;
+ db_op = db_add;
+ } else {
+/* Prepare for checking against the database */
+ if (mailbox_mtime == stat.st_mtime) return 0;
+ if (!mailbox_size) return 0;
+ if (mailbox_size > (long)stat.st_size) {
+ db.flags |= DB_STALE;
+ return 1;
+ }
+ if (lseek(mailbox_fd, 0, SEEK_SET) < 0) return 1;
+ db_op = db_compare; cmp = db.head;
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ MD5Init(&hash);
+
+ file_buffer = malloc(FILE_BUFFER_SIZE + LINE_BUFFER_SIZE);
+ if (!file_buffer) return 1;
+ line_buffer = &file_buffer[FILE_BUFFER_SIZE];
+
+ file_offset = 0; line_offset = 0; offset = 0; /* Start at 0, with */
+ current = file_buffer; block = 0; saved = 0; /* empty buffers */
+
+ done = 0; /* Haven't reached EOF or the original size yet */
+ end = 1; /* Assume we've just seen a LF: parse a new line */
+ blank = 1; /* Assume we've seen a blank line: look for "From " */
+ header = 0; /* Not in message headers, */
+ body = 0; /* and not in message body */
+ fixed = 0; /* Not in a "fixed" part of a message */
+
+/*
+ * The main loop. Its first part extracts the line fragments, while the
+ * second one manages the state flags and performs whatever is required
+ * based on the state. Unfortunately, splitting this into two functions
+ * didn't seem to simplify the code.
+ */
+ do {
+/*
+ * Part 1.
+ * The line fragment extraction.
+ */
+
+/* Look for the next LF in the file buffer */
+ if ((next = memchr(current, '\n', block))) {
+/* Found it: get the length of this piece, and check for buffered data */
+ length = ++next - current;
+ if (saved) {
+/* Have this line's beginning in the line buffer: combine them */
+ extra = LINE_BUFFER_SIZE - saved;
+ if (extra > length) extra = length;
+ memcpy(&line_buffer[saved], current, extra);
+ current += extra; block -= extra;
+ length = saved + extra;
+ line = line_buffer;
+ offset = line_offset;
+ start = end; end = current == next;
+ saved = 0;
+ } else {
+/* Nothing in the line buffer: just process what we've got now */
+ line = current;
+ offset = file_offset - block;
+ start = end; end = 1;
+ current = next; block -= length;
+ }
+ } else {
+/* No more LF's in the file buffer */
+ if (saved || block <= LINE_BUFFER_SIZE) {
+/* Have this line's beginning in the line buffer: combine them */
+/* Not enough data to process right now: buffer it */
+ extra = LINE_BUFFER_SIZE - saved;
+ if (extra > block) extra = block;
+ if (!saved) line_offset = file_offset - block;
+ memcpy(&line_buffer[saved], current, extra);
+ current += extra; block -= extra;
+ saved += extra;
+ length = saved;
+ line = line_buffer;
+ offset = line_offset;
+ } else {
+/* Nothing in the line buffer and we've got enough data: just process it */
+ length = block - 1;
+ line = current;
+ offset = file_offset - block;
+ current += length;
+ block = 1;
+ }
+ if (!block) {
+/* We've emptied the file buffer: fetch some more data */
+ current = file_buffer;
+ if (init)
+ block = FILE_BUFFER_SIZE;
+ else {
+ block = mailbox_size - file_offset;
+ if (block > FILE_BUFFER_SIZE)
+ block = FILE_BUFFER_SIZE;
+ }
+ block = read(mailbox_fd, file_buffer, block);
+ if (block < 0) break;
+ file_offset += block;
+ if (block > 0 && saved < LINE_BUFFER_SIZE)
+ continue;
+ if (!saved) {
+/* Nothing in the line buffer, and read(2) returned 0: we're done */
+ offset = file_offset;
+ done = 1;
+ break;
+ }
+ }
+ start = end; end = !block;
+ saved = 0;
+ }
+
+/*
+ * Part 2.
+ * The following variables are set when we get here:
+ * -- line the line fragment, not NUL terminated;
+ * -- length its length;
+ * -- offset its offset in the file;
+ * -- start whether it's at the start of the line;
+ * -- end whether it's at the end of the line
+ * (all four combinations of "start" and "end" are possible).
+ */
+
+/* Check for a new message if we've just seen a blank line */
+ if (blank && start)
+ if (!linecmp(line, "From ", length)) {
+/* Process the previous one first, if exists */
+ if (offset) {
+ if (!header && !body) break;
+ msg.raw_size = offset - msg.raw_offset;
+ msg.data_size = offset - msg.data_offset;
+ MD5Final(msg.hash, &hash);
+ if (db_op(&msg)) break;
+ }
+/* Now prepare for parsing the new one */
+ msg.raw_offset = offset;
+ msg.data_offset = 0;
+ MD5Init(&hash);
+ header = 1; body = 0;
+ continue;
+ }
+
+/* Memorize file offset of the message data (the line next to "From ") */
+ if (header && start && !msg.data_offset) {
+ msg.data_offset = offset;
+ msg.data_size = 0;
+ msg.size = 0;
+ }
+
+/* Count this fragment, with LF's as CRLF, into the message size */
+ if (msg.data_offset)
+ msg.size += length + end;
+
+/* If we see LF at start of line, then this is a blank line :-) */
+ blank = start && line[0] == '\n';
+
+/* Blank line in headers means start of the message body */
+ if (header && blank) {
+ header = 0; body = 1;
+ }
+
+/* Some header lines are known to remain fixed over MUA runs */
+ if (header && start)
+ if (!linecmp(line, "Received:", length) ||
+ !linecmp(line, "Date:", length) ||
+ !linecmp(line, "Message-Id:", length) ||
+ !linecmp(line, "Subject:", length))
+ fixed = 1;
+
+/* We can hash all fragments of those lines (until "end"), for UIDL */
+ if (fixed) {
+ MD5Update(&hash, line, length);
+ if (end) fixed = 0;
+ }
+ } while (1);
+
+ free(file_buffer);
+
+ if (done) {
+/* Process the last message */
+ if (offset != mailbox_size) return 1;
+ if (!header && !body) return 1;
+ msg.raw_size = offset - msg.raw_offset;
+ msg.data_size = offset - msg.data_offset;
+ MD5Final(msg.hash, &hash);
+ if (db_op(&msg)) return 1;
+
+/* Everything went well, update our timestamp if we were checking */
+ if (!init) mailbox_mtime = stat.st_mtime;
+ }
+
+ return !done;
+}
+
+int mailbox_open(char *spool, char *mailbox)
+{
+ char *pathname;
+ struct stat stat;
+ int result;
+
+ mailbox_fd = -1;
+
+ pathname = malloc(strlen(spool) + strlen(mailbox) + 2);
+ if (!pathname) return 1;
+ sprintf(pathname, "%s/%s", spool, mailbox);
+
+ if (lstat(pathname, &stat)) {
+ free(pathname);
+ return errno != ENOENT;
+ }
+
+ if (!stat.st_size) {
+ free(pathname);
+ return 0;
+ }
+
+ mailbox_fd = open(pathname, O_RDWR);
+
+ free(pathname);
+
+ if (mailbox_fd < 0)
+ return errno != ENOENT;
+
+ if (lock_fd(mailbox_fd, 1)) return 1;
+
+ result = mailbox_parse(1);
+
+ if (!result && time(NULL) == mailbox_mtime)
+ if (sleep_select(1, 0)) result = 1;
+
+ if (unlock_fd(mailbox_fd)) return 1;
+
+ return result;
+}
+
+static int mailbox_changed(void)
+{
+ struct stat stat;
+ int result;
+
+ if (fstat(mailbox_fd, &stat)) return 1;
+ if (mailbox_mtime == stat.st_mtime) return 0;
+
+ if (lock_fd(mailbox_fd, 1)) return 1;
+
+ result = mailbox_parse(0);
+
+ if (!result && time(NULL) == mailbox_mtime)
+ if (sleep_select(1, 0)) result = 1;
+
+ if (unlock_fd(mailbox_fd)) return 1;
+
+ return result;
+}
+
+int mailbox_get(struct db_message *msg, int lines)
+{
+ if (mailbox_changed()) return 1;
+
+ if (lseek(mailbox_fd, msg->data_offset, SEEK_SET) < 0) return 1;
+ if (pop_reply_multiline(mailbox_fd, msg->data_size, lines)) return 1;
+
+ if (mailbox_changed()) return 1;
+
+ return pop_reply_terminate();
+}
+
+static int mailbox_write(char *buffer)
+{
+ struct db_message *msg;
+ long old, new;
+ int block;
+
+ msg = db.head;
+ old = new = 0;
+ do {
+ if (msg->flags & MSG_DELETED) continue;
+ old = msg->raw_offset;
+
+ if (old == new) {
+ old = (new += msg->raw_size);
+ continue;
+ }
+
+ while ((block = msg->raw_size - (old - msg->raw_offset))) {
+ if (lseek(mailbox_fd, old, SEEK_SET) < 0) return 1;
+ if (block > FILE_BUFFER_SIZE) block = FILE_BUFFER_SIZE;
+ block = read(mailbox_fd, buffer, block);
+ if (!block && old == mailbox_size) break;
+ if (block <= 0) return 1;
+
+ if (lseek(mailbox_fd, new, SEEK_SET) < 0) return 1;
+ if (write_loop(mailbox_fd, buffer, block) != block)
+ return 1;
+
+ old += block; new += block;
+ }
+ } while ((msg = msg->next));
+
+ old = mailbox_size;
+ while (1) {
+ if (lseek(mailbox_fd, old, SEEK_SET) < 0) return 1;
+ block = read(mailbox_fd, buffer, FILE_BUFFER_SIZE);
+ if (!block) break;
+ if (block < 0) return 1;
+
+ if (lseek(mailbox_fd, new, SEEK_SET) < 0) return 1;
+ if (write(mailbox_fd, buffer, block) != block) return 1;
+
+ old += block; new += block;
+ }
+
+ if (ftruncate(mailbox_fd, new)) return 1;
+
+ return fsync(mailbox_fd);
+}
+
+static int mailbox_write_blocked(void)
+{
+ sigset_t blocked_set, old_set;
+ char *buffer;
+ int result;
+
+ if (sigfillset(&blocked_set)) return 1;
+ if (sigprocmask(SIG_BLOCK, &blocked_set, &old_set)) return 1;
+
+ if ((buffer = malloc(FILE_BUFFER_SIZE))) {
+ result = mailbox_write(buffer);
+ free(buffer);
+ } else
+ result = 1;
+
+ if (sigprocmask(SIG_SETMASK, &old_set, NULL)) return 1;
+
+ return result;
+}
+
+int mailbox_update(void)
+{
+ int result;
+
+ if (mailbox_fd < 0 || !(db.flags & DB_DIRTY)) return 0;
+
+ if (lock_fd(mailbox_fd, 0)) return 1;
+
+ if (!(result = mailbox_parse(0)))
+ result = mailbox_write_blocked();
+
+ if (unlock_fd(mailbox_fd)) return 1;
+
+ return result;
+}
+
+int mailbox_close(void)
+{
+ if (mailbox_fd < 0) return 0;
+
+ return close(mailbox_fd);
+}
diff --git a/usr.sbin/popa3d/mailbox.h b/usr.sbin/popa3d/mailbox.h
new file mode 100644
index 00000000000..ba6fc7ad0b0
--- /dev/null
+++ b/usr.sbin/popa3d/mailbox.h
@@ -0,0 +1,32 @@
+/* $OpenBSD: mailbox.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Mailbox access.
+ */
+
+#ifndef _POP_MAILBOX_H
+#define _POP_MAILBOX_H
+
+/*
+ * Opens the mailbox, filling in the message database. Returns a non-zero
+ * value on error.
+ */
+extern int mailbox_open(char *spool, char *mailbox);
+
+/*
+ * Sends (first lines of) a message to the POP client. Returns a non-zero
+ * value on error; the POP session then has to crash.
+ */
+extern int mailbox_get(struct db_message *msg, int lines);
+
+/*
+ * Rewrites the mailbox according to flags in the database.
+ */
+extern int mailbox_update(void);
+
+/*
+ * Closes the mailbox file.
+ */
+extern int mailbox_close(void);
+
+#endif
diff --git a/usr.sbin/popa3d/misc.c b/usr.sbin/popa3d/misc.c
new file mode 100644
index 00000000000..7b43a5d8134
--- /dev/null
+++ b/usr.sbin/popa3d/misc.c
@@ -0,0 +1,89 @@
+/* $OpenBSD: misc.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Miscellaneous syscall wrappers. See misc.h for the descriptions.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+
+#include "params.h"
+
+int sleep_select(int sec, int usec)
+{
+ struct timeval timeout;
+
+ timeout.tv_sec = sec;
+ timeout.tv_usec = usec;
+
+ return select(0, NULL, NULL, NULL, &timeout);
+}
+
+int lock_fd(int fd, int shared)
+{
+#if LOCK_FCNTL
+ struct flock l;
+
+ memset(&l, 0, sizeof(l));
+ l.l_whence = SEEK_SET;
+ l.l_type = shared ? F_RDLCK : F_WRLCK;
+ while (fcntl(fd, F_SETLKW, &l)) {
+ if (errno != EBUSY) return -1;
+ sleep_select(1, 0);
+ }
+#endif
+
+#if LOCK_FLOCK
+ while (flock(fd, shared ? LOCK_SH : LOCK_EX)) {
+ if (errno != EBUSY) return -1;
+ sleep_select(1, 0);
+ }
+#endif
+
+ return 0;
+}
+
+int unlock_fd(int fd)
+{
+#if LOCK_FCNTL
+ struct flock l;
+
+ memset(&l, 0, sizeof(l));
+ l.l_whence = SEEK_SET;
+ l.l_type = F_UNLCK;
+ if (fcntl(fd, F_SETLK, &l)) return -1;
+#endif
+
+#if LOCK_FLOCK
+ if (flock(fd, LOCK_UN)) return -1;
+#endif
+
+ return 0;
+}
+
+int write_loop(int fd, char *buffer, int count)
+{
+ int offset, block;
+
+ offset = 0;
+ while (count > 0) {
+ block = write(fd, &buffer[offset], count);
+
+/* If any write(2) fails, we consider that the entire write_loop() has
+ * failed to do its job. We don't even ignore EINTR here. We also don't
+ * retry when a write(2) returns zero, as we could start eating up the
+ * CPU if we did. */
+ if (block < 0) return block;
+ if (!block) return offset;
+
+ offset += block;
+ count -= block;
+ }
+
+/* Should be equal to the requested size, unless our kernel got crazy. */
+ return offset;
+}
diff --git a/usr.sbin/popa3d/misc.h b/usr.sbin/popa3d/misc.h
new file mode 100644
index 00000000000..097d3c75c3a
--- /dev/null
+++ b/usr.sbin/popa3d/misc.h
@@ -0,0 +1,29 @@
+/* $OpenBSD: misc.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Miscellaneous syscall wrappers.
+ */
+
+#ifndef _POP_MISC_H
+#define _POP_MISC_H
+
+/*
+ * A select(2)-based sleep() equivalent: no more problems with SIGALRM,
+ * subsecond precision.
+ */
+extern int sleep_select(int sec, int usec);
+
+/*
+ * Obtain or remove a lock.
+ */
+extern int lock_fd(int fd, int shared);
+extern int unlock_fd(int fd);
+
+/*
+ * Attempts to write all the supplied data. Returns the number of bytes
+ * written. Any value that differs from the requested count means that
+ * an error has occured; if the value is -1, errno is set appropriately.
+ */
+extern int write_loop(int fd, char *buffer, int count);
+
+#endif
diff --git a/usr.sbin/popa3d/params.h b/usr.sbin/popa3d/params.h
new file mode 100644
index 00000000000..03145b92f55
--- /dev/null
+++ b/usr.sbin/popa3d/params.h
@@ -0,0 +1,197 @@
+/* $OpenBSD: params.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Global POP daemon parameters.
+ */
+
+#ifndef _POP_PARAMS_H
+#define _POP_PARAMS_H
+
+/*
+ * Are we going to be a standalone server or start via an inetd clone?
+ */
+#define POP_STANDALONE 1
+
+#if POP_STANDALONE
+
+/*
+ * The address and port to listen on.
+ */
+#define DAEMON_ADDR "0.0.0.0" /* INADDR_ANY */
+#define DAEMON_PORT 110
+
+/*
+ * Limit the number of POP sessions we can handle at a time to reduce
+ * the impact of connection flood DoS attacks.
+ */
+#define MAX_SESSIONS 100
+#define MAX_SESSIONS_PER_SOURCE 10
+#define MAX_BACKLOG 5
+#define MIN_DELAY 10
+
+#endif
+
+/*
+ * Do we want to support virtual domains?
+ */
+#define POP_VIRTUAL 0
+
+#if POP_VIRTUAL
+
+/*
+ * VIRTUAL_HOME_PATH is where the virtual domain root directories live.
+ */
+#define VIRTUAL_HOME_PATH "/vhome"
+
+/*
+ * Subdirectories within each virtual domain root for the authentication
+ * information and mailboxes, respectively. These defaults correspond to
+ * full pathnames of the form "/vhome/IP/{auth,mail}/username".
+ */
+#define VIRTUAL_AUTH_PATH "auth"
+#define VIRTUAL_SPOOL_PATH "mail"
+
+/*
+ * Do we want to support virtual domains only? Normally, if the connected
+ * IP address doesn't correspond to a directory in VIRTUAL_HOME_PATH, the
+ * authentication will be done globally.
+ */
+#define VIRTUAL_ONLY 0
+
+#else
+
+/*
+ * We don't support virtual domains (!POP_VIRTUAL), so we're definitely
+ * not virtual-only. Don't edit this.
+ */
+#define VIRTUAL_ONLY 0
+
+#endif
+
+/*
+ * An unprivileged dummy user to run as before authentication. The user
+ * and its UID must not be used for any other purpose.
+ */
+#define POP_USER "popa3d"
+
+/*
+ * Sessions will be closed if idle for longer than POP_TIMEOUT seconds.
+ * RFC 1939 says that "such a timer MUST be of at least 10 minutes'
+ * duration", so I've made 10 minutes the default. In practice, you
+ * may want to reduce this to, say, 2 minutes.
+ */
+#define POP_TIMEOUT (10 * 60)
+
+/*
+ * Do we want to support the obsolete LAST command, as defined in RFC
+ * 1460? It has been removed from the protocol in 1994 by RFC 1725,
+ * and isn't even mentioned in RFC 1939. Still, some software doesn't
+ * work without it.
+ */
+#define POP_SUPPORT_LAST 1
+
+/*
+ * Introduce some sane limits on the mailbox size in order to prevent
+ * a single huge mailbox from stopping the entire POP service.
+ */
+#define MAX_MAILBOX_MESSAGES 100000
+#define MAX_MAILBOX_BYTES 100000000
+
+#if !VIRTUAL_ONLY
+
+/*
+ * Choose the password authentication method your system uses:
+ *
+ * AUTH_PASSWD Use getpwnam(3) only, for *BSD or readable passwd;
+ *
+ * Note that there's no built-in password aging support.
+ */
+#define AUTH_PASSWD 1
+
+#endif
+
+#if POP_VIRTUAL || AUTH_PASSWD
+
+/*
+ * A salt used to waste some CPU time on dummy crypt(3) calls and make
+ * it harder (but still far from impossible, on most systems) to check
+ * for valid usernames. Adjust it for your crypt(3).
+ */
+/* echo -n "dummyblowfishsalt" | encrypt -b 7 */
+#define AUTH_DUMMY_SALT "$2a$07$deZMiFIouL0vgRsEG.AJY.Jx2Z8wJj3g5qs11UQWrBj8MERIu/rXi"
+
+#endif
+
+/*
+ * Message to return to the client when authentication fails. You can
+ * #undef this for no message.
+ */
+#define AUTH_FAILED_MESSAGE "Authentication failed (bad password?)"
+
+#if !VIRTUAL_ONLY
+
+/*
+ * Your mail spool directory. Note: only local (non-NFS) mode 775 mail
+ * spools are currently supported.
+ */
+#define MAIL_SPOOL_PATH "/var/mail"
+
+#endif
+
+/*
+ * Locking method your system uses for user mailboxes. It is important
+ * that you set this correctly.
+ */
+#define LOCK_FCNTL 1
+#define LOCK_FLOCK 0
+
+/*
+ * How do we talk to syslogd? These should be fine for most systems.
+ */
+#define SYSLOG_IDENT "popa3d"
+#define SYSLOG_OPTIONS LOG_PID
+#define SYSLOG_FACILITY LOG_DAEMON
+#define SYSLOG_PRIORITY LOG_NOTICE
+
+/*
+ * There's probably no reason to touch anything below this comment.
+ */
+
+/*
+ * According to RFC 1939: "Keywords and arguments are each separated by
+ * a single SPACE character. Keywords are three or four characters long.
+ * Each argument may be up to 40 characters long." We're only processing
+ * up to two arguments, so it is safe to truncate after this length.
+ */
+#define POP_BUFFER_SIZE 0x80
+
+/*
+ * There's no reason to change this one either. Making this larger would
+ * waste memory, and smaller values could make the authentication fail.
+ */
+#define AUTH_BUFFER_SIZE (2 * POP_BUFFER_SIZE)
+
+#if POP_VIRTUAL
+
+/*
+ * Buffer size for reading entire per-user authentication files.
+ */
+#define VIRTUAL_AUTH_SIZE 0x100
+
+#endif
+
+/*
+ * File buffer sizes to use while parsing the mailbox and retrieving a
+ * message, respectively. Can be changed.
+ */
+#define FILE_BUFFER_SIZE 0x10000
+#define RETR_BUFFER_SIZE 0x8000
+
+/*
+ * The mailbox parsing code isn't allowed to truncate lines earlier than
+ * this length. Keep this at least as large as the longest header field
+ * name we need to check for, but not too large for performance reasons.
+ */
+#define LINE_BUFFER_SIZE 0x20
+
+#endif
diff --git a/usr.sbin/popa3d/pop_auth.c b/usr.sbin/popa3d/pop_auth.c
new file mode 100644
index 00000000000..65ea612d0bc
--- /dev/null
+++ b/usr.sbin/popa3d/pop_auth.c
@@ -0,0 +1,89 @@
+/* $OpenBSD: pop_auth.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * AUTHORIZATION state handling.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "misc.h"
+#include "params.h"
+#include "protocol.h"
+#include "pop_auth.h"
+#if POP_VIRTUAL
+#include "virtual.h"
+#endif
+
+static char *pop_user, *pop_pass;
+
+static int pop_auth_quit(char *params)
+{
+ if (params) return POP_ERROR;
+ return POP_LEAVE;
+}
+
+static int pop_auth_user(char *params)
+{
+ char *user;
+
+ user = pop_get_param(&params);
+ if (!user || pop_user || params) return POP_ERROR;
+ if (!(pop_user = strdup(user))) return POP_CRASH;
+ return POP_OK;
+}
+
+static int pop_auth_pass(char *params)
+{
+ if (!params || !pop_user) return POP_ERROR;
+ if (!(pop_pass = strdup(params))) return POP_CRASH;
+ return POP_STATE;
+}
+
+static struct pop_command pop_auth_commands[] = {
+ {"QUIT", pop_auth_quit},
+ {"USER", pop_auth_user},
+ {"PASS", pop_auth_pass},
+ {NULL}
+};
+
+int do_pop_auth(int channel)
+{
+ pop_init();
+
+ if (pop_reply_ok()) return 1;
+
+ pop_user = NULL;
+ if (pop_handle_state(pop_auth_commands) == POP_STATE) {
+ pop_clean();
+ write_loop(channel, (char *)&pop_buffer, sizeof(pop_buffer));
+ write_loop(channel, pop_user, strlen(pop_user) + 1);
+ write_loop(channel, pop_pass, strlen(pop_pass) + 1);
+ if (close(channel)) return 1;
+ }
+
+ return 0;
+}
+
+void log_pop_auth(int result, char *mailbox)
+{
+ if (result == AUTH_NONE) {
+ syslog(SYSLOG_PRIORITY, "Didn't attempt authentication");
+ return;
+ }
+
+#if POP_VIRTUAL
+ if (virtual_domain) {
+ syslog(SYSLOG_PRIORITY, "Authentication %s for %s@%s",
+ result == AUTH_OK ? "passed" : "failed",
+ mailbox ? mailbox : "UNKNOWN",
+ virtual_domain);
+ return;
+ }
+#endif
+ syslog(SYSLOG_PRIORITY, "Authentication %s for %s",
+ result == AUTH_OK ? "passed" : "failed",
+ mailbox ? mailbox : "UNKNOWN");
+}
diff --git a/usr.sbin/popa3d/pop_auth.h b/usr.sbin/popa3d/pop_auth.h
new file mode 100644
index 00000000000..4352552f133
--- /dev/null
+++ b/usr.sbin/popa3d/pop_auth.h
@@ -0,0 +1,29 @@
+/* $OpenBSD: pop_auth.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * AUTHORIZATION state handling.
+ */
+
+#ifndef _POP_AUTH_H
+#define _POP_AUTH_H
+
+/*
+ * Possible authentication results.
+ */
+#define AUTH_OK 0
+#define AUTH_NONE 1
+#define AUTH_FAILED 2
+
+/*
+ * Handles the AUTHORIZATION state commands, and writes authentication
+ * data into the channel.
+ */
+extern int do_pop_auth(int channel);
+
+/*
+ * Logs an authentication attempt for mailbox (or NULL if the requested
+ * mailbox doesn't exist).
+ */
+extern void log_pop_auth(int result, char *mailbox);
+
+#endif
diff --git a/usr.sbin/popa3d/pop_root.c b/usr.sbin/popa3d/pop_root.c
new file mode 100644
index 00000000000..194de4a19e4
--- /dev/null
+++ b/usr.sbin/popa3d/pop_root.c
@@ -0,0 +1,220 @@
+/* $OpenBSD: pop_root.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Main daemon code: invokes the actual POP handling routines. Most calls
+ * to functions in other source files are done as a non-root user (either
+ * POP_USER or the authenticated user). Depending on compile-time options
+ * in params.h, the following files may contain code executed as root:
+ *
+ * standalone.c if not running via an inetd clone (POP_STANDALONE)
+ * virtual.c if supporting virtual domains (POP_VIRTUAL)
+ * auth_passwd.c if using passwd or *BSD (AUTH_PASSWD && !VIRTUAL_ONLY)
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+
+#include "params.h"
+#include "protocol.h"
+#include "pop_auth.h"
+#include "pop_trans.h"
+#if POP_VIRTUAL
+#include "virtual.h"
+#endif
+
+#if !VIRTUAL_ONLY
+extern struct passwd *auth_userpass(char *user, char *pass, char **mailbox);
+#endif
+
+static struct passwd pop_pw;
+static char *mailbox;
+
+int log_error(char *s)
+{
+ syslog(SYSLOG_PRIORITY, "%s: %m", s);
+ return 1;
+}
+
+static int set_user(struct passwd *pw)
+{
+ gid_t groups[2];
+
+ if (!pw->pw_uid) return 1;
+
+ groups[0] = groups[1] = pw->pw_gid;
+ if (setgroups(1, groups)) return log_error("setgroups");
+ if (setgid(pw->pw_gid)) return log_error("setgid");
+ if (setuid(pw->pw_uid)) return log_error("setuid");
+
+ return 0;
+}
+
+/*
+ * Attempts to read until EOF, and returns the number of bytes read.
+ * We don't expect any signals, so even EINTR is considered an error.
+ */
+static int read_loop(int fd, char *buffer, int count)
+{
+ int offset, block;
+
+ offset = 0;
+ while (count > 0) {
+ block = read(fd, &buffer[offset], count);
+
+ if (block < 0) return block;
+ if (!block) return offset;
+
+ offset += block;
+ count -= block;
+ }
+
+ return offset;
+}
+
+/*
+ * The root-privileged part of the AUTHORIZATION state handling: reads
+ * the authentication data obtained over POP from its end of the pipe,
+ * attempts authentication, and, if successful, drops privilege to the
+ * authenticated user. Returns one of the AUTH_* result codes.
+ */
+static int do_root_auth(int channel)
+{
+ static char auth[AUTH_BUFFER_SIZE + 2];
+ char *user, *pass;
+ struct passwd *pw;
+
+ mailbox = NULL;
+#if POP_VIRTUAL
+ virtual_domain = NULL;
+#endif
+
+/* The POP client could have sent extra commands without waiting for
+ * successful authentication. We're passing them into the TRANSACTION
+ * state if we ever get there. */
+ if (read_loop(channel, (char *)&pop_buffer, sizeof(pop_buffer)) !=
+ sizeof(pop_buffer)) return AUTH_NONE;
+
+/* Now, the authentication data. */
+ memset(auth, 0, sizeof(auth)); /* Ensure the NUL termination */
+ if (read_loop(channel, auth, AUTH_BUFFER_SIZE) < 0) return AUTH_NONE;
+
+ user = auth;
+ pass = &user[strlen(user) + 1];
+
+ pw = NULL;
+#if POP_VIRTUAL
+ if (!(pw = virtual_userpass(user, pass, &mailbox)) && virtual_domain)
+ return AUTH_FAILED;
+#endif
+#if VIRTUAL_ONLY
+ if (!pw)
+ return AUTH_FAILED;
+#else
+ if (!pw && !(pw = auth_userpass(user, pass, &mailbox)))
+ return AUTH_FAILED;
+#endif
+ if (!*user || !*pass) return AUTH_FAILED;
+
+ memset(pass, 0, strlen(pass));
+
+ if (set_user(pw)) return AUTH_FAILED;
+
+ return AUTH_OK;
+}
+
+int do_pop_startup(void)
+{
+ struct passwd *pw;
+
+ umask(077);
+ signal(SIGPIPE, SIG_IGN);
+
+ openlog(SYSLOG_IDENT, SYSLOG_OPTIONS, SYSLOG_FACILITY);
+
+ errno = 0;
+ if (!(pw = getpwnam(POP_USER))) {
+ syslog(SYSLOG_PRIORITY, "getpwnam(\"" POP_USER "\"): %s",
+ errno ? strerror(errno) : "No such user");
+ return 1;
+ }
+ memcpy(&pop_pw, pw, sizeof(pop_pw));
+
+#if POP_VIRTUAL
+ if (virtual_startup()) return 1;
+#endif
+
+ return 0;
+}
+
+int do_pop_session(void)
+{
+ int channel[2];
+ int result, status;
+
+ signal(SIGCHLD, SIG_IGN);
+
+ if (pipe(channel)) return log_error("pipe");
+
+ switch (fork()) {
+ case -1:
+ return log_error("fork");
+
+ case 0:
+ if (close(channel[0])) return log_error("close");
+ if (set_user(&pop_pw)) return 1;
+ return do_pop_auth(channel[1]);
+ }
+
+ if (close(channel[1]))
+ result = AUTH_NONE;
+ else
+ result = do_root_auth(channel[0]);
+
+ if (wait(&status) < 0)
+ status = 1;
+ else
+ if (WIFEXITED(status))
+ status = WEXITSTATUS(status);
+ else
+ status = 1;
+
+ if (result == AUTH_OK) {
+ if (close(channel[0])) return log_error("close");
+ log_pop_auth(result, mailbox);
+#if POP_VIRTUAL
+ if (virtual_domain)
+ return do_pop_trans(virtual_spool, mailbox);
+#endif
+#if !VIRTUAL_ONLY
+ return do_pop_trans(MAIL_SPOOL_PATH, mailbox);
+#endif
+ }
+
+ if (set_user(&pop_pw)) return 1;
+ log_pop_auth(result, mailbox);
+
+#ifdef AUTH_FAILED_MESSAGE
+ if (result == AUTH_FAILED) pop_reply("-ERR %s", AUTH_FAILED_MESSAGE);
+#else
+ if (result == AUTH_FAILED) pop_reply_error();
+#endif
+
+ return status;
+}
+
+#if !POP_STANDALONE
+int main(void)
+{
+ if (do_pop_startup()) return 1;
+ return do_pop_session();
+}
+#endif
diff --git a/usr.sbin/popa3d/pop_trans.c b/usr.sbin/popa3d/pop_trans.c
new file mode 100644
index 00000000000..05ecbec76a3
--- /dev/null
+++ b/usr.sbin/popa3d/pop_trans.c
@@ -0,0 +1,248 @@
+/* $OpenBSD: pop_trans.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * TRANSACTION state handling.
+ */
+
+#include <stdio.h>
+#include <syslog.h>
+
+#include "params.h"
+#include "protocol.h"
+#include "database.h"
+#include "mailbox.h"
+
+static int pop_trans_quit(char *params)
+{
+ if (params) return POP_ERROR;
+ return POP_STATE;
+}
+
+static int pop_trans_noop(char *params)
+{
+ if (params) return POP_ERROR;
+ return POP_OK;
+}
+
+static int pop_trans_stat(char *params)
+{
+ if (params) return POP_ERROR;
+ if (pop_reply("+OK %d %ld", db.visible_count, db.visible_size))
+ return POP_CRASH;
+ return POP_QUIET;
+}
+
+static int pop_trans_list_or_uidl(char *params, int uidl)
+{
+ int number;
+ struct db_message *msg;
+
+ if (params) {
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count || params)
+ return POP_ERROR;
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) return POP_ERROR;
+ if (uidl) {
+ if (pop_reply("+OK %d "
+ "%02x%02x%02x%02x%02x%02x%02x%02x",
+ number,
+ msg->hash[3], msg->hash[2],
+ msg->hash[1], msg->hash[0],
+ msg->hash[7], msg->hash[6],
+ msg->hash[5], msg->hash[4]))
+ return POP_CRASH;
+ } else
+ if (pop_reply("+OK %d %ld", number, msg->size))
+ return POP_CRASH;
+ return POP_QUIET;
+ }
+
+ if (pop_reply_ok()) return POP_CRASH;
+ for (number = 1; number <= db.total_count; number++) {
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) continue;
+ if (uidl) {
+ if (pop_reply("%d "
+ "%02x%02x%02x%02x%02x%02x%02x%02x",
+ number,
+ msg->hash[3], msg->hash[2],
+ msg->hash[1], msg->hash[0],
+ msg->hash[7], msg->hash[6],
+ msg->hash[5], msg->hash[4]))
+ return POP_CRASH;
+ } else
+ if (pop_reply("%d %ld", number, msg->size))
+ return POP_CRASH;
+ }
+ if (pop_reply_terminate()) return POP_CRASH;
+
+ return POP_QUIET;
+}
+
+static int pop_trans_list(char *params)
+{
+ return pop_trans_list_or_uidl(params, 0);
+}
+
+static int pop_trans_uidl(char *params)
+{
+ return pop_trans_list_or_uidl(params, 1);
+}
+
+static int pop_trans_retr(char *params)
+{
+ int number;
+ struct db_message *msg;
+
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count || params) return POP_ERROR;
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) return POP_ERROR;
+ if (mailbox_get(msg, -1)) return POP_CRASH;
+#if POP_SUPPORT_LAST
+ if (number > db.last) db.last = number;
+#endif
+ return POP_QUIET;
+}
+
+static int pop_trans_top(char *params)
+{
+ int number, lines;
+ struct db_message *msg;
+
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count) return POP_ERROR;
+ lines = pop_get_int(&params);
+ if (lines < 0 || params) return POP_ERROR;
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) return POP_ERROR;
+ if (mailbox_get(msg, lines)) return POP_CRASH;
+ return POP_QUIET;
+}
+
+static int pop_trans_dele(char *params)
+{
+ int number;
+ struct db_message *msg;
+
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count || params) return POP_ERROR;
+ msg = db.array[number - 1];
+ if (db_delete(msg)) return POP_ERROR;
+#if POP_SUPPORT_LAST
+ if (number > db.last) db.last = number;
+#endif
+ return POP_OK;
+}
+
+static int pop_trans_rset(char *params)
+{
+ struct db_message *msg;
+
+ if (params) return POP_ERROR;
+
+ if ((msg = db.head))
+ do {
+ msg->flags &= ~MSG_DELETED;
+ } while ((msg = msg->next));
+
+ db.visible_count = db.total_count;
+ db.visible_size = db.total_size;
+ db.flags &= ~DB_DIRTY;
+#if POP_SUPPORT_LAST
+ db.last = 0;
+#endif
+
+ return POP_OK;
+}
+
+#if POP_SUPPORT_LAST
+static int pop_trans_last(char *params)
+{
+ if (params) return POP_ERROR;
+ if (pop_reply("+OK %d", db.last)) return POP_CRASH;
+ return POP_QUIET;
+}
+#endif
+
+static struct pop_command pop_trans_commands[] = {
+ {"QUIT", pop_trans_quit},
+ {"NOOP", pop_trans_noop},
+ {"STAT", pop_trans_stat},
+ {"LIST", pop_trans_list},
+ {"UIDL", pop_trans_uidl},
+ {"RETR", pop_trans_retr},
+ {"TOP", pop_trans_top},
+ {"DELE", pop_trans_dele},
+ {"RSET", pop_trans_rset},
+#if POP_SUPPORT_LAST
+ {"LAST", pop_trans_last},
+#endif
+ {NULL}
+};
+
+static int db_load(char *spool, char *mailbox)
+{
+ db_init();
+
+ if (mailbox_open(spool, mailbox)) return 1;
+
+ if (db_fix()) {
+ mailbox_close();
+ return 1;
+ }
+
+ return 0;
+}
+
+int do_pop_trans(char *spool, char *mailbox)
+{
+ int result;
+
+ if (!pop_sane()) return 1;
+
+ if (db_load(spool, mailbox)) {
+ syslog(SYSLOG_PRIORITY, "Failed to open mailbox");
+ pop_reply_error();
+ return 0;
+ }
+
+ syslog(SYSLOG_PRIORITY, "%d message%s (%ld byte%s) loaded",
+ db.total_count, db.total_count == 1 ? "" : "s",
+ db.total_size, db.total_size == 1 ? "" : "s");
+
+ if (pop_reply_ok())
+ result = POP_CRASH;
+ else
+ switch ((result = pop_handle_state(pop_trans_commands))) {
+ case POP_STATE:
+ if (mailbox_update()) {
+ if (db.flags & DB_STALE) break;
+ syslog(SYSLOG_PRIORITY, "Failed to update mailbox");
+ pop_reply_error();
+ break;
+ }
+
+ syslog(SYSLOG_PRIORITY, "%d (%ld) deleted, %d (%ld) left",
+ db.total_count - db.visible_count,
+ db.total_size - db.visible_size,
+ db.visible_count,
+ db.visible_size);
+ pop_reply_ok();
+ break;
+
+ case POP_TIMED_OUT:
+ syslog(SYSLOG_PRIORITY, "Connection timed out");
+ }
+
+ if (db.flags & DB_STALE)
+ syslog(SYSLOG_PRIORITY, "Another MUA active, giving up");
+ else
+ if (result == POP_CRASH)
+ syslog(SYSLOG_PRIORITY, "Session crashed");
+
+ mailbox_close();
+
+ return 0;
+}
diff --git a/usr.sbin/popa3d/pop_trans.h b/usr.sbin/popa3d/pop_trans.h
new file mode 100644
index 00000000000..0e0edd91cf9
--- /dev/null
+++ b/usr.sbin/popa3d/pop_trans.h
@@ -0,0 +1,12 @@
+/* $OpenBSD: pop_trans.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * TRANSACTION state handling.
+ */
+
+#ifndef _POP_TRANS_H
+#define _POP_TRANS_H
+
+extern int do_pop_trans(char *spool, char *mailbox);
+
+#endif
diff --git a/usr.sbin/popa3d/popa3d.8 b/usr.sbin/popa3d/popa3d.8
new file mode 100644
index 00000000000..87b59cafef2
--- /dev/null
+++ b/usr.sbin/popa3d/popa3d.8
@@ -0,0 +1,38 @@
+.\" $OpenBSD: popa3d.8,v 1.1 2001/08/19 13:05:57 deraadt Exp $
+.Dd August 15, 2001
+.Dt POPA3D 8
+.Os
+.Sh NAME
+.Nm popa3d
+.Nd "Post Office Protocol server"
+.Sh SYNOPSIS
+.Nm popa3d
+.Sh DESCRIPTION
+.Nm
+is a POP3 server. It offers remote access to user mailboxes.
+Using the POP protocol, a remote mail user agent (MUA) can list,
+retrieve and delete email messages on the server.
+The server uses the
+.Tn TCP
+protocol
+and listens at the port specified in the
+.Dq pop3
+service specification; see
+.Xr services 5 .
+.Sh FILES
+.Bl -tag -width /var/mail -compact
+.It Pa /var/mail
+User mail spools
+.El
+.Sh SEE ALSO
+.Xr inetd 8 ,
+.Xr sendmail 8
+.Sh HISTORY
+.Nm
+first appeared in
+.Ox 3.0 .
+.Sh CAVEATS
+POP authenticates using cleartext passwords.
+.Sh AUTHOR
+Solar Designer
+.Aq solar@openwall.com
diff --git a/usr.sbin/popa3d/protocol.c b/usr.sbin/popa3d/protocol.c
new file mode 100644
index 00000000000..cbe5aa41b19
--- /dev/null
+++ b/usr.sbin/popa3d/protocol.c
@@ -0,0 +1,276 @@
+/* $OpenBSD: protocol.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * POP protocol handling.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "misc.h"
+#include "params.h"
+#include "protocol.h"
+
+struct pop_buffer pop_buffer;
+static jmp_buf pop_timed_out;
+
+void pop_init(void)
+{
+ pop_buffer.ptr = pop_buffer.size = 0;
+}
+
+void pop_clean(void)
+{
+ memset(pop_buffer.data, 0, pop_buffer.ptr);
+ memmove(pop_buffer.data, &pop_buffer.data[pop_buffer.ptr],
+ pop_buffer.size -= pop_buffer.ptr);
+ pop_buffer.ptr = 0;
+}
+
+int pop_sane(void)
+{
+ return (unsigned int)pop_buffer.size <= sizeof(pop_buffer.data) &&
+ (unsigned int)pop_buffer.ptr <= (unsigned int)pop_buffer.size;
+}
+
+static void pop_timeout(int signum)
+{
+ signal(SIGALRM, SIG_DFL);
+ longjmp(pop_timed_out, 1);
+}
+
+static int pop_fetch(void)
+{
+ signal(SIGALRM, pop_timeout);
+ alarm(POP_TIMEOUT);
+
+ pop_buffer.size = read(0, pop_buffer.data, sizeof(pop_buffer.data));
+
+ alarm(0);
+ signal(SIGALRM, SIG_DFL);
+
+ pop_buffer.ptr = 0;
+ return pop_buffer.size <= 0;
+}
+
+static int pop_get_char(void)
+{
+ if (pop_buffer.ptr >= pop_buffer.size)
+ if (pop_fetch()) return -1;
+
+ return (unsigned char)pop_buffer.data[pop_buffer.ptr++];
+}
+
+static char *pop_get_line(char *line, int size)
+{
+ int pos;
+ int seen_cr, seen_nul;
+ int c;
+
+ pos = 0;
+ seen_cr = seen_nul = 0;
+ while ((c = pop_get_char()) >= 0) {
+ if (c == '\n') {
+ if (seen_cr) line[pos - 1] = 0;
+ break;
+ }
+ if (pos < size - 1)
+ seen_cr = ((line[pos++] = c) == '\r');
+ else
+ seen_cr = 0;
+ seen_nul |= !c;
+ }
+ line[pos] = 0;
+
+ if (seen_nul)
+ line[0] = 0;
+
+ if (pos || c >= 0)
+ return line;
+ else
+ return NULL;
+}
+
+int pop_handle_state(struct pop_command *commands)
+{
+ char line[POP_BUFFER_SIZE];
+ char *params;
+ struct pop_command *command;
+ int response;
+
+ if (setjmp(pop_timed_out)) return POP_TIMED_OUT;
+
+ while (pop_get_line(line, sizeof(line))) {
+ if ((params = strchr(line, ' '))) {
+ *params++ = 0;
+ if (!*params) params = NULL;
+ }
+
+ response = POP_ERROR;
+ for (command = commands; command->name; command++)
+ if (!strcasecmp(command->name, line)) {
+ response = command->handler(params);
+ break;
+ }
+
+ switch (response) {
+ case POP_OK:
+ if (pop_reply_ok()) return POP_CRASH;
+ break;
+
+ case POP_ERROR:
+ if (pop_reply_error()) return POP_CRASH;
+
+ case POP_QUIET:
+ break;
+
+ case POP_LEAVE:
+ if (pop_reply_ok()) return POP_CRASH;
+
+ default:
+ return response;
+ }
+ }
+
+ return POP_CRASH;
+}
+
+char *pop_get_param(char **params)
+{
+ char *current, *next;
+
+ if ((current = *params)) {
+ if ((next = strchr(current, ' '))) {
+ *next++ = 0;
+ *params = *next ? next : NULL;
+ } else
+ *params = NULL;
+
+ if (strlen(current) > 40) current = NULL;
+ }
+
+ return current;
+}
+
+int pop_get_int(char **params)
+{
+ char *param, *error;
+ long value;
+
+ if ((param = pop_get_param(params))) {
+ value = strtol(param, &error, 10);
+ if (!*param || *error || (value & ~0x3FFFFFFFL)) return -1;
+
+ return value;
+ }
+
+ return -1;
+}
+
+int pop_reply(char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+
+ putc('\r', stdout);
+ putc('\n', stdout);
+
+ switch (format[0]) {
+ case '+':
+ case '-':
+ return fflush(stdout);
+
+ case '.':
+ if (!format[1]) return fflush(stdout);
+ }
+
+ return ferror(stdout);
+}
+
+int pop_reply_ok(void)
+{
+ return pop_reply("+OK");
+}
+
+int pop_reply_error(void)
+{
+ return pop_reply("-ERR");
+}
+
+int pop_reply_multiline(int fd, long size, int lines)
+{
+ char *in_buffer;
+ char *out_buffer;
+ char *in, *out;
+ int in_block, out_block;
+ int start, body;
+
+ if (lines >= 0) lines++;
+
+ if (pop_reply_ok()) return 1;
+
+ in_buffer = malloc(RETR_BUFFER_SIZE * 3);
+ if (!in_buffer) return 1;
+ out_buffer = &in_buffer[RETR_BUFFER_SIZE];
+
+ start = 1;
+ body = 0;
+ while (size && lines) {
+ if (size > RETR_BUFFER_SIZE)
+ in_block = read(fd, in_buffer, RETR_BUFFER_SIZE);
+ else
+ in_block = read(fd, in_buffer, size);
+ if (in_block <= 0) {
+ free(in_buffer);
+ return 1;
+ }
+
+ in = in_buffer;
+ out = out_buffer;
+ while (in < &in_buffer[in_block] && lines)
+ switch (*in) {
+ case '\n':
+ *out++ = '\r';
+ *out++ = *in++;
+ if (start) body = 1;
+ if (body) lines--;
+ start = 1;
+ break;
+
+ case '.':
+ if (start) *out++ = '.';
+
+ default:
+ *out++ = *in++;
+ start = 0;
+ }
+
+ out_block = out - out_buffer;
+ if (write_loop(1, out_buffer, out_block) != out_block) {
+ free(in_buffer);
+ return 1;
+ }
+
+ size -= in_block;
+ }
+
+ free(in_buffer);
+
+ if (!start)
+ if (pop_reply("%s", "")) return 1;
+
+ return 0;
+}
+
+int pop_reply_terminate(void)
+{
+ return pop_reply(".");
+}
diff --git a/usr.sbin/popa3d/protocol.h b/usr.sbin/popa3d/protocol.h
new file mode 100644
index 00000000000..05cacace937
--- /dev/null
+++ b/usr.sbin/popa3d/protocol.h
@@ -0,0 +1,105 @@
+/* $OpenBSD: protocol.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * POP protocol handling.
+ */
+
+#ifndef _POP_PROTOCOL_H
+#define _POP_PROTOCOL_H
+
+/*
+ * Responses and events, to be returned by command and state handlers.
+ */
+#define POP_OK 0 /* Reply with "+OK" */
+#define POP_ERROR 1 /* Reply with "-ERR" */
+#define POP_QUIET 2 /* We've already replied */
+#define POP_LEAVE 3 /* Leave the session */
+#define POP_STATE 4 /* Advance the state */
+#define POP_CRASH 5 /* Session crashed */
+#define POP_TIMED_OUT 6 /* Connection timed out */
+
+/*
+ * POP command description.
+ */
+struct pop_command {
+ char *name;
+ int (*handler)(char *params);
+};
+
+/*
+ * Internal POP command buffer.
+ */
+struct pop_buffer {
+ int ptr, size;
+ char data[POP_BUFFER_SIZE];
+};
+
+extern struct pop_buffer pop_buffer;
+
+/*
+ * Initializes the buffer.
+ */
+extern void pop_init(void);
+
+/*
+ * Zeroes out the part of the buffer that has already been processed.
+ */
+extern void pop_clean(void);
+
+/*
+ * Checks if the buffer is sane.
+ */
+extern int pop_sane(void);
+
+/*
+ * Handles a POP protocol state (AUTHORIZATION or TRANSACTION, as defined
+ * in RFC 1939), processing the supplied commands. Returns when the state
+ * is changed.
+ */
+extern int pop_handle_state(struct pop_command *commands);
+
+/*
+ * Returns the next parameter, or NULL if there's none or it is too long
+ * to be valid (as defined in the RFC).
+ */
+extern char *pop_get_param(char **params);
+
+/*
+ * Returns the next parameter as a non-negative number, or -1 if there's
+ * none or the syntax is invalid.
+ */
+extern int pop_get_int(char **params);
+
+/*
+ * Produces a generic POP response. Returns a non-zero value on error;
+ * the POP session then has to crash.
+ */
+extern int pop_reply(char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 1, 2)));
+#else
+ ;
+#endif
+
+/*
+ * The two simple POP responses. Return a non-zero value on error; the
+ * POP session then has to crash.
+ */
+extern int pop_reply_ok(void);
+extern int pop_reply_error(void);
+
+/*
+ * Produces a multi-line POP response, reading the data from the supplied
+ * file descriptor for up to the requested size or number of lines of the
+ * message body, if that number is non-negative. Returns a non-zero value
+ * on error; the POP session then has to crash.
+ */
+extern int pop_reply_multiline(int fd, long size, int lines);
+
+/*
+ * Terminates a multi-line POP response. Returns a non-zero value on error;
+ * the POP session then has to crash.
+ */
+extern int pop_reply_terminate(void);
+
+#endif
diff --git a/usr.sbin/popa3d/standalone.c b/usr.sbin/popa3d/standalone.c
new file mode 100644
index 00000000000..0f374e01ce3
--- /dev/null
+++ b/usr.sbin/popa3d/standalone.c
@@ -0,0 +1,211 @@
+/* $OpenBSD: standalone.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Standalone POP server: accepts connections, checks the anti-flood limits,
+ * logs and starts the actual POP sessions.
+ */
+
+#include "params.h"
+
+#if POP_STANDALONE
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/*
+ * These are defined in pop_root.c.
+ */
+extern int log_error(char *s);
+extern int do_pop_startup(void);
+extern int do_pop_session(void);
+
+/*
+ * Active POP sessions. Those that were started within the last MIN_DELAY
+ * seconds are also considered active (regardless of their actual state),
+ * to allow for limiting the logging rate without throwing away critical
+ * information about sessions that we could have allowed to proceed.
+ */
+static struct {
+ struct in_addr addr; /* Source IP address */
+ int pid; /* PID of the server, or 0 for none */
+ clock_t start; /* When the server was started */
+ clock_t log; /* When we've last logged a failure */
+} sessions[MAX_SESSIONS];
+
+static volatile int child_blocked; /* We use blocking to avoid races */
+static volatile int child_pending; /* Are any dead children waiting? */
+
+/*
+ * SIGCHLD handler; can also be called directly with a zero signum.
+ */
+static void handle_child(int signum)
+{
+ int saved_errno;
+ int pid;
+ int i;
+
+ saved_errno = errno;
+
+ if (child_blocked)
+ child_pending = 1;
+ else {
+ child_pending = 0;
+
+ while ((pid = waitpid(0, NULL, WNOHANG)) > 0)
+ for (i = 0; i < MAX_SESSIONS; i++)
+ if (sessions[i].pid == pid) {
+ sessions[i].pid = 0;
+ break;
+ }
+ }
+
+ if (signum) signal(SIGCHLD, handle_child);
+
+ errno = saved_errno;
+}
+
+int main(void)
+{
+ int true = 1;
+ int sock, new;
+ struct sockaddr_in addr;
+ int addrlen;
+ int pid;
+ struct tms buf;
+ clock_t now;
+ int i, j, n;
+
+ if (do_pop_startup()) return 1;
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
+ return log_error("socket");
+
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (void *)&true, sizeof(true)))
+ return log_error("setsockopt");
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(DAEMON_ADDR);
+ addr.sin_port = htons(DAEMON_PORT);
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)))
+ return log_error("bind");
+
+ if (listen(sock, MAX_BACKLOG))
+ return log_error("listen");
+
+ chdir("/");
+ setsid();
+
+ switch (fork()) {
+ case -1:
+ return log_error("fork");
+
+ case 0:
+ break;
+
+ default:
+ return 0;
+ }
+
+ setsid();
+
+ child_blocked = 1;
+ child_pending = 0;
+ signal(SIGCHLD, handle_child);
+
+ memset((void *)sessions, 0, sizeof(sessions));
+ new = 0;
+
+ while (1) {
+ child_blocked = 0;
+ if (child_pending) handle_child(0);
+
+ if (new > 0)
+ if (close(new)) return log_error("close");
+
+ addrlen = sizeof(addr);
+ new = accept(sock, (struct sockaddr *)&addr, &addrlen);
+
+/*
+ * I wish there was a portable way to classify errno's... In this case,
+ * it appears to be better to risk eating up the CPU on a fatal error
+ * rather than risk terminating the entire service because of a minor
+ * temporary error having to do with one particular connection attempt.
+ */
+ if (new < 0) continue;
+
+ now = times(&buf);
+
+ child_blocked = 1;
+
+ j = -1; n = 0;
+ for (i = 0; i < MAX_SESSIONS; i++) {
+ if (sessions[i].start > now)
+ sessions[i].start = 0;
+ if (sessions[i].pid ||
+ (sessions[i].start &&
+ now - sessions[i].start < MIN_DELAY * CLK_TCK)) {
+ if (sessions[i].addr.s_addr ==
+ addr.sin_addr.s_addr)
+ if (++n >= MAX_SESSIONS_PER_SOURCE) break;
+ } else
+ if (j < 0) j = i;
+ }
+
+ if (n >= MAX_SESSIONS_PER_SOURCE) {
+ if (!sessions[i].log ||
+ now < sessions[i].log ||
+ now - sessions[i].log >= MIN_DELAY * CLK_TCK) {
+ syslog(SYSLOG_PRIORITY,
+ "%s: per source limit reached",
+ inet_ntoa(addr.sin_addr));
+ sessions[i].log = now;
+ }
+ continue;
+ }
+
+ if (j < 0) {
+ syslog(SYSLOG_PRIORITY, "%s: sessions limit reached",
+ inet_ntoa(addr.sin_addr));
+ continue;
+ }
+
+ switch ((pid = fork())) {
+ case -1:
+ syslog(SYSLOG_PRIORITY, "%s: fork: %m",
+ inet_ntoa(addr.sin_addr));
+ break;
+
+ case 0:
+ syslog(SYSLOG_PRIORITY, "Session from %s",
+ inet_ntoa(addr.sin_addr));
+ if (close(sock)) return log_error("close");
+ if (dup2(new, 0) < 0) return log_error("dup2");
+ if (dup2(new, 1) < 0) return log_error("dup2");
+ if (dup2(new, 2) < 0) return log_error("dup2");
+ if (close(new)) return log_error("close");
+ return do_pop_session();
+
+ default:
+ sessions[j].addr = addr.sin_addr;
+ (volatile int)sessions[j].pid = pid;
+ sessions[j].start = now;
+ sessions[j].log = 0;
+ }
+ }
+}
+
+#endif
diff --git a/usr.sbin/popa3d/virtual.c b/usr.sbin/popa3d/virtual.c
new file mode 100644
index 00000000000..c11975d7a5d
--- /dev/null
+++ b/usr.sbin/popa3d/virtual.c
@@ -0,0 +1,185 @@
+/* $OpenBSD: virtual.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Virtual domain support.
+ */
+
+#include "params.h"
+
+#if POP_VIRTUAL
+
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 4
+#define _XPG4_2
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+extern int log_error(char *s);
+
+char *virtual_domain;
+char *virtual_spool;
+
+int virtual_startup(void)
+{
+ return 0;
+}
+
+static char *lookup(void)
+{
+ struct sockaddr_in sin;
+ int length;
+
+ length = sizeof(sin);
+ if (getsockname(0, (struct sockaddr *)&sin, &length)) {
+ if (errno == ENOTSOCK) return "";
+ log_error("getsockname");
+ return NULL;
+ }
+ if (length != sizeof(sin) || sin.sin_family != AF_INET) return NULL;
+
+ return inet_ntoa(sin.sin_addr);
+}
+
+static int isvaliduser(char *user)
+{
+ unsigned char *p;
+
+/* This is pretty liberal, but we're going to use direct syscalls only,
+ * and they have to accept all the printable characters */
+ for (p = (unsigned char *)user; *p; p++)
+ if (*p < ' ' || *p > 0x7E || *p == '.' || *p == '/') return 0;
+
+ if (p - (unsigned char *)user > NAME_MAX) return 0;
+
+ return 1;
+}
+
+struct passwd *virtual_userpass(char *user, char *pass, char **mailbox)
+{
+ struct passwd *pw, *result;
+ struct stat stat;
+ char auth[VIRTUAL_AUTH_SIZE];
+ char *address, *pathname;
+ char *template, *passwd;
+ int fail;
+ int fd, size;
+
+/* Make sure we don't try to authenticate globally if something fails
+ * before we find out whether the virtual domain is known to us */
+ virtual_domain = "UNKNOWN";
+ virtual_spool = NULL;
+
+ if (!(address = lookup())) return NULL;
+
+/* Authenticate globally (if supported) if run on a non-socket */
+ if (!*address) {
+ virtual_domain = NULL;
+ return NULL;
+ }
+
+ fail = 0;
+ if (!isvaliduser(user)) {
+ user = "INVALID";
+ fail = 1;
+ }
+
+/* This "can't happen", but is just too critical to not check explicitly */
+ if (strchr(address, '/') || strchr(user, '/'))
+ return NULL;
+
+ pathname = malloc(strlen(VIRTUAL_HOME_PATH) + strlen(address) +
+ strlen(VIRTUAL_AUTH_PATH) + strlen(user) + 4);
+ if (!pathname) return NULL;
+ sprintf(pathname, "%s/%s", VIRTUAL_HOME_PATH, address);
+
+ if (lstat(pathname, &stat)) {
+ if (errno == ENOENT)
+ virtual_domain = NULL;
+ else
+ log_error("lstat");
+ free(pathname);
+ return NULL;
+ }
+
+ if (!(address = strdup(address))) return NULL;
+ virtual_domain = address;
+
+ sprintf(pathname, "%s/%s/%s/%s", VIRTUAL_HOME_PATH, address,
+ VIRTUAL_AUTH_PATH, user);
+
+ if ((fd = open(pathname, O_RDONLY)) < 0 && errno != ENOENT) {
+ log_error("open");
+ fail = 1;
+ }
+
+ free(pathname);
+
+ virtual_spool = malloc(strlen(VIRTUAL_HOME_PATH) +
+ strlen(virtual_domain) +
+ strlen(VIRTUAL_SPOOL_PATH) + 3);
+ if (!virtual_spool) {
+ close(fd);
+ return NULL;
+ }
+ sprintf(virtual_spool, "%s/%s/%s", VIRTUAL_HOME_PATH, virtual_domain,
+ VIRTUAL_SPOOL_PATH);
+
+ size = 0;
+ if (fd >= 0) {
+ if (!fail) *mailbox = user;
+
+ if ((size = read(fd, auth, sizeof(auth))) < 0) {
+ log_error("read");
+ size = 0;
+ fail = 1;
+ }
+
+ close(fd);
+ }
+
+ if (size >= sizeof(auth)) {
+ size = 0;
+ fail = 1;
+ }
+
+ auth[size] = 0;
+
+ if (!(template = strtok(auth, ":")) || !*template) {
+ template = "INVALID";
+ fail = 1;
+ }
+ if (!(passwd = strtok(NULL, ":")) || !*passwd ||
+ *passwd == '*' || *passwd == '!') {
+ passwd = AUTH_DUMMY_SALT;
+ fail = 1;
+ }
+ if (!strtok(NULL, ":")) fail = 1;
+
+ if ((pw = getpwnam(template))) {
+ memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+ pw->pw_passwd = "*";
+ }
+ endpwent();
+
+ result = NULL;
+ if (!strcmp(crypt(pass, passwd), passwd) && !fail)
+ result = pw;
+
+ memset(auth, 0, sizeof(auth));
+
+ return result;
+}
+
+#endif
diff --git a/usr.sbin/popa3d/virtual.h b/usr.sbin/popa3d/virtual.h
new file mode 100644
index 00000000000..312e7c99743
--- /dev/null
+++ b/usr.sbin/popa3d/virtual.h
@@ -0,0 +1,36 @@
+/* $OpenBSD: virtual.h,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * 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