diff options
Diffstat (limited to 'usr.sbin/popa3d')
-rw-r--r-- | usr.sbin/popa3d/DESIGN | 204 | ||||
-rw-r--r-- | usr.sbin/popa3d/LICENSE | 9 | ||||
-rw-r--r-- | usr.sbin/popa3d/Makefile | 10 | ||||
-rw-r--r-- | usr.sbin/popa3d/VIRTUAL | 5 | ||||
-rw-r--r-- | usr.sbin/popa3d/auth_passwd.c | 41 | ||||
-rw-r--r-- | usr.sbin/popa3d/database.c | 88 | ||||
-rw-r--r-- | usr.sbin/popa3d/database.h | 55 | ||||
-rw-r--r-- | usr.sbin/popa3d/mailbox.c | 453 | ||||
-rw-r--r-- | usr.sbin/popa3d/mailbox.h | 32 | ||||
-rw-r--r-- | usr.sbin/popa3d/misc.c | 89 | ||||
-rw-r--r-- | usr.sbin/popa3d/misc.h | 29 | ||||
-rw-r--r-- | usr.sbin/popa3d/params.h | 197 | ||||
-rw-r--r-- | usr.sbin/popa3d/pop_auth.c | 89 | ||||
-rw-r--r-- | usr.sbin/popa3d/pop_auth.h | 29 | ||||
-rw-r--r-- | usr.sbin/popa3d/pop_root.c | 220 | ||||
-rw-r--r-- | usr.sbin/popa3d/pop_trans.c | 248 | ||||
-rw-r--r-- | usr.sbin/popa3d/pop_trans.h | 12 | ||||
-rw-r--r-- | usr.sbin/popa3d/popa3d.8 | 38 | ||||
-rw-r--r-- | usr.sbin/popa3d/protocol.c | 276 | ||||
-rw-r--r-- | usr.sbin/popa3d/protocol.h | 105 | ||||
-rw-r--r-- | usr.sbin/popa3d/standalone.c | 211 | ||||
-rw-r--r-- | usr.sbin/popa3d/virtual.c | 185 | ||||
-rw-r--r-- | usr.sbin/popa3d/virtual.h | 36 |
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(¶ms); + 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(¶ms); + 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(¶ms); + 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(¶ms); + if (number < 1 || number > db.total_count) return POP_ERROR; + lines = pop_get_int(¶ms); + 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(¶ms); + 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 |