diff options
author | Camiel Dobbelaar <camield@cvs.openbsd.org> | 2001-08-13 19:59:03 +0000 |
---|---|---|
committer | Camiel Dobbelaar <camield@cvs.openbsd.org> | 2001-08-13 19:59:03 +0000 |
commit | c29fb47c597abce79a48b9179039dca232bb9b85 (patch) | |
tree | f24b9f4ec5272eeb475f2d8e159619e662b95ad2 | |
parent | e912815d30df3264d2e6df15b9cf9cc8cd144487 (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
-rw-r--r-- | libexec/popa3d/pop_auth.c | 87 | ||||
-rw-r--r-- | libexec/popa3d/pop_auth.h | 27 | ||||
-rw-r--r-- | libexec/popa3d/pop_root.c | 220 | ||||
-rw-r--r-- | libexec/popa3d/pop_trans.c | 246 | ||||
-rw-r--r-- | libexec/popa3d/pop_trans.h | 10 | ||||
-rw-r--r-- | libexec/popa3d/protocol.c | 274 | ||||
-rw-r--r-- | libexec/popa3d/protocol.h | 103 | ||||
-rw-r--r-- | libexec/popa3d/standalone.c | 209 | ||||
-rw-r--r-- | libexec/popa3d/virtual.c | 183 |
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(¶ms); + if (!user || pop_user || params) return POP_ERROR; + if (!(pop_user = strdup(user))) return POP_CRASH; + return POP_OK; +} + +static int pop_auth_pass(char *params) +{ + if (!params || !pop_user) return POP_ERROR; + if (!(pop_pass = strdup(params))) return POP_CRASH; + return POP_STATE; +} + +static struct pop_command pop_auth_commands[] = { + {"QUIT", pop_auth_quit}, + {"USER", pop_auth_user}, + {"PASS", pop_auth_pass}, + {NULL} +}; + +int do_pop_auth(int channel) +{ + pop_init(); + + if (pop_reply_ok()) return 1; + + pop_user = NULL; + if (pop_handle_state(pop_auth_commands) == POP_STATE) { + pop_clean(); + write_loop(channel, (char *)&pop_buffer, sizeof(pop_buffer)); + write_loop(channel, pop_user, strlen(pop_user) + 1); + write_loop(channel, pop_pass, strlen(pop_pass) + 1); + if (close(channel)) return 1; + } + + return 0; +} + +void log_pop_auth(int result, char *mailbox) +{ + if (result == AUTH_NONE) { + syslog(SYSLOG_PRIORITY, "Didn't attempt authentication"); + return; + } + +#if POP_VIRTUAL + if (virtual_domain) { + syslog(SYSLOG_PRIORITY, "Authentication %s for %s@%s", + result == AUTH_OK ? "passed" : "failed", + mailbox ? mailbox : "UNKNOWN", + virtual_domain); + return; + } +#endif + syslog(SYSLOG_PRIORITY, "Authentication %s for %s", + result == AUTH_OK ? "passed" : "failed", + mailbox ? mailbox : "UNKNOWN"); +} diff --git a/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(¶ms); + if (number < 1 || number > db.total_count || params) + return POP_ERROR; + msg = db.array[number - 1]; + if (msg->flags & MSG_DELETED) return POP_ERROR; + if (uidl) { + if (pop_reply("+OK %d " + "%02x%02x%02x%02x%02x%02x%02x%02x", + number, + msg->hash[3], msg->hash[2], + msg->hash[1], msg->hash[0], + msg->hash[7], msg->hash[6], + msg->hash[5], msg->hash[4])) + return POP_CRASH; + } else + if (pop_reply("+OK %d %ld", number, msg->size)) + return POP_CRASH; + return POP_QUIET; + } + + if (pop_reply_ok()) return POP_CRASH; + for (number = 1; number <= db.total_count; number++) { + msg = db.array[number - 1]; + if (msg->flags & MSG_DELETED) continue; + if (uidl) { + if (pop_reply("%d " + "%02x%02x%02x%02x%02x%02x%02x%02x", + number, + msg->hash[3], msg->hash[2], + msg->hash[1], msg->hash[0], + msg->hash[7], msg->hash[6], + msg->hash[5], msg->hash[4])) + return POP_CRASH; + } else + if (pop_reply("%d %ld", number, msg->size)) + return POP_CRASH; + } + if (pop_reply_terminate()) return POP_CRASH; + + return POP_QUIET; +} + +static int pop_trans_list(char *params) +{ + return pop_trans_list_or_uidl(params, 0); +} + +static int pop_trans_uidl(char *params) +{ + return pop_trans_list_or_uidl(params, 1); +} + +static int pop_trans_retr(char *params) +{ + int number; + struct db_message *msg; + + number = pop_get_int(¶ms); + if (number < 1 || number > db.total_count || params) return POP_ERROR; + msg = db.array[number - 1]; + if (msg->flags & MSG_DELETED) return POP_ERROR; + if (mailbox_get(msg, -1)) return POP_CRASH; +#if POP_SUPPORT_LAST + if (number > db.last) db.last = number; +#endif + return POP_QUIET; +} + +static int pop_trans_top(char *params) +{ + int number, lines; + struct db_message *msg; + + number = pop_get_int(¶ms); + if (number < 1 || number > db.total_count) return POP_ERROR; + lines = pop_get_int(¶ms); + if (lines < 0 || params) return POP_ERROR; + msg = db.array[number - 1]; + if (msg->flags & MSG_DELETED) return POP_ERROR; + if (mailbox_get(msg, lines)) return POP_CRASH; + return POP_QUIET; +} + +static int pop_trans_dele(char *params) +{ + int number; + struct db_message *msg; + + number = pop_get_int(¶ms); + if (number < 1 || number > db.total_count || params) return POP_ERROR; + msg = db.array[number - 1]; + if (db_delete(msg)) return POP_ERROR; +#if POP_SUPPORT_LAST + if (number > db.last) db.last = number; +#endif + return POP_OK; +} + +static int pop_trans_rset(char *params) +{ + struct db_message *msg; + + if (params) return POP_ERROR; + + if ((msg = db.head)) + do { + msg->flags &= ~MSG_DELETED; + } while ((msg = msg->next)); + + db.visible_count = db.total_count; + db.visible_size = db.total_size; + db.flags &= ~DB_DIRTY; +#if POP_SUPPORT_LAST + db.last = 0; +#endif + + return POP_OK; +} + +#if POP_SUPPORT_LAST +static int pop_trans_last(char *params) +{ + if (params) return POP_ERROR; + if (pop_reply("+OK %d", db.last)) return POP_CRASH; + return POP_QUIET; +} +#endif + +static struct pop_command pop_trans_commands[] = { + {"QUIT", pop_trans_quit}, + {"NOOP", pop_trans_noop}, + {"STAT", pop_trans_stat}, + {"LIST", pop_trans_list}, + {"UIDL", pop_trans_uidl}, + {"RETR", pop_trans_retr}, + {"TOP", pop_trans_top}, + {"DELE", pop_trans_dele}, + {"RSET", pop_trans_rset}, +#if POP_SUPPORT_LAST + {"LAST", pop_trans_last}, +#endif + {NULL} +}; + +static int db_load(char *spool, char *mailbox) +{ + db_init(); + + if (mailbox_open(spool, mailbox)) return 1; + + if (db_fix()) { + mailbox_close(); + return 1; + } + + return 0; +} + +int do_pop_trans(char *spool, char *mailbox) +{ + int result; + + if (!pop_sane()) return 1; + + if (db_load(spool, mailbox)) { + syslog(SYSLOG_PRIORITY, "Failed to open mailbox"); + pop_reply_error(); + return 0; + } + + syslog(SYSLOG_PRIORITY, "%d message%s (%ld byte%s) loaded", + db.total_count, db.total_count == 1 ? "" : "s", + db.total_size, db.total_size == 1 ? "" : "s"); + + if (pop_reply_ok()) + result = POP_CRASH; + else + switch ((result = pop_handle_state(pop_trans_commands))) { + case POP_STATE: + if (mailbox_update()) { + if (db.flags & DB_STALE) break; + syslog(SYSLOG_PRIORITY, "Failed to update mailbox"); + pop_reply_error(); + break; + } + + syslog(SYSLOG_PRIORITY, "%d (%ld) deleted, %d (%ld) left", + db.total_count - db.visible_count, + db.total_size - db.visible_size, + db.visible_count, + db.visible_size); + pop_reply_ok(); + break; + + case POP_TIMED_OUT: + syslog(SYSLOG_PRIORITY, "Connection timed out"); + } + + if (db.flags & DB_STALE) + syslog(SYSLOG_PRIORITY, "Another MUA active, giving up"); + else + if (result == POP_CRASH) + syslog(SYSLOG_PRIORITY, "Session crashed"); + + mailbox_close(); + + return 0; +} diff --git a/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 |