diff -r 000000000000 -r 9e2cb1ed20b1 maildrop.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/maildrop.c Thu Mar 27 09:53:52 2014 +0500 @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2014 Sunil Nimmagadda + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imsgev.h" +#include "pop3d.h" + +static void session_imsgev(struct imsgev *, int, struct imsg *); +static void update(struct imsgev *, struct imsg *, struct m_backend *); +static void retr(struct imsgev *, struct imsg *, struct m_backend *); +static void dele(struct imsgev *, struct imsg *, struct m_backend *); +static void rset(struct imsgev *, struct imsg *, struct m_backend *); +static void list(struct imsgev *, struct imsg *, struct m_backend *); +static void list_all(struct imsgev *, struct imsg *, struct m_backend *, int); +static void do_list(unsigned int, size_t *, char *, size_t); +static void *do_list_all(int, size_t *); +static struct m_backend *m_backend_lookup(enum m_type); +static void sig_handler(int, short, void *); +static void needfd(struct imsgev *); +static size_t expand(char *, const char *, size_t, struct passwd *); + +static struct mdrop m; + +void +maildrop_init(uint32_t session_id, int pair[2], struct passwd *pw, + int type, const char *path) +{ + struct imsgev iev_session; + struct event ev_sigint, ev_sigterm; + struct stats stats; + struct m_backend *mb; + char buf[MAXPATHLEN]; + pid_t pid; + mode_t old_mask; + int fd, flags, res = -1; + + if (seteuid(pw->pw_uid) < 0) + fatal("cannot lower privileges"); + + pid = fork(); + if (seteuid(0) < 0) + fatal("cannot restore privileges"); + + if (pid < 0) + fatal("maildrop: fork"); + + if (pid > 0) + return; + + if (seteuid(pw->pw_uid) < 0) + fatal("cannot lower privileges"); + + close(pair[0]); + setproctitle("maildrop"); + if ((mb = m_backend_lookup(type)) == NULL) + fatalx("maildrop: invalid backend"); + + if (expand(buf, path, sizeof(buf), pw) >= sizeof(buf)) + fatalx("maildrop: path truncation"); + + flags = O_CREAT; + if (type == M_MBOX) + flags |= O_RDWR; + else + flags |= O_RDONLY; + + old_mask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if ((fd = open(buf, flags)) == -1) + logit(LOG_CRIT, "%zu: failed to open %s", session_id , buf); + + if (fd != -1) { + m.fd = fd; + res = mb->init(&m, &stats.nmsgs, &stats.sz); + } + + umask(old_mask); + if (seteuid(0) < 0) + fatal("cannot restore privileges"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("cannot drop privileges"); + + event_init(); + signal_set(&ev_sigint, SIGINT, sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + imsgev_init(&iev_session, pair[1], mb, session_imsgev, needfd); + + if (res == 0) { + imsgev_xcompose(&iev_session, IMSG_MAILDROP_INIT, session_id, + 0, -1, &stats, sizeof(struct stats), "maildrop_init"); + } else { + logit(LOG_CRIT, "%zu: maildrop init failed %s", + session_id, buf); + imsgev_xcompose(&iev_session, IMSG_MAILDROP_INIT, session_id, + 0, -1, NULL, 0, "maildrop_init"); + } + + if (event_dispatch() < 0) + fatal("event_dispatch"); + + logit(LOG_INFO, "maildrop process exiting"); + _exit(0); +} + +/* + * Build dst by substituting '~' with user's home dir and '%u' with user name + * in src. Return the length of string built. If return value >= dst_sz then + * dst is truncated. + */ +static size_t +expand(char *dst, const char *src, size_t dst_sz, struct passwd *pw) +{ + size_t i = 0, r; + int c; + + while ((c = *src++)) { + if (i >= dst_sz) + break; + + switch (c) { + case '~': + if ((r = strlcpy(&dst[i], pw->pw_dir, + (dst_sz - i))) >= (dst_sz - i)) { + i += r; + goto end; + } + i += r; + break; + case '%': + if (*src == 'u') { + if ((r = strlcpy(&dst[i], pw->pw_name, + (dst_sz - i))) >= (dst_sz - i)) { + i += r; + goto end; + } + i += r; + src++; + } else + dst[i++] = c; + break; + default: + dst[i++] = c; + break; + } + } + +end: + if (c) + while ((c = *src++)) + i++; + + dst[dst_sz - 1] = '\0'; + return (i); +} + +static void +session_imsgev(struct imsgev *iev, int code, struct imsg *imsg) +{ + struct m_backend *mb = iev->data; + int uidl = 0; + + switch (code) { + case IMSGEV_IMSG: + switch (imsg->hdr.type) { + case IMSG_MAILDROP_UPDATE: + update(iev, imsg, mb); + break; + case IMSG_MAILDROP_RETR: + retr(iev, imsg, mb); + break; + case IMSG_MAILDROP_DELE: + dele(iev, imsg, mb); + break; + case IMSG_MAILDROP_RSET: + rset(iev, imsg, mb); + break; + case IMSG_MAILDROP_LIST: + list(iev, imsg, mb); + break; + case IMSG_MAILDROP_UIDLALL: + uidl = 1; + /* FALLTHROUGH */ + case IMSG_MAILDROP_LISTALL: + list_all(iev, imsg, mb, uidl); + break; + default: + logit(LOG_DEBUG, "%s: unexpected imsg %u", + __func__, imsg->hdr.type); + break; + } + break; + case IMSGEV_EREAD: + case IMSGEV_EWRITE: + case IMSGEV_EIMSG: + fatal("maildrop: imsgev read/write error"); + break; + case IMSGEV_DONE: + event_loopexit(NULL); + break; + } +} + +static void +update(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb) +{ + int res; + uint32_t session_id = imsg->hdr.peerid; + + if ((res = mb->update(&m)) == 0) + logit(LOG_INFO, "%zu: maildrop updated", session_id); + else + logit(LOG_CRIT, "%zu: maildrop updated failed", session_id); + + imsgev_xcompose(iev, IMSG_MAILDROP_UPDATE, session_id, 0, + -1, &res, sizeof(res), "maildrop_update"); +} + +static void +retr(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb) +{ + struct retr_res res; + struct retr_req *req = imsg->data; + int fd; + + fd = mb->retr(&m, req->idx, &res.nlines, &res.offset); + /* pass on top arguments */ + res.top = req->top; + res.ntop = req->ntop; + imsgev_xcompose(iev, IMSG_MAILDROP_RETR, imsg->hdr.peerid, 0, + fd, &res, sizeof(res), "maildrop_retr"); +} + +static void +dele(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb) +{ + unsigned int *idx = imsg->data; + int res = 0; + + if (m.msgs_index[*idx]->flags & F_DELE) { + res = -1; + goto end; + } + + m.msgs_index[*idx]->flags |= F_DELE; +end: + imsgev_xcompose(iev, IMSG_MAILDROP_DELE, imsg->hdr.peerid, 0, + -1, &res, sizeof(res), "maildrop_dele"); +} + +static void +rset(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb) +{ + size_t i; + + for (i = 0; i < m.nmsgs; i++) + m.msgs_index[i]->flags = 0; + + imsgev_xcompose(iev, IMSG_MAILDROP_RSET, imsg->hdr.peerid, 0, + -1, NULL, 0, "maildrop_rset"); +} + +static void +list(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb) +{ + struct list_req *req = imsg->data; + struct list_res res; + char hash[SHA1_DIGEST_STRING_LENGTH]; + size_t sz; + + res.idx = req->idx; + do_list(req->idx, &sz, hash, sizeof(hash)); + res.uidl = req->uidl; + if (res.uidl) + strlcpy(res.u.hash, hash, sizeof(res.u.hash)); + else + res.u.sz = sz; + + imsgev_xcompose(iev, IMSG_MAILDROP_LIST, imsg->hdr.peerid, 0, + -1, &res, sizeof(res), "maildrop_list"); + +} + +static void +list_all(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb, int uidl) +{ + void *res; + size_t sz; + + res = do_list_all(uidl, &sz); + /* XXX watchout for sz > MAX_IMSGSIZE */ + imsgev_xcompose(iev, + (uidl) ? IMSG_MAILDROP_UIDLALL : IMSG_MAILDROP_LISTALL, + imsg->hdr.peerid, 0, -1, res, sz, "maildrop_list"); +} + +static void +do_list(unsigned int idx, size_t *sz, char *hash, size_t hash_sz) +{ + if (m.msgs_index[idx]->flags & F_DELE) { + *sz = 0; + strlcpy(hash, "", hash_sz); + return; + } + + *sz = m.msgs_index[idx]->sz; + strlcpy(hash, m.msgs_index[idx]->hash, hash_sz); +} + +static void * +do_list_all(int uidl, size_t *sz) +{ + size_t i, j, *nsz = NULL; + char *nhash = NULL; + + if (uidl) { + nhash = xcalloc(m.nmsgs, SHA1_DIGEST_STRING_LENGTH, "list_all"); + } else + nsz = xcalloc(m.nmsgs, sizeof(size_t), "list_all"); + + for (i = 0; i < m.nmsgs; i++) { + + if (uidl) { + j = i * SHA1_DIGEST_STRING_LENGTH; + if (m.msgs_index[i]->flags & F_DELE) + nhash[j] = '\0'; + else + strlcpy(nhash + j, m.msgs_index[i]->hash, + SHA1_DIGEST_STRING_LENGTH); + } else { + if (m.msgs_index[i]->flags & F_DELE) + nsz[i] = 0; + else + nsz[i] = m.msgs_index[i]->sz; + } + } + + if (uidl) { + *sz = m.nmsgs * SHA1_DIGEST_STRING_LENGTH; + return (nhash); + } else { + *sz = m.nmsgs * sizeof(size_t); + return (nsz); + } +} + +static void +needfd(struct imsgev *iev) +{ + fatalx("maildrop should never need an fd"); +} + +static void +sig_handler(int sig, short event, void *arg) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + event_loopexit(NULL); + } +} + +extern struct m_backend m_backend_mbox; +extern struct m_backend m_backend_maildir; + +static struct m_backend * +m_backend_lookup(enum m_type type) +{ + switch (type) { + case M_MBOX: + return &m_backend_mbox; + break; + case M_MAILDIR: + return &m_backend_maildir; + break; + default: + fatalx("m_backend_lookup: invalid m_type"); + }; + + return (NULL); +} +