popa3d-1.0.3/0000700000000000000000000000000012014013012011345 5ustar rootrootpopa3d-1.0.3/pop_trans.c0000600000000000000000000001311707630270200013537 0ustar rootroot/* * TRANSACTION state handling. */ #include #include #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 %u %lu", db.visible_count, db.visible_size)) return POP_CRASH_NETFAIL; return POP_QUIET; } static int pop_trans_list_or_uidl_all(int uidl) { unsigned int number; struct db_message *msg; if (pop_reply_ok()) return POP_CRASH_NETFAIL; for (number = 1; number <= db.total_count; number++) { msg = db.array[number - 1]; if (msg->flags & MSG_DELETED) continue; if (uidl) { if (pop_reply("%u " "%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_NETFAIL; } else if (pop_reply("%u %lu", number, msg->size)) return POP_CRASH_NETFAIL; } if (pop_reply_terminate()) return POP_CRASH_NETFAIL; return POP_QUIET; } static int pop_trans_list_or_uidl(char *params, int uidl) { int number; struct db_message *msg; if (!params) return pop_trans_list_or_uidl_all(uidl); 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_NETFAIL; } else if (pop_reply("+OK %d %lu", number, msg->size)) return POP_CRASH_NETFAIL; 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; int event; 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 ((event = mailbox_get(msg, -1)) != POP_OK) return event; #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; int event; 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 ((event = mailbox_get(msg, lines)) != POP_OK) return event; 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 %u", db.last)) return POP_CRASH_NETFAIL; 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, 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 event; if (!pop_sane()) return 1; if (db_load(spool, mailbox)) { syslog(SYSLOG_PRI_HI, "Failed or refused to load %s/%s", spool, mailbox); pop_reply_error(); return 0; } syslog(SYSLOG_PRI_LO, "%u message%s (%lu byte%s) loaded", db.total_count, db.total_count == 1 ? "" : "s", db.total_size, db.total_size == 1 ? "" : "s"); if (pop_reply_ok()) event = POP_CRASH_NETFAIL; else switch ((event = pop_handle_state(pop_trans_commands))) { case POP_STATE: if (mailbox_update()) { if (db.flags & DB_STALE) break; syslog(SYSLOG_PRI_ERROR, "Failed to update %s/%s", spool, mailbox); pop_reply_error(); break; } syslog(SYSLOG_PRI_LO, "%u (%lu) deleted, %u (%lu) 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_CRASH_NETFAIL: syslog(SYSLOG_PRI_LO, "Premature disconnect"); break; case POP_CRASH_NETTIME: syslog(SYSLOG_PRI_LO, "Connection timed out"); } if (db.flags & DB_STALE) syslog(SYSLOG_PRI_LO, "Another MUA active, giving up"); else if (event == POP_CRASH_SERVER) syslog(SYSLOG_PRI_ERROR, "Server failure accessing %s/%s", spool, mailbox); mailbox_close(); return 0; } popa3d-1.0.3/mailbox.c0000600000000000000000000003170710402562450013174 0ustar rootroot/* * Mailbox access. */ #include #include #include #include #include #include #include #include #include #include #include "md5/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 unsigned 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, of n2 chars. */ #ifdef __GNUC__ __inline__ #endif static int eq(char *s1, int n1, char *s2, int n2) { if (n1 < n2) return 0; if (!memcmp(s1, s2, n2)) return 1; return !strncasecmp(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 */ unsigned long file_offset, line_offset; /* Their offsets in the file */ unsigned long offset; /* A line fragment's offset */ 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; /* the state information */ int fixed, received; /* ...and more of it */ 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; if (stat.st_size > MAX_MAILBOX_OPEN_BYTES || stat.st_size > ~0UL) return 1; mailbox_size = stat.st_size; if (!mailbox_size) return 0; 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 (stat.st_size < mailbox_size) { db.flags |= DB_STALE; return 1; } if (stat.st_size > MAX_MAILBOX_WORK_BYTES || stat.st_size > ~0UL) return 1; if (lseek(mailbox_fd, 0, SEEK_SET) < 0) return 1; db_op = db_compare; cmp = db.head; } memset(&msg, 0, sizeof(msg)); MD5_Init(&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, */ received = 0; /* and haven't got a Received: header yet */ /* * 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 LFs 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; block = FILE_BUFFER_SIZE; if (!init && block > mailbox_size - file_offset) block = mailbox_size - file_offset; 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 (line[0] == 'F' && length >= 5 && line[1] == 'r' && line[2] == 'o' && line[3] == 'm' && line[4] == ' ') { /* Process the previous one first, if exists */ if (offset) { /* If we aren't at the very beginning, there must have been a message */ if (!msg.data_offset) break; msg.raw_size = offset - msg.raw_offset; msg.data_size = offset - body - msg.data_offset; msg.size -= body << 1; MD5_Final(msg.hash, &hash); if (db_op(&msg)) break; } /* Now prepare for parsing the new one */ msg.raw_offset = offset; msg.data_offset = 0; MD5_Init(&hash); header = 1; body = 0; fixed = received = 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 LFs 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'; if (!header) { /* If we're no longer in message headers and we see more data, then it's * the body. */ if (msg.data_offset) body = 1; /* The rest of actions in this loop are for header lines only */ continue; } /* Blank line ends message headers */ if (blank) { header = 0; continue; } /* Some header lines are known to remain fixed over MUA runs */ if (start) switch (line[0]) { case '\t': case ' ': /* Inherit "fixed" from the previous line */ break; case 'R': case 'r': /* One Received: header from the local MTA should be sufficient */ fixed = !received && (received = eq(line, length, "Received:", 9)); break; case 'D': case 'd': fixed = eq(line, length, "Delivered-To:", 13) || (!received && eq(line, length, "Date:", 5)); break; case 'M': case 'm': fixed = !received && eq(line, length, "Message-ID:", 11); break; case 'X': /* Let the local delivery agent help generate unique IDs but don't blindly * trust this header alone as it could just as easily come from the remote. */ fixed = eq(line, length, "X-Delivery-ID:", 14); break; default: fixed = 0; continue; } /* We can hash all fragments of those lines, for UIDL */ if (fixed) MD5_Update(&hash, line, length); } while (1); free(file_buffer); if (done) { /* Process the last message */ if (offset != mailbox_size) return 1; if (!msg.data_offset) return 1; msg.raw_size = offset - msg.raw_offset; msg.data_size = offset - (blank & body) - msg.data_offset; msg.size -= (blank & body) << 1; MD5_Final(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 = concat(spool, "/", mailbox, NULL); if (!pathname) return 1; if (lstat(pathname, &stat)) { free(pathname); return errno != ENOENT; } if (!S_ISREG(stat.st_mode)) { free(pathname); return 1; } if (!stat.st_size) { free(pathname); return 0; } mailbox_fd = open(pathname, O_RDWR | O_NOCTTY | O_NONBLOCK); 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) { int event; if (mailbox_changed()) return POP_CRASH_SERVER; /* The calls to mailbox_changed() will set DB_STALE if that is the case */ if (lseek(mailbox_fd, msg->data_offset, SEEK_SET) < 0) { mailbox_changed(); return POP_CRASH_SERVER; } if ((event = pop_reply_multiline(mailbox_fd, msg->data_size, lines))) { if (event == POP_CRASH_SERVER) mailbox_changed(); return event; } if (mailbox_changed()) return POP_CRASH_SERVER; if (pop_reply_terminate()) return POP_CRASH_NETFAIL; return POP_OK; } static int mailbox_write(char *buffer) { struct db_message *msg; unsigned long old, new; unsigned long size; 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 ((size = msg->raw_size - (old - msg->raw_offset))) { if (lseek(mailbox_fd, old, SEEK_SET) < 0) return 1; if (size > FILE_BUFFER_SIZE) size = FILE_BUFFER_SIZE; block = read(mailbox_fd, buffer, size); 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_loop(mailbox_fd, buffer, block) != block) return 1; /* Cannot overflow unless locking is bypassed */ if ((old += block) < block || (new += block) < block) return 1; } 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); } popa3d-1.0.3/database.c0000600000000000000000000000306407536607770013323 0ustar rootroot/* * Message database management. */ #include #include #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) goto out_fail; if (++db.total_count <= 0) goto out_undo_count; if ((db.total_size += msg->size) < msg->size) goto out_undo_size; entry = malloc(sizeof(struct db_message)); if (!entry) goto out_undo_size; 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; return 0; out_undo_size: db.total_size -= msg->size; out_undo_count: db.total_count--; out_fail: return 1; } 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) { unsigned long size; struct db_message *entry; unsigned 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 / 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; } popa3d-1.0.3/auth_passwd.c0000600000000000000000000000140112014013012014031 0ustar rootroot/* * The /etc/passwd authentication routine. */ #include "params.h" #if AUTH_PASSWD && !VIRTUAL_ONLY #define _XOPEN_SOURCE 4 #define _XOPEN_SOURCE_EXTENDED #define _XOPEN_VERSION 4 #define _XPG4_2 #include #include #include #include struct passwd *auth_userpass(char *user, char *pass, int *known) { struct passwd *pw, *result; *known = (pw = getpwnam(user)) != NULL; endpwent(); result = NULL; if (!pw || !*pw->pw_passwd || *pw->pw_passwd == '*' || *pw->pw_passwd == '!') crypt(pass, AUTH_DUMMY_SALT); else { char *hash = crypt(pass, pw->pw_passwd); if (hash && !strcmp(hash, pw->pw_passwd)) result = pw; } if (pw) memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); return result; } #endif popa3d-1.0.3/md5/0000700000000000000000000000000012231226475012056 5ustar rootrootpopa3d-1.0.3/md5/md5.h0000600000000000000000000000260212231225150012703 0ustar rootroot/* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: * Alexander Peslyak, better known as Solar Designer * * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * See md5.c for more information. */ #ifdef HAVE_OPENSSL #include #elif !defined(_MD5_H) #define _MD5_H /* Any 32-bit or wider unsigned integer data type will do */ typedef unsigned int MD5_u32plus; typedef struct { MD5_u32plus lo, hi; MD5_u32plus a, b, c, d; unsigned char buffer[64]; MD5_u32plus block[16]; } MD5_CTX; extern void MD5_Init(MD5_CTX *ctx); extern void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); #endif popa3d-1.0.3/md5/md5.c0000600000000000000000000002061312231225422012702 0ustar rootroot/* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: * Alexander Peslyak, better known as Solar Designer * * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * (This is a heavily cut-down "BSD license".) * * This differs from Colin Plumb's older public domain implementation in that * no exactly 32-bit integer data type is required (any 32-bit or wider * unsigned integer data type will do), there's no compile-time endianness * configuration, and the function prototypes match OpenSSL's. No code from * Colin Plumb's implementation has been reused; this comment merely compares * the properties of the two independent implementations. * * The primary goals of this implementation are portability and ease of use. * It is meant to be fast, but not as fast as possible. Some known * optimizations are not included to reduce source code size and avoid * compile-time configuration. */ #ifndef HAVE_OPENSSL #include #include "md5.h" /* * The basic MD5 functions. * * F and G are optimized compared to their RFC 1321 definitions for * architectures that lack an AND-NOT instruction, just like in Colin Plumb's * implementation. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) #define H(x, y, z) (((x) ^ (y)) ^ (z)) #define H2(x, y, z) ((x) ^ ((y) ^ (z))) #define I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); /* * SET reads 4 input bytes in little-endian byte order and stores them * in a properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned * memory accesses is just an optimization. Nothing will break if it * doesn't work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define SET(n) \ (*(MD5_u32plus *)&ptr[(n) * 4]) #define GET(n) \ SET(n) #else #define SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) #define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update * the bit counters. There are no alignment requirements. */ static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) { const unsigned char *ptr; MD5_u32plus a, b, c, d; MD5_u32plus saved_a, saved_b, saved_c, saved_d; ptr = (const unsigned char *)data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) STEP(F, c, d, a, b, SET(2), 0x242070db, 17) STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) /* Round 2 */ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) STEP(G, d, a, b, c, GET(10), 0x02441453, 9) STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) /* Round 3 */ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) /* Round 4 */ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += 64; } while (size -= 64); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void MD5_Init(MD5_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; } void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) { MD5_u32plus saved_lo; unsigned long used, available; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) ctx->hi++; ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used) { available = 64 - used; if (size < available) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; body(ctx, ctx->buffer, 64); } if (size >= 64) { data = body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } void MD5_Final(unsigned char *result, MD5_CTX *ctx) { unsigned long used, available; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; available = 64 - used; if (available < 8) { memset(&ctx->buffer[used], 0, available); body(ctx, ctx->buffer, 64); used = 0; available = 64; } memset(&ctx->buffer[used], 0, available - 8); ctx->lo <<= 3; ctx->buffer[56] = ctx->lo; ctx->buffer[57] = ctx->lo >> 8; ctx->buffer[58] = ctx->lo >> 16; ctx->buffer[59] = ctx->lo >> 24; ctx->buffer[60] = ctx->hi; ctx->buffer[61] = ctx->hi >> 8; ctx->buffer[62] = ctx->hi >> 16; ctx->buffer[63] = ctx->hi >> 24; body(ctx, ctx->buffer, 64); result[0] = ctx->a; result[1] = ctx->a >> 8; result[2] = ctx->a >> 16; result[3] = ctx->a >> 24; result[4] = ctx->b; result[5] = ctx->b >> 8; result[6] = ctx->b >> 16; result[7] = ctx->b >> 24; result[8] = ctx->c; result[9] = ctx->c >> 8; result[10] = ctx->c >> 16; result[11] = ctx->c >> 24; result[12] = ctx->d; result[13] = ctx->d >> 8; result[14] = ctx->d >> 16; result[15] = ctx->d >> 24; memset(ctx, 0, sizeof(*ctx)); } #endif popa3d-1.0.3/params.h0000600000000000000000000001564210402562450013031 0ustar rootroot/* * Global POP daemon parameters. */ #ifndef _POP_PARAMS_H #define _POP_PARAMS_H /* * Our name to use when talking to various interfaces. */ #define POP_SERVER "popa3d" /* * Are we going to be a standalone server or start via an inetd clone? */ #define POP_STANDALONE 0 #if POP_STANDALONE /* * Should the command line options be supported? * If enabled, popa3d will default to inetd mode and will require a -D * to actually enable the standalone mode. */ #define POP_OPTIONS 1 /* * The address and port to listen on. */ #define DAEMON_ADDR "0.0.0.0" /* INADDR_ANY */ #define DAEMON_PORT 110 /* * Should libwrap be used? * * This may make things slower and also adds to code running as root, * so it is recommended that you use a packet filter instead. This * option is provided primarily as a way to meet conventions of certain * systems where all services obey libwrap access controls. */ #define DAEMON_LIBWRAP 0 #if DAEMON_LIBWRAP /* * How do we talk to libwrap? */ #define DAEMON_LIBWRAP_IDENT POP_SERVER #endif /* * Limit the number of POP sessions we can handle at a time to reduce * the impact of connection flood DoS attacks. * * The defaults are rather large. It is recommended that you decrease * MAX_SESSIONS and MAX_SESSIONS_PER_SOURCE to 100 and 10, respectively, * if that would be sufficient for your users. */ #define MAX_SESSIONS 500 #define MAX_SESSIONS_PER_SOURCE 50 #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 /* * A pseudo-user to run as before authentication. The user and its UID * must not be used for any other purpose. */ #define POP_USER POP_SERVER /* * An empty directory to chroot to before authentication. The directory * and its parent directories must not be writable by anyone but root. */ #define POP_CHROOT "/var/empty" /* * 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. * * The defaults are rather large (2 GB filled with messages as small as * 1 KB each). It is recommended that you decrease MAX_MAILBOX_MESSAGES, * MAX_MAILBOX_OPEN_BYTES, and MAX_MAILBOX_WORK_BYTES to, say, 100000, * 100000000 (100 MB), and 150000000 (150 MB), respectively, if that * would be sufficient for your users. */ #define MAX_MAILBOX_MESSAGES 2097152 #define MAX_MAILBOX_OPEN_BYTES 2147483647 #define MAX_MAILBOX_WORK_BYTES 2147483647 #if !VIRTUAL_ONLY /* * Choose the password authentication method your system uses: * * AUTH_PASSWD Use getpwnam(3) only, for *BSD or readable passwd; * AUTH_SHADOW Use shadow passwords directly (not via PAM); * AUTH_PAM Use PAM in the old-fashioned way; * AUTH_PAM_USERPASS Talk to pam_userpass via Linux-PAM binary prompts * USE_LIBPAM_USERPASS ...and use libpam_userpass. * * Note that there's no built-in password aging support. */ #define AUTH_PASSWD 0 #define AUTH_SHADOW 1 #define AUTH_PAM 0 #define AUTH_PAM_USERPASS 0 #define USE_LIBPAM_USERPASS 0 #if AUTH_PAM || AUTH_PAM_USERPASS #define AUTH_PAM_SERVICE POP_SERVER #endif #endif #if POP_VIRTUAL || AUTH_PASSWD || AUTH_SHADOW /* * 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). */ #define AUTH_DUMMY_SALT "xx" #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. * * #undef this for qmail-style $HOME/Mailbox mailboxes. */ #define MAIL_SPOOL_PATH "/var/mail" #ifndef MAIL_SPOOL_PATH /* * The mailbox file name relative to the user's home directory. */ #define HOME_MAILBOX_NAME "Mailbox" #endif #endif /* * Locking method your system uses for user mailboxes. It is important * that you set this correctly. * * *BSDs use flock(2), others typically use fcntl(2). */ #define LOCK_FCNTL 1 #define LOCK_FLOCK 0 /* * How do we talk to syslogd? These should be fine for most systems. */ #define SYSLOG_IDENT POP_SERVER #define SYSLOG_OPTIONS LOG_PID #define SYSLOG_FACILITY LOG_DAEMON #define SYSLOG_PRI_LO LOG_INFO #define SYSLOG_PRI_HI LOG_NOTICE #define SYSLOG_PRI_ERROR LOG_CRIT /* * 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 popa3d-1.0.3/standalone.c0000600000000000000000000001261010403177107013663 0ustar rootroot/* * Standalone POP server: accepts connections, checks the anti-flood limits, * logs and starts the actual POP sessions. */ #include "params.h" #if POP_STANDALONE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DAEMON_LIBWRAP #include int allow_severity = SYSLOG_PRI_LO; int deny_severity = SYSLOG_PRI_HI; #endif /* * 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); typedef volatile sig_atomic_t va_int; /* * 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 */ volatile 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 va_int child_blocked; /* We use blocking to avoid races */ static va_int child_pending; /* Are any dead children waiting? */ /* * SIGCHLD handler. */ 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; } } signal(SIGCHLD, handle_child); errno = saved_errno; } #if DAEMON_LIBWRAP static void check_access(int sock) { struct request_info request; request_init(&request, RQ_DAEMON, DAEMON_LIBWRAP_IDENT, RQ_FILE, sock, 0); fromhost(&request); if (!hosts_access(&request)) { /* refuse() shouldn't return... */ refuse(&request); /* ...but just in case */ exit(1); } } #endif #if POP_OPTIONS int do_standalone(void) #else int main(void) #endif { int true = 1; int sock, new; struct sockaddr_in addr; socklen_t addrlen; int pid; struct tms buf; clock_t min_delay, now, log; 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(); #if defined(_SC_CLK_TCK) || !defined(CLK_TCK) min_delay = MIN_DELAY * sysconf(_SC_CLK_TCK); #else min_delay = MIN_DELAY * CLK_TCK; #endif child_blocked = 1; child_pending = 0; signal(SIGCHLD, handle_child); memset((void *)sessions, 0, sizeof(sessions)); log = 0; new = 0; while (1) { child_blocked = 0; if (child_pending) raise(SIGCHLD); if (new > 0) if (close(new)) return log_error("close"); addrlen = sizeof(addr); new = accept(sock, (struct sockaddr *)&addr, &addrlen); /* * I wish there were 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); if (!now) now = 1; 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)) { 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) { syslog(SYSLOG_PRI_HI, "%s: per source limit reached", inet_ntoa(addr.sin_addr)); sessions[i].log = now; } continue; } if (j < 0) { if (!log || now < log || now - log >= min_delay) { syslog(SYSLOG_PRI_HI, "%s: sessions limit reached", inet_ntoa(addr.sin_addr)); log = now; } continue; } switch ((pid = fork())) { case -1: syslog(SYSLOG_PRI_ERROR, "%s: fork: %m", inet_ntoa(addr.sin_addr)); break; case 0: if (close(sock)) return log_error("close"); #if DAEMON_LIBWRAP check_access(new); #endif syslog(SYSLOG_PRI_LO, "Session from %s", inet_ntoa(addr.sin_addr)); 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; sessions[j].pid = pid; sessions[j].start = now; sessions[j].log = 0; } } } #endif popa3d-1.0.3/CONTACT0000600000000000000000000000163310336632032012406 0ustar rootrootThe popa3d homepage is: http://www.openwall.com/popa3d/ There's a mailing list where you can share your experience with popa3d and ask questions. To subscribe, send an empty message to . The list archives are available on MARC: http://marc.theaimsgroup.com/?l=popa3d-users In addition to the popa3d-users list, you have the option to direct your comments regarding popa3d to the author privately at . Commercial support for popa3d is available from Openwall: http://www.openwall.com/services/ We may help you install popa3d or even help you integrate it into an existing mail server and mail user database setup you might have. The latter might be a combination of our free software development and consulting services. -- Solar Designer $Owl: Owl/packages/popa3d/popa3d/CONTACT,v 1.3 2005/11/16 13:28:58 solar Exp $ popa3d-1.0.3/protocol.h0000600000000000000000000000471310402562451013405 0ustar rootroot/* * 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_NETFAIL 5 /* Network failure */ #define POP_CRASH_NETTIME 6 /* Network timeout */ #define POP_CRASH_SERVER 7 /* POP server failure */ /* * POP command description. */ struct pop_command { char *name; int (*handler)(char *params); }; /* * Internal POP command buffer. */ struct pop_buffer { unsigned 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 POP_OK or one of * the POP_CRASH_* event codes. */ extern int pop_reply_multiline(int fd, unsigned 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 popa3d-1.0.3/pop_trans.h0000600000000000000000000000021707446141035013551 0ustar rootroot/* * TRANSACTION state handling. */ #ifndef _POP_TRANS_H #define _POP_TRANS_H extern int do_pop_trans(char *spool, char *mailbox); #endif popa3d-1.0.3/CHANGES0000600000000000000000000001055312014013012012346 0ustar rootroot Changes made between 1.0.2 and 1.0.3 (2012/08/15). Handle possible NULL returns from crypt(3). Revised the included MD5 routines to help the compiler detect a common subexpression between steps in round 3. Switched to heavily cut-down BSD license. Changes made between 1.0.1 and 1.0.2 (2006/05/23). A couple of optimizations specific to x86-64 have been applied to the included MD5 routines. Changes made between 1.0 and 1.0.1 (2006/03/07). Use sysconf(_SC_CLK_TCK) instead of CLK_TCK when _SC_CLK_TCK is known to be available or CLK_TCK is not. This is needed for portability to glibc 2.3.90+ and possibly to other recent and future systems. Use socklen_t where appropriate. Changes made between 0.6.4.1 and 1.0 (2005/05/26). Corrected the source code to not break C strict aliasing rules (this only affected auth_pam.c). With Sun PAM (Solaris, HP-UX), insist on only one PAM message per call to the conversation function because of differences in the layout of the "msg" parameter. Bumped the default limits to values that are way too high for most systems (I'm sure these will still be insufficient for some, though, but hopefully those systems actually got experienced sysadmins). Added comments suggesting that these defaults be decreased on particular installs. Changes made between 0.6.4 and 0.6.4.1 (2004/07/22). Bugfix: actually zeroize the context structure in MD5_Final(). Thanks to Andrey Panin and Timo Sirainen for bringing this to my attention. This change makes no difference for popa3d, but may be important for possible other applications which might use these MD5 routines. Changes made between 0.6.3 and 0.6.4 (2003/11/17). The uses of sprintf(3) have been replaced by the concat() function implemented locally. Changes made between 0.6.2 and 0.6.3 (2003/04/13). libpam_userpass support (with pam_userpass 0.9+). Changes made between 0.6.1 and 0.6.2 (2003/03/10). Rate-limit the "sessions limit reached" log message similarly to the per-source one; spotted by Michael Tokarev. Started maintaining this change log (including information on a few past versions), due to popular demand. Added a separate file with contact information (homepage, mailing list, author e-mail address, commercial support). Updated the installation instructions to note the importance of authentication and locking method choice, added instructions on the use of xinetd and popa3d's standalone mode. Changes made between 0.6 and 0.6.1 (2003/03/02). Ensure proper logging of abnormally terminated sessions: distinguish server failures from external modification to the mailbox by other instances of popa3d or other MUAs. Previously, if external mailbox modification would occur during processing of a RETR command, popa3d could improperly log a "server failure" (0.6) or even a "premature disconnect" (older versions). Added version.c and the -V option to print out version information. Changes made between 0.5.9 and 0.6 (2003/02/20). Corrected the message size reporting bug introduced with 0.4.9.3 and now reported on popa3d-users by Nuno Teixeira. The only known POP3 client known to complain about this is fetchmail, and it would get the mail correctly anyway. Changes made between 0.5.1 and 0.5.9 (2002/09/24). Correctness and interoperability fixes. This includes a workaround for an Outlook Express client bug which would show up on body-less messages. Changes made between 0.5 and 0.5.1 (2002/04/17). Re-worked all of the UIDL calculation, adding support for multi-line headers and re-considering which headers to use. Let the local delivery agent help generate unique IDs by setting the X-Delivery-ID: header. Changes made between 0.4 and 0.5 (2001/10/28). Added PAM support (old-fashioned or via Linux-PAM binary prompts), choice for fcntl(2) and/or flock(2) locking, qmail-style ~/Mailbox support, improved logging, optional libwrap support, virtual domain support hooks. Parts of the daemon code are now run in a chroot jail (/var/empty). The inetd vs. standalone mode setting is now run-time configurable. The GNU MD5 routines have been replaced with own public domain implementation to relax the license for the entire package (now BSD and (L)GPL-compatible), solve certain portability issues, and reduce code size. Added a popa3d(8) manual page based on the one Camiel Dobbelaar wrote for OpenBSD. $Owl: Owl/packages/popa3d/popa3d/CHANGES,v 1.12 2012/08/15 09:29:17 solar Exp $ popa3d-1.0.3/pop_auth.h0000600000000000000000000000072707446141035013371 0ustar rootroot/* * 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 user, use NULL for non-existent. */ extern void log_pop_auth(int result, char *user); #endif popa3d-1.0.3/virtual.c0000600000000000000000000000716412014013012013211 0ustar rootroot/* * Virtual domain support. */ #include "params.h" #if POP_VIRTUAL #define _XOPEN_SOURCE 4 #define _XOPEN_SOURCE_EXTENDED #define _XOPEN_VERSION 4 #define _XPG4_2 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NAME_MAX #define NAME_MAX 255 #endif #include "misc.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; socklen_t 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 is_valid_user(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, int *known) { struct passwd *pw, *result; struct stat stat; char auth[VIRTUAL_AUTH_SIZE]; char *address, *pathname; char *template, *passwd; int fail; int fd, size; *known = 0; /* 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 (!is_valid_user(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 = concat(VIRTUAL_HOME_PATH, "/", address, NULL); if (!pathname) return NULL; if (lstat(pathname, &stat)) { free(pathname); if (errno == ENOENT) virtual_domain = NULL; else log_error("lstat"); return NULL; } free(pathname); if (!(address = strdup(address))) return NULL; virtual_domain = address; pathname = concat(VIRTUAL_HOME_PATH, "/", address, "/", VIRTUAL_AUTH_PATH, "/", user, NULL); if (!pathname) return NULL; if ((fd = open(pathname, O_RDONLY)) < 0 && errno != ENOENT) { log_error("open"); fail = 1; } free(pathname); virtual_spool = concat(VIRTUAL_HOME_PATH, "/", virtual_domain, "/", VIRTUAL_SPOOL_PATH, NULL); if (!virtual_spool) { if (fd >= 0) close(fd); return NULL; } size = 0; if (fd >= 0) { *known = !fail; 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)); endpwent(); result = NULL; { char *computed_hash = crypt(pass, passwd); if (computed_hash && !strcmp(computed_hash, passwd) && !fail) result = pw; } memset(auth, 0, sizeof(auth)); return result; } #endif popa3d-1.0.3/auth_shadow.c0000600000000000000000000000320012014013012014014 0ustar rootroot/* * The /etc/shadow authentication routine. This one is really tricky, * in order to make sure we don't have an /etc/shadow fd or sensitive * data in our address space after we drop the root privileges. It is * arguable whether this was worth the extra code and the performance * penalty or not, but such discussions are outside of the scope of a * comment like this. ;^) */ #include "params.h" #if AUTH_SHADOW && !VIRTUAL_ONLY #define _XOPEN_SOURCE 4 #define _XOPEN_SOURCE_EXTENDED #define _XOPEN_VERSION 4 #define _XPG4_2 #include #include #include #include #include #include #include extern int log_error(char *s); struct passwd *auth_userpass(char *user, char *pass, int *known) { int channel[2]; struct passwd *pw; struct spwd *spw; char result; if ((*known = (pw = getpwnam(user)) != NULL)) memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); endpwent(); result = 0; if (pipe(channel)) { log_error("pipe"); return NULL; } switch (fork()) { case -1: log_error("fork"); return NULL; case 0: close(channel[0]); if (!(spw = getspnam(user)) || !pw || !*spw->sp_pwdp || *spw->sp_pwdp == '*' || *spw->sp_pwdp == '!') crypt(pass, AUTH_DUMMY_SALT); else { char *hash = crypt(pass, spw->sp_pwdp); if (hash && !strcmp(hash, spw->sp_pwdp)) result = 1; } write(channel[1], &result, 1); exit(0); } if (close(channel[1])) pw = NULL; else { if (read(channel[0], &result, 1) != 1) pw = NULL; if (result != 1) pw = NULL; if (close(channel[0])) pw = NULL; } wait(NULL); return result == 1 ? pw : NULL; } #endif popa3d-1.0.3/startup.c0000600000000000000000000000213010402540273013226 0ustar rootroot/* * Command line option parsing. */ #include "params.h" #if POP_OPTIONS #include #include #include /* version.c */ extern char popa3d_version[]; extern char popa3d_date[]; /* standalone.c */ extern int do_standalone(void); /* pop_root.c */ extern int do_pop_startup(void); extern int do_pop_session(void); #ifdef HAVE_PROGNAME extern char *__progname; #define progname __progname #else static char *progname; #endif static void usage(void) { fprintf(stderr, "Usage: %s [-D] [-V]\n", progname); exit(1); } static void version(void) { printf("popa3d version %s (%s)\n", popa3d_version, popa3d_date); exit(0); } int main(int argc, char **argv) { int c; int standalone = 0; #ifndef HAVE_PROGNAME if (!(progname = argv[0])) progname = POP_SERVER; #endif while ((c = getopt(argc, argv, "DV")) != -1) { switch (c) { case 'D': standalone++; break; case 'V': version(); default: usage(); } } if (optind != argc) usage(); if (standalone) return do_standalone(); if (do_pop_startup()) return 1; return do_pop_session(); } #endif popa3d-1.0.3/database.h0000600000000000000000000000270407537100746013320 0ustar rootroot/* * Message database management. */ #ifndef _POP_DATABASE_H #define _POP_DATABASE_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; unsigned long size; /* Size as reported via POP */ unsigned int flags; /* MSG_* flags defined above */ unsigned long raw_offset; /* Raw, with the "From " line */ unsigned long raw_size; unsigned long data_offset; /* Just the message itself */ unsigned long data_size; 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 */ unsigned int total_count; /* All loaded messages and */ unsigned int visible_count; /* just those not DELEted */ unsigned long total_size; /* Their cumulative sizes, */ unsigned long visible_size; /* to be reported via POP */ unsigned int flags; /* DB_* flags defined above */ #if POP_SUPPORT_LAST unsigned 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 popa3d-1.0.3/popa3d.80000600000000000000000000000723310151650062012647 0ustar rootroot.TH POPA3D 8 "2 March 2003" "Openwall Project" "System Administration" .SH NAME popa3d \- Post Office Protocol (POP3) server .SH SYNOPSIS .B popa3d .RB [ -D ] .RB [ -V ] .SH DESCRIPTION .B popa3d is a Post Office Protocol version 3 (POP3) server. .PP A POP3 server operates on local mailboxes on behalf of its remote users. Users can connect at any time to check their mailbox and fetch the mail that has accumulated. The advantage of this "pull" approach is that any user with a simple POP3-capable mail reader program can receive mail, eschewing the need for a full-fledged Mail Transfer Agent (MTA) and a permanent network connection. .PP Note that POP3 can only be used to retrieve mail, not to send it. To send mail, the SMTP protocol is commonly used. .PP For access to a mailbox through POP3, the username must be in the password database. Additionally, .B popa3d does not permit null passwords and will refuse to serve mail for root (UID 0) users. .SH OPTIONS .TP .B -D Standalone server mode. In this mode, .B popa3d will become a daemon, accepting connections on the pop3 port (110/tcp) and forking child processes to handle them. This has lower overhead than starting .B popa3d from an inetd equivalent (which .B popa3d assumes by default) and is thus useful on busy servers to reduce load. In this mode .B popa3d also does quite a few checks to significantly reduce the impact of connection flood attacks. .TP .B -V Print version information and exit. .SH COMMANDS A normal POP3 session progresses through three states: AUTHORIZATION, TRANSACTION, and UPDATE. .PP After the TCP connection opens, the client must authenticate itself to the server during the AUTHORIZATION state. The following commands are supported in the AUTHORIZATION state (all command names are case-insensitive). .TP .BI USER " name" Authenticate as user .IR name . .TP .BI PASS " string" Authenticate using password .IR string . .TP .B QUIT Quit; do not enter UPDATE state. .PP When authorization is successful, the server enters the TRANSACTION state. The client can now list and retrieve messages or mark messages for deletion. The following commands are supported in the TRANSACTION state. .TP .BI DELE " msg" Mark message for deletion. .TP .B LAST Show highest message number accessed (obsolete). .TP \fBLIST\fR [\fImsg\fR] List message number and size. .TP .B NOOP Do nothing. .TP .B QUIT Quit; enter UPDATE state. .TP .BI RETR " msg" Retrieve message. .TP .B RSET Clear deletion marks. .TP .B STAT Return total number of messages and total size. .TP .BI TOP " msg n" Show top .I n lines of message body. .TP \fBUIDL\fR [\fImsg\fR] List message number and digest. .PP When the client issues the .B QUIT command in the TRANSACTION state, the server enters the UPDATE state. All messages that were marked for deletion are now removed. The server then closes the connection. .SH BUGS POP3 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. .PP There exist extensions to the protocol that are supposed to fix this problem. .B popa3d does not support them yet, partly because this isn't going to fully fix the problem. In fact, APOP and the weaker defined SASL mechanisms such as CRAM-MD5 may potentially be even less secure than transmission of plaintext passwords because of the requirement that plaintext equivalents be stored on the server. .SH AUTHORS Solar Designer .PP This manual page is based heavily on the one Camiel Dobbelaar wrote for OpenBSD. .SH SEE ALSO Official Internet Protocol Standard .IR "STD 53" , also known as .IR "RFC 1939" . .PP http://www.openwall.com/popa3d/ popa3d-1.0.3/misc.h0000600000000000000000000000156110402562450012474 0ustar rootroot/* * Miscellaneous system and library call 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 occurred; if the value is -1, errno is set appropriately. */ extern int write_loop(int fd, char *buffer, int count); /* * Concatenates a variable number of strings. The argument list must be * terminated with a NULL. Returns a pointer to malloc(3)'ed memory with * the concatenated string, or NULL on error. */ extern char *concat(char *s1, ...); #endif popa3d-1.0.3/version.c0000600000000000000000000000015312014013012013177 0ustar rootroot/* * popa3d version information. */ char popa3d_version[] = "1.0.3"; char popa3d_date[] = "2012/08/15"; popa3d-1.0.3/INSTALL0000600000000000000000000000462510336632032012425 0ustar rootroot Installing popa3d. 1. Read the comments in params.h and edit the #define's to suit your needs: $ vi params.h It is especially important to choose a password authentication method that will be right for your system (you need to pick exactly one) and the mailbox locking method your system uses (it must match your other mail software). 2. Possibly edit the Makefile (uncomment "-lcrypt" for Linux with glibc, FreeBSD or NetBSD, uncomment "-lpam" if you will use PAM): $ vi Makefile 3. Build popa3d: $ make 4. Create a non-privileged popa3d user, like this (or using whatever other user management commands your system has): # groupadd popa3d # useradd -g popa3d -d /dev/null -s /dev/null popa3d 5. Create an empty directory for popa3d to chroot to (unless your system provides a directory intended for this purpose already): # mkdir -m 755 /var/empty 6. Install the popa3d binary and the man page under /usr/local: # make install 7. If you intend to start popa3d via inetd (rather than run it as a standalone service), the /etc/inetd.conf line for popa3d should look like this: pop3 stream tcp nowait root /usr/sbin/tcpd /usr/local/sbin/popa3d This assumes that you have and want to use TCP wrappers. If not, you may omit the tcpd (but see the note below). Alternatively, if you're using xinetd, the configuration file section may be like this: service pop3 { socket_type = stream protocol = tcp wait = no user = root server = /usr/local/sbin/popa3d log_type = SYSLOG daemon info log_on_success = PID HOST DURATION log_on_failure = HOST instances = 100 per_source = 10 } The last 5 attributes are optional. Note: when started via an inetd clone, the logging of connections is left up to that inetd clone or TCP wrappers. If your inetd does not log connecting IP addresses and you don't want to use TCP wrappers, you should use popa3d in standalone mode. To actually start the service, you need to tell your inetd to reload configuration. Typically, this is achieved by sending it a SIGHUP: # kill -HUP `cat /var/run/inetd.pid` For xinetd, the reload signal is either SIGUSR2 for older versions or SIGHUP for newer ones, please consult your xinetd documentation. 8. If you intend to run popa3d as a standalone service (and you built it with standalone mode support!), you need to start it with: # /usr/local/sbin/popa3d -D $Owl: Owl/packages/popa3d/popa3d/INSTALL,v 1.3 2005/11/16 13:28:58 solar Exp $ popa3d-1.0.3/auth_pam.c0000600000000000000000000001137110402562450013332 0ustar rootroot/* * PAM authentication routines. */ #include "params.h" #if (AUTH_PAM || AUTH_PAM_USERPASS) && !VIRTUAL_ONLY #define _XOPEN_SOURCE 4 #define _XOPEN_SOURCE_EXTENDED #define _XOPEN_VERSION 4 #define _XPG4_2 #include #include #include #include #include #if (defined(__sun) || defined(__hpux)) && \ !defined(LINUX_PAM) && !defined(_OPENPAM) #define lo_const /* Sun's PAM doesn't use const here */ #else #define lo_const const #endif typedef lo_const void *pam_item_t; #if USE_LIBPAM_USERPASS #include #else #if AUTH_PAM_USERPASS #include #ifndef PAM_BP_RCONTROL /* Linux-PAM prior to 0.74 */ #define PAM_BP_RCONTROL PAM_BP_CONTROL #define PAM_BP_WDATA PAM_BP_DATA #define PAM_BP_RDATA PAM_BP_DATA #endif #define USERPASS_AGENT_ID "userpass" #define USERPASS_AGENT_ID_LENGTH 8 #define USERPASS_USER_MASK 0x03 #define USERPASS_USER_REQUIRED 1 #define USERPASS_USER_KNOWN 2 #define USERPASS_USER_FIXED 3 #endif typedef struct { char *user; char *pass; } pam_userpass_t; static int pam_userpass_conv(int num_msg, lo_const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { pam_userpass_t *userpass = (pam_userpass_t *)appdata_ptr; #if AUTH_PAM_USERPASS pamc_bp_t prompt; const char *input; char *output; char flags; if (num_msg != 1 || msg[0]->msg_style != PAM_BINARY_PROMPT) return PAM_CONV_ERR; prompt = (pamc_bp_t)msg[0]->msg; input = PAM_BP_RDATA(prompt); if (PAM_BP_RCONTROL(prompt) != PAM_BPC_SELECT || strncmp(input, USERPASS_AGENT_ID "/", USERPASS_AGENT_ID_LENGTH + 1)) return PAM_CONV_ERR; flags = input[USERPASS_AGENT_ID_LENGTH + 1]; input += USERPASS_AGENT_ID_LENGTH + 1 + 1; if ((flags & USERPASS_USER_MASK) == USERPASS_USER_FIXED && strcmp(input, userpass->user)) return PAM_CONV_AGAIN; if (!(*resp = malloc(sizeof(struct pam_response)))) return PAM_CONV_ERR; prompt = NULL; PAM_BP_RENEW(&prompt, PAM_BPC_DONE, strlen(userpass->user) + 1 + strlen(userpass->pass)); output = PAM_BP_WDATA(prompt); strcpy(output, userpass->user); output += strlen(output) + 1; memcpy(output, userpass->pass, strlen(userpass->pass)); (*resp)[0].resp_retcode = 0; (*resp)[0].resp = (char *)prompt; #else char *string; int i; #if (defined(__sun) || defined(__hpux)) && \ !defined(LINUX_PAM) && !defined(_OPENPAM) /* * Insist on only one message per call because of differences in the * layout of the "msg" parameter. It can be an array of pointers to * struct pam_message (Linux-PAM, OpenPAM) or a pointer to an array of * struct pam_message (Sun PAM). We only fully support the former. */ if (num_msg != 1) return PAM_CONV_ERR; #endif if (!(*resp = malloc(num_msg * sizeof(struct pam_response)))) return PAM_CONV_ERR; for (i = 0; i < num_msg; i++) { string = NULL; switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_ON: string = userpass->user; case PAM_PROMPT_ECHO_OFF: if (!string) string = userpass->pass; if (!(string = strdup(string))) break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: (*resp)[i].resp_retcode = PAM_SUCCESS; (*resp)[i].resp = string; continue; } while (--i >= 0) { if (!(*resp)[i].resp) continue; memset((*resp)[i].resp, 0, strlen((*resp)[i].resp)); free((*resp)[i].resp); (*resp)[i].resp = NULL; } free(*resp); *resp = NULL; return PAM_CONV_ERR; } #endif return PAM_SUCCESS; } #endif /* USE_LIBPAM_USERPASS */ static int is_user_known(char *user) { struct passwd *pw; if ((pw = getpwnam(user))) memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); endpwent(); return pw != NULL; } struct passwd *auth_userpass(char *user, char *pass, int *known) { struct passwd *pw; pam_handle_t *pamh; pam_userpass_t userpass; struct pam_conv conv = {pam_userpass_conv, &userpass}; pam_item_t item; lo_const char *template; int status; *known = 0; userpass.user = user; userpass.pass = pass; if (pam_start(AUTH_PAM_SERVICE, user, &conv, &pamh) != PAM_SUCCESS) { *known = is_user_known(user); return NULL; } if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) { pam_end(pamh, status); *known = is_user_known(user); return NULL; } if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) { pam_end(pamh, status); *known = is_user_known(user); return NULL; } status = pam_get_item(pamh, PAM_USER, &item); if (status != PAM_SUCCESS) { pam_end(pamh, status); *known = is_user_known(user); return NULL; } template = item; template = strdup(template); if (pam_end(pamh, PAM_SUCCESS) != PAM_SUCCESS || !template) { *known = is_user_known(user); return NULL; } if ((pw = getpwnam(template))) { memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); *known = 1; } endpwent(); return pw; } #endif popa3d-1.0.3/virtual.h0000600000000000000000000000173610402562451013234 0ustar rootroot/* * Virtual domain support. */ #ifndef _POP_VIRTUAL_H #define _POP_VIRTUAL_H #include #include /* * 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, known is set. Returns the template user to run as if * authentication succeeds, or NULL otherwise. */ extern struct passwd *virtual_userpass(char *user, char *pass, int *known); #endif popa3d-1.0.3/misc.c0000600000000000000000000000442110402562450012465 0ustar rootroot/* * Miscellaneous system and library call wrappers. * See misc.h for the descriptions. */ #include #include #include #include #include #include #include #include #include #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; } char *concat(char *s1, ...) { va_list args; char *s, *p, *result; unsigned long l, m, n; m = n = strlen(s1); va_start(args, s1); while ((s = va_arg(args, char *))) { l = strlen(s); if ((m += l) < l) break; } va_end(args); if (s || m >= INT_MAX) return NULL; result = malloc(m + 1); if (!result) return NULL; memcpy(p = result, s1, n); p += n; va_start(args, s1); while ((s = va_arg(args, char *))) { l = strlen(s); if ((n += l) < l || n > m) break; memcpy(p, s, l); p += l; } va_end(args); if (s || m != n || p - result != n) { free(result); return NULL; } *p = 0; return result; } popa3d-1.0.3/Makefile0000600000000000000000000000333310402537506013033 0ustar rootrootCC = gcc LD = $(CC) RM = rm -f MKDIR = mkdir -p INSTALL = install -c CFLAGS = -Wall -O2 -fomit-frame-pointer # You may use OpenSSL's MD5 routines instead of the ones supplied here #CFLAGS += -DHAVE_OPENSSL LDFLAGS = -s LIBS = # Linux with glibc, FreeBSD, NetBSD #LIBS += -lcrypt # HP-UX trusted system #LIBS += -lsec # Solaris (POP_STANDALONE, POP_VIRTUAL) #LIBS += -lsocket -lnsl # PAM #LIBS += -lpam # TCP wrappers #LIBS += -lwrap # libwrap may also want this #LIBS += -lnsl # OpenSSL (-DHAVE_OPENSSL) #LIBS += -lcrypto DESTDIR = PREFIX = /usr/local SBINDIR = $(PREFIX)/sbin MANDIR = $(PREFIX)/man PROJ = popa3d OBJS = \ version.o \ startup.o \ standalone.o \ virtual.o \ auth_passwd.o auth_shadow.o auth_pam.o \ pop_root.o pop_auth.o pop_trans.o \ protocol.o database.o mailbox.o \ misc.o \ md5/md5.o all: $(PROJ) popa3d: $(OBJS) $(LD) $(LDFLAGS) $(OBJS) $(LIBS) -o popa3d auth_pam.o: params.h auth_passwd.o: params.h auth_shadow.o: params.h database.o: params.h database.h mailbox.o: params.h misc.h protocol.h database.h md5/md5.h misc.o: params.h pop_auth.o: params.h misc.h protocol.h pop_auth.h virtual.h pop_root.o: params.h protocol.h pop_auth.h pop_trans.h virtual.h pop_trans.o: params.h protocol.h database.h mailbox.h protocol.o: params.h misc.h protocol.h standalone.o: params.h startup.o: params.h virtual.o: params.h misc.h md5/md5.o: md5/md5.c md5/md5.h $(CC) $(CFLAGS) -c md5/md5.c -o md5/md5.o .c.o: $(CC) $(CFLAGS) -c $*.c install: $(PROJ) $(MKDIR) -m 755 $(DESTDIR)$(SBINDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 700 popa3d $(DESTDIR)$(SBINDIR)/ $(INSTALL) -m 644 popa3d.8 $(DESTDIR)$(MANDIR)/man8/ remove: $(RM) $(DESTDIR)$(SBINDIR)/popa3d $(DESTDIR)$(MANDIR)/man8/popa3d.8 clean: $(RM) $(PROJ) $(OBJS) popa3d-1.0.3/protocol.c0000600000000000000000000001165607625047243013416 0ustar rootroot/* * POP protocol handling. */ #include #include #include #include #include #include #include #include #include "misc.h" #include "params.h" #include "protocol.h" struct pop_buffer pop_buffer; static sigjmp_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 pop_buffer.size <= sizeof(pop_buffer.data) && pop_buffer.ptr <= pop_buffer.size; } static void pop_timeout(int signum) { signal(SIGALRM, SIG_DFL); siglongjmp(pop_timed_out, 1); } static void pop_fetch(void) { int size; signal(SIGALRM, pop_timeout); alarm(POP_TIMEOUT); size = read(0, pop_buffer.data, sizeof(pop_buffer.data)); alarm(0); signal(SIGALRM, SIG_DFL); pop_buffer.ptr = 0; pop_buffer.size = (size >= 0) ? size : 0; } static int pop_get_char(void) { if (pop_buffer.ptr >= pop_buffer.size) { pop_fetch(); if (!pop_buffer.size) 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 (sigsetjmp(pop_timed_out, 1)) return POP_CRASH_NETTIME; 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_NETFAIL; break; case POP_ERROR: if (pop_reply_error()) return POP_CRASH_NETFAIL; case POP_QUIET: break; case POP_LEAVE: if (pop_reply_ok()) return POP_CRASH_NETFAIL; default: return response; } } return POP_CRASH_NETFAIL; } 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))) { /* SUSv2 says: * "Because 0, LONG_MIN and LONG_MAX are returned on error and are also * valid returns on success, an application wishing to check for error * situations should set errno to 0, then call strtol(), then check errno." */ errno = 0; value = strtol(param, &error, 10); if (errno || !*param || *error || value < 0 || (long)(int)value != value) return -1; return (int)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, unsigned long size, int lines) { char *in_buffer, *out_buffer; char *in, *out; int in_block, out_block; int start, body; if (lines >= 0) lines++; if (pop_reply_ok()) return POP_CRASH_NETFAIL; in_buffer = malloc(RETR_BUFFER_SIZE * 3); if (!in_buffer) return POP_CRASH_SERVER; 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 POP_CRASH_SERVER; } 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 POP_CRASH_NETFAIL; } size -= in_block; } free(in_buffer); if (!start) if (pop_reply("%s", "")) return POP_CRASH_NETFAIL; return POP_OK; } int pop_reply_terminate(void) { return pop_reply("."); } popa3d-1.0.3/pop_auth.c0000600000000000000000000000350307537100746013364 0ustar rootroot/* * AUTHORIZATION state handling. */ #include #include #include #include #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_SERVER; 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_SERVER; return POP_STATE; } static struct pop_command pop_auth_commands[] = { {"QUIT", pop_auth_quit}, {"USER", pop_auth_user}, {"PASS", pop_auth_pass}, {NULL, 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 *user) { if (result == AUTH_NONE) { syslog(SYSLOG_PRI_LO, "Didn't attempt authentication"); return; } #if POP_VIRTUAL if (virtual_domain) { syslog(result == AUTH_OK ? SYSLOG_PRI_LO : SYSLOG_PRI_HI, "Authentication %s for %s@%s", result == AUTH_OK ? "passed" : "failed", user ? user : "UNKNOWN USER", virtual_domain); return; } #endif syslog(result == AUTH_OK ? SYSLOG_PRI_LO : SYSLOG_PRI_HI, "Authentication %s for %s", result == AUTH_OK ? "passed" : "failed", user ? user : "UNKNOWN USER"); } popa3d-1.0.3/DESIGN0000600000000000000000000001772210434452503012274 0ustar rootrootThis file describes the design goals and the reasoning behind some of the design decisions for my tiny POP3 daemon, popa3d. Why popa3d? There are lots of different POP3 servers -- with different feature sets, performance, and reliability. However, as far as I know, before I started the work on popa3d, there had been only one with security as one of its primary design goals: qmail-pop3d. Unfortunately, it would only work with qmail, and only with its new maildir format. While both qmail and maildirs do indeed have some advantages, a lot of people continue running other MTAs, and/or use the older mailbox format, for various reasons. Many of them need a POP3 server. The design goals. Well, the goals themselves are obvious; they're probably the same for most other POP3 servers as well. It's their priority that differs. For popa3d, the goals are: 1. Security (to the extent that is possible with POP3 at all, of course). 2. Reliability (again, as limited by the mailbox format and the protocol). 3. RFC compliance (slightly relaxed to work with real-world POP3 clients). 4. Performance (limited by the more important goals, above). Obviously, just like the comments indicate, none of the goals can be met completely, and balanced decisions need to be made. Security. First, it is important that none of the popa3d users get a false sense of security just because it was the primary design goal. The POP3 protocol transmits passwords in plaintext and thus, if you care about the security of your individual user accounts, should only be used either in trusted networks or tunneled over encrypted channels. There exist extensions to the protocol that are supposed to fix this problem. I am not supporting them yet, partly because this isn't going to fully fix the problem. In fact, APOP and the weaker defined SASL mechanisms such as CRAM-MD5 may potentially be even less secure than transmission of plaintext passwords because of the requirement that plaintext equivalents be stored on the server. It is also important to understand that nothing can be perfectly secure. I can make mistakes. While the design of popa3d makes it harder for those to turn into security holes, this is nevertheless still possible. Having that said, let's get to the security-critical design decisions. Privilege management. Initially, popa3d is started as root to handle a connection. However, it does very little work as root: switching to less privileged UIDs, communication with child processes, and authentication information checks (which often involve accessing shadow or master.passwd files). The following privilege switches happen during a successful POP3 session, with /etc/shadow authentication: startup as root | ----------------- |child |parent v v drop to user popa3d, still as root, handle the AUTHORIZATION wait for and state, write the results, - - > read the authentication and exit information | ----------------- |child |parent v v getspnam(3), crypt(3), wait for and check, write the result, - - > read the authentication and exit (to clean up) result | v drop to the authenticated user, handle the TRANSACTION state, possibly UPDATE the mailbox, and exit Trust. No part of popa3d trusts any information obtained from external sources (that is, the data is never assumed to be of the expected format, and is treated as subject to authorization checks). This includes POP3 commands, mailbox contents, and even popa3d's own less-privileged child process for the AUTHORIZATION state handling. DoS attacks. Just like with most other software, there exist ways to cause a Denial of Service, by supplying popa3d with an enormous amount of otherwise valid input. I am aware of the following attacks on popa3d itself: 1. Connection flood. When running in the standalone mode, popa3d does quite a few checks to significantly reduce the impact of such attacks by limiting resource consumption (child processes and logging rate), while still providing full service for other source IP addresses and logging everything that might be important. However, when running from an inetd clone, the handling of these attacks is left up to your inetd and the kernel. 2. Huge mailbox sizes, either in message count or bytes. There are limits in popa3d (see params.h) that are intended to prevent this attack from stopping the entire service. Depending on your disk and other quotas, it may still be possible to stop individual users from getting their mail. Reliability. Quoting Dan Bernstein, "the mbox format ... is inherently unreliable". While popa3d, just like other mail software that deals with mailboxes, doesn't guarantee reliability over system crashes, it still makes sense to talk about its operation on an otherwise stable system. Interaction with other MUAs. Similarly to cucipop (but unlike qpopper), popa3d works on the original mailbox file, without copying. However, unlike cucipop, popa3d is able to ensure that the mailbox doesn't get corrupted if another MUA modifies it during the POP session. Before each mailbox access, popa3d checks its timestamp and, if that has changed, determines if that is due to new mail that has just been delivered, or other changes made to the mailbox. In the latter case, the POP session is silently aborted (which doesn't violate the RFC). popa3d is careful to make sure the timestamp will change if the mailbox is written to, by keeping the lock for up to a second if necessary. Mailbox access. Except for the total size and message count limits mentioned above (and you can disable even those), there are no other artificial limits on the mailbox contents. In particular, there are no line length limits; unlike with qmail-pop3d, lines don't even need to fit in the available memory. NUL bytes are allowed in messages as well. Locking. Because of dropping to the user "completely" (that is, not even keeping a GID of mail like some other POP3 servers do), popa3d only uses fcntl(2) or flock(2) for locking. As a result, it may not be safe over NFS. This is where I choose security over either functionality or reliability. RFC compliance. I tried to make popa3d as strictly RFC 1939 compliant as possible. Most other POP3 servers have extra "features" that violate the RFC. Examples include: wrapping long commands (no matter if they're valid or not) and thus generating multiple -ERR responses (if not even worse: processing something from the middle of the line as a command) to a single command, processing "LIST 4294967297" as "LIST 1" instead of reporting the error, ignoring past a NUL byte till end of line and thus misinterpreting the command. While these are mostly harmless, they can theoretically cause a POP3 client not to detect the unavailability of a protocol extension. There's however one place where popa3d's RFC compliance is deliberately relaxed: popa3d accepts commands terminated by single LFs, even though the RFC says the commands are terminated by a CRLF pair. Performance. Despite the two extra "security" fork(2) calls, popa3d seems to behave fairly efficiently: the efficient mailbox parsing code and the lack of mailbox copying compensate for the extra fork's. Here's some real performance data that I've collected (popa3d running via inetd; larger sites would use the standalone mode instead): 24864 295.50re 16.92cp popa3d* 12749 4578.88re 15.50cp popa3d That is, 12749 POP3 sessions took 32.42 minutes of CPU time (on a 350 MHz Pentium II); of those, more than a half was spent in the temporary child processes. It's not that bad though, as this system was running an (intentionally) expensive crypt(3) that got accounted to the child /etc/shadow authentication processes. Before upgrading to popa3d, the same machine was running qpopper (out of inetd, too): 12025 3169.38re 35.56cp popper It used to take a bit more CPU for less POP3 sessions. -- Solar Designer $Owl: Owl/packages/popa3d/popa3d/DESIGN,v 1.6 2006/05/23 00:20:19 solar Exp $ popa3d-1.0.3/LICENSE0000600000000000000000000000030212014013012012347 0ustar rootrootRedistribution and use in source and binary forms, with or without modification, are permitted. There's ABSOLUTELY NO WARRANTY, express or implied. (This is a heavily cut-down "BSD license".) popa3d-1.0.3/pop_root.c0000600000000000000000000001364010402562450013376 0ustar rootroot/* * 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: * * startup.c if supporting command line options (POP_OPTIONS) * 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) * auth_shadow.c if using shadow (AUTH_SHADOW && !VIRTUAL_ONLY) * auth_pam.c if using PAM (AUTH_PAM || AUTH_PAM_USERPASS) */ #include #include #include #include #include #include #include #include #include #include #include #include #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, int *known); #endif /* POP_USER's pw_uid and pw_gid, other fields may not be valid */ static struct passwd pop_pw; static int known; static char *user; static char *spool, *mailbox; int log_error(char *s) { syslog(SYSLOG_PRI_ERROR, "%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; } static int drop_root(void) { tzset(); openlog(SYSLOG_IDENT, SYSLOG_OPTIONS | LOG_NDELAY, SYSLOG_FACILITY); if (chroot(POP_CHROOT)) return log_error("chroot"); if (chdir("/")) return log_error("chdir"); return set_user(&pop_pw); } /* * 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 *pass; struct passwd *pw; known = 0; #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 NUL termination */ if (read_loop(channel, auth, AUTH_BUFFER_SIZE) < 0) { memset(auth, 0, sizeof(auth)); return AUTH_NONE; } user = auth; pass = &user[strlen(user) + 1]; pw = NULL; #if POP_VIRTUAL if (!(pw = virtual_userpass(user, pass, &known)) && virtual_domain) { memset(pass, 0, strlen(pass)); return AUTH_FAILED; } #endif #if VIRTUAL_ONLY if (!pw) { memset(pass, 0, strlen(pass)); return AUTH_FAILED; } #else if (!pw && !(pw = auth_userpass(user, pass, &known))) { memset(pass, 0, strlen(pass)); return AUTH_FAILED; } #endif if (!*pass) return AUTH_FAILED; memset(pass, 0, strlen(pass)); if (!*user) return AUTH_FAILED; #if VIRTUAL_ONLY if (!virtual_domain) return AUTH_FAILED; #endif if (set_user(pw)) return AUTH_FAILED; #if POP_VIRTUAL if (virtual_domain) { spool = virtual_spool; mailbox = user; return AUTH_OK; } #endif #if VIRTUAL_ONLY /* never reached */ return AUTH_FAILED; #else #ifdef MAIL_SPOOL_PATH spool = MAIL_SPOOL_PATH; mailbox = user; #else spool = pw->pw_dir; mailbox = HOME_MAILBOX_NAME; #endif return AUTH_OK; #endif } 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_PRI_ERROR, "getpwnam(\"" POP_USER "\"): %s", errno ? strerror(errno) : "No such user"); return 1; } memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); endpwent(); pop_pw.pw_uid = pw->pw_uid; pop_pw.pw_gid = pw->pw_gid; #if POP_VIRTUAL if (virtual_startup()) return 1; #endif return 0; } int do_pop_session(void) { int channel[2]; int result, status; /* For SIGCHLD, default action is to ignore the signal. {SIGCHLD, SIG_IGN} * may be invalid (POSIX) or may enable a different behavior (SUSv2), none * of which are any good for us. */ signal(SIGCHLD, SIG_DFL); 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 (drop_root()) 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, user); return do_pop_trans(spool, mailbox); } if (drop_root()) return 1; log_pop_auth(result, known ? user : NULL); #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 && !POP_OPTIONS int main(void) { if (do_pop_startup()) return 1; return do_pop_session(); } #endif popa3d-1.0.3/mailbox.h0000600000000000000000000000107110402562450013170 0ustar rootroot/* * 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 one of the * POP_* event codes. */ 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 popa3d-1.0.3/VIRTUAL0000600000000000000000000000030207446141035012437 0ustar rootrootThe 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.