summaryrefslogtreecommitdiff
path: root/libexec
diff options
context:
space:
mode:
authorCamiel Dobbelaar <camield@cvs.openbsd.org>2001-08-13 19:59:03 +0000
committerCamiel Dobbelaar <camield@cvs.openbsd.org>2001-08-13 19:59:03 +0000
commitc29fb47c597abce79a48b9179039dca232bb9b85 (patch)
treef24b9f4ec5272eeb475f2d8e159619e662b95ad2 /libexec
parente912815d30df3264d2e6df15b9cf9cc8cd144487 (diff)
Solar Designer's popa3d POP3 daemon, version 0.4.9.1
Changes so far: - removed auth_pam.c - removed auth_shadow.c - add BSD makefile - remove md5, in favour of libc md5 - params.h: AUTH_PASSWD and MAIL_SPOOL_PATH
Diffstat (limited to 'libexec')
-rw-r--r--libexec/popa3d/pop_auth.c87
-rw-r--r--libexec/popa3d/pop_auth.h27
-rw-r--r--libexec/popa3d/pop_root.c220
-rw-r--r--libexec/popa3d/pop_trans.c246
-rw-r--r--libexec/popa3d/pop_trans.h10
-rw-r--r--libexec/popa3d/protocol.c274
-rw-r--r--libexec/popa3d/protocol.h103
-rw-r--r--libexec/popa3d/standalone.c209
-rw-r--r--libexec/popa3d/virtual.c183
9 files changed, 1359 insertions, 0 deletions
diff --git a/libexec/popa3d/pop_auth.c b/libexec/popa3d/pop_auth.c
new file mode 100644
index 00000000000..22450da14ac
--- /dev/null
+++ b/libexec/popa3d/pop_auth.c
@@ -0,0 +1,87 @@
+/*
+ * AUTHORIZATION state handling.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "misc.h"
+#include "params.h"
+#include "protocol.h"
+#include "pop_auth.h"
+#if POP_VIRTUAL
+#include "virtual.h"
+#endif
+
+static char *pop_user, *pop_pass;
+
+static int pop_auth_quit(char *params)
+{
+ if (params) return POP_ERROR;
+ return POP_LEAVE;
+}
+
+static int pop_auth_user(char *params)
+{
+ char *user;
+
+ user = pop_get_param(&params);
+ if (!user || pop_user || params) return POP_ERROR;
+ if (!(pop_user = strdup(user))) return POP_CRASH;
+ return POP_OK;
+}
+
+static int pop_auth_pass(char *params)
+{
+ if (!params || !pop_user) return POP_ERROR;
+ if (!(pop_pass = strdup(params))) return POP_CRASH;
+ return POP_STATE;
+}
+
+static struct pop_command pop_auth_commands[] = {
+ {"QUIT", pop_auth_quit},
+ {"USER", pop_auth_user},
+ {"PASS", pop_auth_pass},
+ {NULL}
+};
+
+int do_pop_auth(int channel)
+{
+ pop_init();
+
+ if (pop_reply_ok()) return 1;
+
+ pop_user = NULL;
+ if (pop_handle_state(pop_auth_commands) == POP_STATE) {
+ pop_clean();
+ write_loop(channel, (char *)&pop_buffer, sizeof(pop_buffer));
+ write_loop(channel, pop_user, strlen(pop_user) + 1);
+ write_loop(channel, pop_pass, strlen(pop_pass) + 1);
+ if (close(channel)) return 1;
+ }
+
+ return 0;
+}
+
+void log_pop_auth(int result, char *mailbox)
+{
+ if (result == AUTH_NONE) {
+ syslog(SYSLOG_PRIORITY, "Didn't attempt authentication");
+ return;
+ }
+
+#if POP_VIRTUAL
+ if (virtual_domain) {
+ syslog(SYSLOG_PRIORITY, "Authentication %s for %s@%s",
+ result == AUTH_OK ? "passed" : "failed",
+ mailbox ? mailbox : "UNKNOWN",
+ virtual_domain);
+ return;
+ }
+#endif
+ syslog(SYSLOG_PRIORITY, "Authentication %s for %s",
+ result == AUTH_OK ? "passed" : "failed",
+ mailbox ? mailbox : "UNKNOWN");
+}
diff --git a/libexec/popa3d/pop_auth.h b/libexec/popa3d/pop_auth.h
new file mode 100644
index 00000000000..224fa94bf07
--- /dev/null
+++ b/libexec/popa3d/pop_auth.h
@@ -0,0 +1,27 @@
+/*
+ * AUTHORIZATION state handling.
+ */
+
+#ifndef _POP_AUTH_H
+#define _POP_AUTH_H
+
+/*
+ * Possible authentication results.
+ */
+#define AUTH_OK 0
+#define AUTH_NONE 1
+#define AUTH_FAILED 2
+
+/*
+ * Handles the AUTHORIZATION state commands, and writes authentication
+ * data into the channel.
+ */
+extern int do_pop_auth(int channel);
+
+/*
+ * Logs an authentication attempt for mailbox (or NULL if the requested
+ * mailbox doesn't exist).
+ */
+extern void log_pop_auth(int result, char *mailbox);
+
+#endif
diff --git a/libexec/popa3d/pop_root.c b/libexec/popa3d/pop_root.c
new file mode 100644
index 00000000000..b109e47973d
--- /dev/null
+++ b/libexec/popa3d/pop_root.c
@@ -0,0 +1,220 @@
+/*
+ * Main daemon code: invokes the actual POP handling routines. Most calls
+ * to functions in other source files are done as a non-root user (either
+ * POP_USER or the authenticated user). Depending on compile-time options
+ * in params.h, the following files may contain code executed as root:
+ *
+ * standalone.c if not running via an inetd clone (POP_STANDALONE)
+ * virtual.c if supporting virtual domains (POP_VIRTUAL)
+ * auth_passwd.c if using passwd or *BSD (AUTH_PASSWD && !VIRTUAL_ONLY)
+ * auth_shadow.c if using shadow (AUTH_SHADOW && !VIRTUAL_ONLY)
+ * auth_pam.c if using PAM (AUTH_PAM || AUTH_PAM_USERPASS)
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+
+#include "params.h"
+#include "protocol.h"
+#include "pop_auth.h"
+#include "pop_trans.h"
+#if POP_VIRTUAL
+#include "virtual.h"
+#endif
+
+#if !VIRTUAL_ONLY
+extern struct passwd *auth_userpass(char *user, char *pass, char **mailbox);
+#endif
+
+static struct passwd pop_pw;
+static char *mailbox;
+
+int log_error(char *s)
+{
+ syslog(SYSLOG_PRIORITY, "%s: %m", s);
+ return 1;
+}
+
+static int set_user(struct passwd *pw)
+{
+ gid_t groups[2];
+
+ if (!pw->pw_uid) return 1;
+
+ groups[0] = groups[1] = pw->pw_gid;
+ if (setgroups(1, groups)) return log_error("setgroups");
+ if (setgid(pw->pw_gid)) return log_error("setgid");
+ if (setuid(pw->pw_uid)) return log_error("setuid");
+
+ return 0;
+}
+
+/*
+ * Attempts to read until EOF, and returns the number of bytes read.
+ * We don't expect any signals, so even EINTR is considered an error.
+ */
+static int read_loop(int fd, char *buffer, int count)
+{
+ int offset, block;
+
+ offset = 0;
+ while (count > 0) {
+ block = read(fd, &buffer[offset], count);
+
+ if (block < 0) return block;
+ if (!block) return offset;
+
+ offset += block;
+ count -= block;
+ }
+
+ return offset;
+}
+
+/*
+ * The root-privileged part of the AUTHORIZATION state handling: reads
+ * the authentication data obtained over POP from its end of the pipe,
+ * attempts authentication, and, if successful, drops privilege to the
+ * authenticated user. Returns one of the AUTH_* result codes.
+ */
+static int do_root_auth(int channel)
+{
+ static char auth[AUTH_BUFFER_SIZE + 2];
+ char *user, *pass;
+ struct passwd *pw;
+
+ mailbox = NULL;
+#if POP_VIRTUAL
+ virtual_domain = NULL;
+#endif
+
+/* The POP client could have sent extra commands without waiting for
+ * successful authentication. We're passing them into the TRANSACTION
+ * state if we ever get there. */
+ if (read_loop(channel, (char *)&pop_buffer, sizeof(pop_buffer)) !=
+ sizeof(pop_buffer)) return AUTH_NONE;
+
+/* Now, the authentication data. */
+ memset(auth, 0, sizeof(auth)); /* Ensure the NUL termination */
+ if (read_loop(channel, auth, AUTH_BUFFER_SIZE) < 0) return AUTH_NONE;
+
+ user = auth;
+ pass = &user[strlen(user) + 1];
+
+ pw = NULL;
+#if POP_VIRTUAL
+ if (!(pw = virtual_userpass(user, pass, &mailbox)) && virtual_domain)
+ return AUTH_FAILED;
+#endif
+#if VIRTUAL_ONLY
+ if (!pw)
+ return AUTH_FAILED;
+#else
+ if (!pw && !(pw = auth_userpass(user, pass, &mailbox)))
+ return AUTH_FAILED;
+#endif
+ if (!*user || !*pass) return AUTH_FAILED;
+
+ memset(pass, 0, strlen(pass));
+
+ if (set_user(pw)) return AUTH_FAILED;
+
+ return AUTH_OK;
+}
+
+int do_pop_startup(void)
+{
+ struct passwd *pw;
+
+ umask(077);
+ signal(SIGPIPE, SIG_IGN);
+
+ openlog(SYSLOG_IDENT, SYSLOG_OPTIONS, SYSLOG_FACILITY);
+
+ errno = 0;
+ if (!(pw = getpwnam(POP_USER))) {
+ syslog(SYSLOG_PRIORITY, "getpwnam(\"" POP_USER "\"): %s",
+ errno ? strerror(errno) : "No such user");
+ return 1;
+ }
+ memcpy(&pop_pw, pw, sizeof(pop_pw));
+
+#if POP_VIRTUAL
+ if (virtual_startup()) return 1;
+#endif
+
+ return 0;
+}
+
+int do_pop_session(void)
+{
+ int channel[2];
+ int result, status;
+
+ signal(SIGCHLD, SIG_IGN);
+
+ if (pipe(channel)) return log_error("pipe");
+
+ switch (fork()) {
+ case -1:
+ return log_error("fork");
+
+ case 0:
+ if (close(channel[0])) return log_error("close");
+ if (set_user(&pop_pw)) return 1;
+ return do_pop_auth(channel[1]);
+ }
+
+ if (close(channel[1]))
+ result = AUTH_NONE;
+ else
+ result = do_root_auth(channel[0]);
+
+ if (wait(&status) < 0)
+ status = 1;
+ else
+ if (WIFEXITED(status))
+ status = WEXITSTATUS(status);
+ else
+ status = 1;
+
+ if (result == AUTH_OK) {
+ if (close(channel[0])) return log_error("close");
+ log_pop_auth(result, mailbox);
+#if POP_VIRTUAL
+ if (virtual_domain)
+ return do_pop_trans(virtual_spool, mailbox);
+#endif
+#if !VIRTUAL_ONLY
+ return do_pop_trans(MAIL_SPOOL_PATH, mailbox);
+#endif
+ }
+
+ if (set_user(&pop_pw)) return 1;
+ log_pop_auth(result, mailbox);
+
+#ifdef AUTH_FAILED_MESSAGE
+ if (result == AUTH_FAILED) pop_reply("-ERR %s", AUTH_FAILED_MESSAGE);
+#else
+ if (result == AUTH_FAILED) pop_reply_error();
+#endif
+
+ return status;
+}
+
+#if !POP_STANDALONE
+int main(void)
+{
+ if (do_pop_startup()) return 1;
+ return do_pop_session();
+}
+#endif
diff --git a/libexec/popa3d/pop_trans.c b/libexec/popa3d/pop_trans.c
new file mode 100644
index 00000000000..fe61b98a757
--- /dev/null
+++ b/libexec/popa3d/pop_trans.c
@@ -0,0 +1,246 @@
+/*
+ * TRANSACTION state handling.
+ */
+
+#include <stdio.h>
+#include <syslog.h>
+
+#include "params.h"
+#include "protocol.h"
+#include "database.h"
+#include "mailbox.h"
+
+static int pop_trans_quit(char *params)
+{
+ if (params) return POP_ERROR;
+ return POP_STATE;
+}
+
+static int pop_trans_noop(char *params)
+{
+ if (params) return POP_ERROR;
+ return POP_OK;
+}
+
+static int pop_trans_stat(char *params)
+{
+ if (params) return POP_ERROR;
+ if (pop_reply("+OK %d %ld", db.visible_count, db.visible_size))
+ return POP_CRASH;
+ return POP_QUIET;
+}
+
+static int pop_trans_list_or_uidl(char *params, int uidl)
+{
+ int number;
+ struct db_message *msg;
+
+ if (params) {
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count || params)
+ return POP_ERROR;
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) return POP_ERROR;
+ if (uidl) {
+ if (pop_reply("+OK %d "
+ "%02x%02x%02x%02x%02x%02x%02x%02x",
+ number,
+ msg->hash[3], msg->hash[2],
+ msg->hash[1], msg->hash[0],
+ msg->hash[7], msg->hash[6],
+ msg->hash[5], msg->hash[4]))
+ return POP_CRASH;
+ } else
+ if (pop_reply("+OK %d %ld", number, msg->size))
+ return POP_CRASH;
+ return POP_QUIET;
+ }
+
+ if (pop_reply_ok()) return POP_CRASH;
+ for (number = 1; number <= db.total_count; number++) {
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) continue;
+ if (uidl) {
+ if (pop_reply("%d "
+ "%02x%02x%02x%02x%02x%02x%02x%02x",
+ number,
+ msg->hash[3], msg->hash[2],
+ msg->hash[1], msg->hash[0],
+ msg->hash[7], msg->hash[6],
+ msg->hash[5], msg->hash[4]))
+ return POP_CRASH;
+ } else
+ if (pop_reply("%d %ld", number, msg->size))
+ return POP_CRASH;
+ }
+ if (pop_reply_terminate()) return POP_CRASH;
+
+ return POP_QUIET;
+}
+
+static int pop_trans_list(char *params)
+{
+ return pop_trans_list_or_uidl(params, 0);
+}
+
+static int pop_trans_uidl(char *params)
+{
+ return pop_trans_list_or_uidl(params, 1);
+}
+
+static int pop_trans_retr(char *params)
+{
+ int number;
+ struct db_message *msg;
+
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count || params) return POP_ERROR;
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) return POP_ERROR;
+ if (mailbox_get(msg, -1)) return POP_CRASH;
+#if POP_SUPPORT_LAST
+ if (number > db.last) db.last = number;
+#endif
+ return POP_QUIET;
+}
+
+static int pop_trans_top(char *params)
+{
+ int number, lines;
+ struct db_message *msg;
+
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count) return POP_ERROR;
+ lines = pop_get_int(&params);
+ if (lines < 0 || params) return POP_ERROR;
+ msg = db.array[number - 1];
+ if (msg->flags & MSG_DELETED) return POP_ERROR;
+ if (mailbox_get(msg, lines)) return POP_CRASH;
+ return POP_QUIET;
+}
+
+static int pop_trans_dele(char *params)
+{
+ int number;
+ struct db_message *msg;
+
+ number = pop_get_int(&params);
+ if (number < 1 || number > db.total_count || params) return POP_ERROR;
+ msg = db.array[number - 1];
+ if (db_delete(msg)) return POP_ERROR;
+#if POP_SUPPORT_LAST
+ if (number > db.last) db.last = number;
+#endif
+ return POP_OK;
+}
+
+static int pop_trans_rset(char *params)
+{
+ struct db_message *msg;
+
+ if (params) return POP_ERROR;
+
+ if ((msg = db.head))
+ do {
+ msg->flags &= ~MSG_DELETED;
+ } while ((msg = msg->next));
+
+ db.visible_count = db.total_count;
+ db.visible_size = db.total_size;
+ db.flags &= ~DB_DIRTY;
+#if POP_SUPPORT_LAST
+ db.last = 0;
+#endif
+
+ return POP_OK;
+}
+
+#if POP_SUPPORT_LAST
+static int pop_trans_last(char *params)
+{
+ if (params) return POP_ERROR;
+ if (pop_reply("+OK %d", db.last)) return POP_CRASH;
+ return POP_QUIET;
+}
+#endif
+
+static struct pop_command pop_trans_commands[] = {
+ {"QUIT", pop_trans_quit},
+ {"NOOP", pop_trans_noop},
+ {"STAT", pop_trans_stat},
+ {"LIST", pop_trans_list},
+ {"UIDL", pop_trans_uidl},
+ {"RETR", pop_trans_retr},
+ {"TOP", pop_trans_top},
+ {"DELE", pop_trans_dele},
+ {"RSET", pop_trans_rset},
+#if POP_SUPPORT_LAST
+ {"LAST", pop_trans_last},
+#endif
+ {NULL}
+};
+
+static int db_load(char *spool, char *mailbox)
+{
+ db_init();
+
+ if (mailbox_open(spool, mailbox)) return 1;
+
+ if (db_fix()) {
+ mailbox_close();
+ return 1;
+ }
+
+ return 0;
+}
+
+int do_pop_trans(char *spool, char *mailbox)
+{
+ int result;
+
+ if (!pop_sane()) return 1;
+
+ if (db_load(spool, mailbox)) {
+ syslog(SYSLOG_PRIORITY, "Failed to open mailbox");
+ pop_reply_error();
+ return 0;
+ }
+
+ syslog(SYSLOG_PRIORITY, "%d message%s (%ld byte%s) loaded",
+ db.total_count, db.total_count == 1 ? "" : "s",
+ db.total_size, db.total_size == 1 ? "" : "s");
+
+ if (pop_reply_ok())
+ result = POP_CRASH;
+ else
+ switch ((result = pop_handle_state(pop_trans_commands))) {
+ case POP_STATE:
+ if (mailbox_update()) {
+ if (db.flags & DB_STALE) break;
+ syslog(SYSLOG_PRIORITY, "Failed to update mailbox");
+ pop_reply_error();
+ break;
+ }
+
+ syslog(SYSLOG_PRIORITY, "%d (%ld) deleted, %d (%ld) left",
+ db.total_count - db.visible_count,
+ db.total_size - db.visible_size,
+ db.visible_count,
+ db.visible_size);
+ pop_reply_ok();
+ break;
+
+ case POP_TIMED_OUT:
+ syslog(SYSLOG_PRIORITY, "Connection timed out");
+ }
+
+ if (db.flags & DB_STALE)
+ syslog(SYSLOG_PRIORITY, "Another MUA active, giving up");
+ else
+ if (result == POP_CRASH)
+ syslog(SYSLOG_PRIORITY, "Session crashed");
+
+ mailbox_close();
+
+ return 0;
+}
diff --git a/libexec/popa3d/pop_trans.h b/libexec/popa3d/pop_trans.h
new file mode 100644
index 00000000000..315b8cb2f4c
--- /dev/null
+++ b/libexec/popa3d/pop_trans.h
@@ -0,0 +1,10 @@
+/*
+ * TRANSACTION state handling.
+ */
+
+#ifndef _POP_TRANS_H
+#define _POP_TRANS_H
+
+extern int do_pop_trans(char *spool, char *mailbox);
+
+#endif
diff --git a/libexec/popa3d/protocol.c b/libexec/popa3d/protocol.c
new file mode 100644
index 00000000000..20382dc31ca
--- /dev/null
+++ b/libexec/popa3d/protocol.c
@@ -0,0 +1,274 @@
+/*
+ * POP protocol handling.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "misc.h"
+#include "params.h"
+#include "protocol.h"
+
+struct pop_buffer pop_buffer;
+static jmp_buf pop_timed_out;
+
+void pop_init(void)
+{
+ pop_buffer.ptr = pop_buffer.size = 0;
+}
+
+void pop_clean(void)
+{
+ memset(pop_buffer.data, 0, pop_buffer.ptr);
+ memmove(pop_buffer.data, &pop_buffer.data[pop_buffer.ptr],
+ pop_buffer.size -= pop_buffer.ptr);
+ pop_buffer.ptr = 0;
+}
+
+int pop_sane(void)
+{
+ return (unsigned int)pop_buffer.size <= sizeof(pop_buffer.data) &&
+ (unsigned int)pop_buffer.ptr <= (unsigned int)pop_buffer.size;
+}
+
+static void pop_timeout(int signum)
+{
+ signal(SIGALRM, SIG_DFL);
+ longjmp(pop_timed_out, 1);
+}
+
+static int pop_fetch(void)
+{
+ signal(SIGALRM, pop_timeout);
+ alarm(POP_TIMEOUT);
+
+ pop_buffer.size = read(0, pop_buffer.data, sizeof(pop_buffer.data));
+
+ alarm(0);
+ signal(SIGALRM, SIG_DFL);
+
+ pop_buffer.ptr = 0;
+ return pop_buffer.size <= 0;
+}
+
+static int pop_get_char(void)
+{
+ if (pop_buffer.ptr >= pop_buffer.size)
+ if (pop_fetch()) return -1;
+
+ return (unsigned char)pop_buffer.data[pop_buffer.ptr++];
+}
+
+static char *pop_get_line(char *line, int size)
+{
+ int pos;
+ int seen_cr, seen_nul;
+ int c;
+
+ pos = 0;
+ seen_cr = seen_nul = 0;
+ while ((c = pop_get_char()) >= 0) {
+ if (c == '\n') {
+ if (seen_cr) line[pos - 1] = 0;
+ break;
+ }
+ if (pos < size - 1)
+ seen_cr = ((line[pos++] = c) == '\r');
+ else
+ seen_cr = 0;
+ seen_nul |= !c;
+ }
+ line[pos] = 0;
+
+ if (seen_nul)
+ line[0] = 0;
+
+ if (pos || c >= 0)
+ return line;
+ else
+ return NULL;
+}
+
+int pop_handle_state(struct pop_command *commands)
+{
+ char line[POP_BUFFER_SIZE];
+ char *params;
+ struct pop_command *command;
+ int response;
+
+ if (setjmp(pop_timed_out)) return POP_TIMED_OUT;
+
+ while (pop_get_line(line, sizeof(line))) {
+ if ((params = strchr(line, ' '))) {
+ *params++ = 0;
+ if (!*params) params = NULL;
+ }
+
+ response = POP_ERROR;
+ for (command = commands; command->name; command++)
+ if (!strcasecmp(command->name, line)) {
+ response = command->handler(params);
+ break;
+ }
+
+ switch (response) {
+ case POP_OK:
+ if (pop_reply_ok()) return POP_CRASH;
+ break;
+
+ case POP_ERROR:
+ if (pop_reply_error()) return POP_CRASH;
+
+ case POP_QUIET:
+ break;
+
+ case POP_LEAVE:
+ if (pop_reply_ok()) return POP_CRASH;
+
+ default:
+ return response;
+ }
+ }
+
+ return POP_CRASH;
+}
+
+char *pop_get_param(char **params)
+{
+ char *current, *next;
+
+ if ((current = *params)) {
+ if ((next = strchr(current, ' '))) {
+ *next++ = 0;
+ *params = *next ? next : NULL;
+ } else
+ *params = NULL;
+
+ if (strlen(current) > 40) current = NULL;
+ }
+
+ return current;
+}
+
+int pop_get_int(char **params)
+{
+ char *param, *error;
+ long value;
+
+ if ((param = pop_get_param(params))) {
+ value = strtol(param, &error, 10);
+ if (!*param || *error || (value & ~0x3FFFFFFFL)) return -1;
+
+ return value;
+ }
+
+ return -1;
+}
+
+int pop_reply(char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+
+ putc('\r', stdout);
+ putc('\n', stdout);
+
+ switch (format[0]) {
+ case '+':
+ case '-':
+ return fflush(stdout);
+
+ case '.':
+ if (!format[1]) return fflush(stdout);
+ }
+
+ return ferror(stdout);
+}
+
+int pop_reply_ok(void)
+{
+ return pop_reply("+OK");
+}
+
+int pop_reply_error(void)
+{
+ return pop_reply("-ERR");
+}
+
+int pop_reply_multiline(int fd, long size, int lines)
+{
+ char *in_buffer;
+ char *out_buffer;
+ char *in, *out;
+ int in_block, out_block;
+ int start, body;
+
+ if (lines >= 0) lines++;
+
+ if (pop_reply_ok()) return 1;
+
+ in_buffer = malloc(RETR_BUFFER_SIZE * 3);
+ if (!in_buffer) return 1;
+ out_buffer = &in_buffer[RETR_BUFFER_SIZE];
+
+ start = 1;
+ body = 0;
+ while (size && lines) {
+ if (size > RETR_BUFFER_SIZE)
+ in_block = read(fd, in_buffer, RETR_BUFFER_SIZE);
+ else
+ in_block = read(fd, in_buffer, size);
+ if (in_block <= 0) {
+ free(in_buffer);
+ return 1;
+ }
+
+ in = in_buffer;
+ out = out_buffer;
+ while (in < &in_buffer[in_block] && lines)
+ switch (*in) {
+ case '\n':
+ *out++ = '\r';
+ *out++ = *in++;
+ if (start) body = 1;
+ if (body) lines--;
+ start = 1;
+ break;
+
+ case '.':
+ if (start) *out++ = '.';
+
+ default:
+ *out++ = *in++;
+ start = 0;
+ }
+
+ out_block = out - out_buffer;
+ if (write_loop(1, out_buffer, out_block) != out_block) {
+ free(in_buffer);
+ return 1;
+ }
+
+ size -= in_block;
+ }
+
+ free(in_buffer);
+
+ if (!start)
+ if (pop_reply("%s", "")) return 1;
+
+ return 0;
+}
+
+int pop_reply_terminate(void)
+{
+ return pop_reply(".");
+}
diff --git a/libexec/popa3d/protocol.h b/libexec/popa3d/protocol.h
new file mode 100644
index 00000000000..f429d9de723
--- /dev/null
+++ b/libexec/popa3d/protocol.h
@@ -0,0 +1,103 @@
+/*
+ * POP protocol handling.
+ */
+
+#ifndef _POP_PROTOCOL_H
+#define _POP_PROTOCOL_H
+
+/*
+ * Responses and events, to be returned by command and state handlers.
+ */
+#define POP_OK 0 /* Reply with "+OK" */
+#define POP_ERROR 1 /* Reply with "-ERR" */
+#define POP_QUIET 2 /* We've already replied */
+#define POP_LEAVE 3 /* Leave the session */
+#define POP_STATE 4 /* Advance the state */
+#define POP_CRASH 5 /* Session crashed */
+#define POP_TIMED_OUT 6 /* Connection timed out */
+
+/*
+ * POP command description.
+ */
+struct pop_command {
+ char *name;
+ int (*handler)(char *params);
+};
+
+/*
+ * Internal POP command buffer.
+ */
+struct pop_buffer {
+ int ptr, size;
+ char data[POP_BUFFER_SIZE];
+};
+
+extern struct pop_buffer pop_buffer;
+
+/*
+ * Initializes the buffer.
+ */
+extern void pop_init(void);
+
+/*
+ * Zeroes out the part of the buffer that has already been processed.
+ */
+extern void pop_clean(void);
+
+/*
+ * Checks if the buffer is sane.
+ */
+extern int pop_sane(void);
+
+/*
+ * Handles a POP protocol state (AUTHORIZATION or TRANSACTION, as defined
+ * in RFC 1939), processing the supplied commands. Returns when the state
+ * is changed.
+ */
+extern int pop_handle_state(struct pop_command *commands);
+
+/*
+ * Returns the next parameter, or NULL if there's none or it is too long
+ * to be valid (as defined in the RFC).
+ */
+extern char *pop_get_param(char **params);
+
+/*
+ * Returns the next parameter as a non-negative number, or -1 if there's
+ * none or the syntax is invalid.
+ */
+extern int pop_get_int(char **params);
+
+/*
+ * Produces a generic POP response. Returns a non-zero value on error;
+ * the POP session then has to crash.
+ */
+extern int pop_reply(char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 1, 2)));
+#else
+ ;
+#endif
+
+/*
+ * The two simple POP responses. Return a non-zero value on error; the
+ * POP session then has to crash.
+ */
+extern int pop_reply_ok(void);
+extern int pop_reply_error(void);
+
+/*
+ * Produces a multi-line POP response, reading the data from the supplied
+ * file descriptor for up to the requested size or number of lines of the
+ * message body, if that number is non-negative. Returns a non-zero value
+ * on error; the POP session then has to crash.
+ */
+extern int pop_reply_multiline(int fd, long size, int lines);
+
+/*
+ * Terminates a multi-line POP response. Returns a non-zero value on error;
+ * the POP session then has to crash.
+ */
+extern int pop_reply_terminate(void);
+
+#endif
diff --git a/libexec/popa3d/standalone.c b/libexec/popa3d/standalone.c
new file mode 100644
index 00000000000..400bdc6e793
--- /dev/null
+++ b/libexec/popa3d/standalone.c
@@ -0,0 +1,209 @@
+/*
+ * Standalone POP server: accepts connections, checks the anti-flood limits,
+ * logs and starts the actual POP sessions.
+ */
+
+#include "params.h"
+
+#if POP_STANDALONE
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/*
+ * These are defined in pop_root.c.
+ */
+extern int log_error(char *s);
+extern int do_pop_startup(void);
+extern int do_pop_session(void);
+
+/*
+ * Active POP sessions. Those that were started within the last MIN_DELAY
+ * seconds are also considered active (regardless of their actual state),
+ * to allow for limiting the logging rate without throwing away critical
+ * information about sessions that we could have allowed to proceed.
+ */
+static struct {
+ struct in_addr addr; /* Source IP address */
+ int pid; /* PID of the server, or 0 for none */
+ clock_t start; /* When the server was started */
+ clock_t log; /* When we've last logged a failure */
+} sessions[MAX_SESSIONS];
+
+static volatile int child_blocked; /* We use blocking to avoid races */
+static volatile int child_pending; /* Are any dead children waiting? */
+
+/*
+ * SIGCHLD handler; can also be called directly with a zero signum.
+ */
+static void handle_child(int signum)
+{
+ int saved_errno;
+ int pid;
+ int i;
+
+ saved_errno = errno;
+
+ if (child_blocked)
+ child_pending = 1;
+ else {
+ child_pending = 0;
+
+ while ((pid = waitpid(0, NULL, WNOHANG)) > 0)
+ for (i = 0; i < MAX_SESSIONS; i++)
+ if (sessions[i].pid == pid) {
+ sessions[i].pid = 0;
+ break;
+ }
+ }
+
+ if (signum) signal(SIGCHLD, handle_child);
+
+ errno = saved_errno;
+}
+
+int main(void)
+{
+ int true = 1;
+ int sock, new;
+ struct sockaddr_in addr;
+ int addrlen;
+ int pid;
+ struct tms buf;
+ clock_t now;
+ int i, j, n;
+
+ if (do_pop_startup()) return 1;
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
+ return log_error("socket");
+
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (void *)&true, sizeof(true)))
+ return log_error("setsockopt");
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(DAEMON_ADDR);
+ addr.sin_port = htons(DAEMON_PORT);
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)))
+ return log_error("bind");
+
+ if (listen(sock, MAX_BACKLOG))
+ return log_error("listen");
+
+ chdir("/");
+ setsid();
+
+ switch (fork()) {
+ case -1:
+ return log_error("fork");
+
+ case 0:
+ break;
+
+ default:
+ return 0;
+ }
+
+ setsid();
+
+ child_blocked = 1;
+ child_pending = 0;
+ signal(SIGCHLD, handle_child);
+
+ memset((void *)sessions, 0, sizeof(sessions));
+ new = 0;
+
+ while (1) {
+ child_blocked = 0;
+ if (child_pending) handle_child(0);
+
+ if (new > 0)
+ if (close(new)) return log_error("close");
+
+ addrlen = sizeof(addr);
+ new = accept(sock, (struct sockaddr *)&addr, &addrlen);
+
+/*
+ * I wish there was a portable way to classify errno's... In this case,
+ * it appears to be better to risk eating up the CPU on a fatal error
+ * rather than risk terminating the entire service because of a minor
+ * temporary error having to do with one particular connection attempt.
+ */
+ if (new < 0) continue;
+
+ now = times(&buf);
+
+ child_blocked = 1;
+
+ j = -1; n = 0;
+ for (i = 0; i < MAX_SESSIONS; i++) {
+ if (sessions[i].start > now)
+ sessions[i].start = 0;
+ if (sessions[i].pid ||
+ (sessions[i].start &&
+ now - sessions[i].start < MIN_DELAY * CLK_TCK)) {
+ if (sessions[i].addr.s_addr ==
+ addr.sin_addr.s_addr)
+ if (++n >= MAX_SESSIONS_PER_SOURCE) break;
+ } else
+ if (j < 0) j = i;
+ }
+
+ if (n >= MAX_SESSIONS_PER_SOURCE) {
+ if (!sessions[i].log ||
+ now < sessions[i].log ||
+ now - sessions[i].log >= MIN_DELAY * CLK_TCK) {
+ syslog(SYSLOG_PRIORITY,
+ "%s: per source limit reached",
+ inet_ntoa(addr.sin_addr));
+ sessions[i].log = now;
+ }
+ continue;
+ }
+
+ if (j < 0) {
+ syslog(SYSLOG_PRIORITY, "%s: sessions limit reached",
+ inet_ntoa(addr.sin_addr));
+ continue;
+ }
+
+ switch ((pid = fork())) {
+ case -1:
+ syslog(SYSLOG_PRIORITY, "%s: fork: %m",
+ inet_ntoa(addr.sin_addr));
+ break;
+
+ case 0:
+ syslog(SYSLOG_PRIORITY, "Session from %s",
+ inet_ntoa(addr.sin_addr));
+ if (close(sock)) return log_error("close");
+ if (dup2(new, 0) < 0) return log_error("dup2");
+ if (dup2(new, 1) < 0) return log_error("dup2");
+ if (dup2(new, 2) < 0) return log_error("dup2");
+ if (close(new)) return log_error("close");
+ return do_pop_session();
+
+ default:
+ sessions[j].addr = addr.sin_addr;
+ (volatile int)sessions[j].pid = pid;
+ sessions[j].start = now;
+ sessions[j].log = 0;
+ }
+ }
+}
+
+#endif
diff --git a/libexec/popa3d/virtual.c b/libexec/popa3d/virtual.c
new file mode 100644
index 00000000000..9b87ebb9660
--- /dev/null
+++ b/libexec/popa3d/virtual.c
@@ -0,0 +1,183 @@
+/*
+ * Virtual domain support.
+ */
+
+#include "params.h"
+
+#if POP_VIRTUAL
+
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 4
+#define _XPG4_2
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+extern int log_error(char *s);
+
+char *virtual_domain;
+char *virtual_spool;
+
+int virtual_startup(void)
+{
+ return 0;
+}
+
+static char *lookup(void)
+{
+ struct sockaddr_in sin;
+ int length;
+
+ length = sizeof(sin);
+ if (getsockname(0, (struct sockaddr *)&sin, &length)) {
+ if (errno == ENOTSOCK) return "";
+ log_error("getsockname");
+ return NULL;
+ }
+ if (length != sizeof(sin) || sin.sin_family != AF_INET) return NULL;
+
+ return inet_ntoa(sin.sin_addr);
+}
+
+static int isvaliduser(char *user)
+{
+ unsigned char *p;
+
+/* This is pretty liberal, but we're going to use direct syscalls only,
+ * and they have to accept all the printable characters */
+ for (p = (unsigned char *)user; *p; p++)
+ if (*p < ' ' || *p > 0x7E || *p == '.' || *p == '/') return 0;
+
+ if (p - (unsigned char *)user > NAME_MAX) return 0;
+
+ return 1;
+}
+
+struct passwd *virtual_userpass(char *user, char *pass, char **mailbox)
+{
+ struct passwd *pw, *result;
+ struct stat stat;
+ char auth[VIRTUAL_AUTH_SIZE];
+ char *address, *pathname;
+ char *template, *passwd;
+ int fail;
+ int fd, size;
+
+/* Make sure we don't try to authenticate globally if something fails
+ * before we find out whether the virtual domain is known to us */
+ virtual_domain = "UNKNOWN";
+ virtual_spool = NULL;
+
+ if (!(address = lookup())) return NULL;
+
+/* Authenticate globally (if supported) if run on a non-socket */
+ if (!*address) {
+ virtual_domain = NULL;
+ return NULL;
+ }
+
+ fail = 0;
+ if (!isvaliduser(user)) {
+ user = "INVALID";
+ fail = 1;
+ }
+
+/* This "can't happen", but is just too critical to not check explicitly */
+ if (strchr(address, '/') || strchr(user, '/'))
+ return NULL;
+
+ pathname = malloc(strlen(VIRTUAL_HOME_PATH) + strlen(address) +
+ strlen(VIRTUAL_AUTH_PATH) + strlen(user) + 4);
+ if (!pathname) return NULL;
+ sprintf(pathname, "%s/%s", VIRTUAL_HOME_PATH, address);
+
+ if (lstat(pathname, &stat)) {
+ if (errno == ENOENT)
+ virtual_domain = NULL;
+ else
+ log_error("lstat");
+ free(pathname);
+ return NULL;
+ }
+
+ if (!(address = strdup(address))) return NULL;
+ virtual_domain = address;
+
+ sprintf(pathname, "%s/%s/%s/%s", VIRTUAL_HOME_PATH, address,
+ VIRTUAL_AUTH_PATH, user);
+
+ if ((fd = open(pathname, O_RDONLY)) < 0 && errno != ENOENT) {
+ log_error("open");
+ fail = 1;
+ }
+
+ free(pathname);
+
+ virtual_spool = malloc(strlen(VIRTUAL_HOME_PATH) +
+ strlen(virtual_domain) +
+ strlen(VIRTUAL_SPOOL_PATH) + 3);
+ if (!virtual_spool) {
+ close(fd);
+ return NULL;
+ }
+ sprintf(virtual_spool, "%s/%s/%s", VIRTUAL_HOME_PATH, virtual_domain,
+ VIRTUAL_SPOOL_PATH);
+
+ size = 0;
+ if (fd >= 0) {
+ if (!fail) *mailbox = user;
+
+ if ((size = read(fd, auth, sizeof(auth))) < 0) {
+ log_error("read");
+ size = 0;
+ fail = 1;
+ }
+
+ close(fd);
+ }
+
+ if (size >= sizeof(auth)) {
+ size = 0;
+ fail = 1;
+ }
+
+ auth[size] = 0;
+
+ if (!(template = strtok(auth, ":")) || !*template) {
+ template = "INVALID";
+ fail = 1;
+ }
+ if (!(passwd = strtok(NULL, ":")) || !*passwd ||
+ *passwd == '*' || *passwd == '!') {
+ passwd = AUTH_DUMMY_SALT;
+ fail = 1;
+ }
+ if (!strtok(NULL, ":")) fail = 1;
+
+ if ((pw = getpwnam(template))) {
+ memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+ pw->pw_passwd = "*";
+ }
+ endpwent();
+
+ result = NULL;
+ if (!strcmp(crypt(pass, passwd), passwd) && !fail)
+ result = pw;
+
+ memset(auth, 0, sizeof(auth));
+
+ return result;
+}
+
+#endif