/* * 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 #include "imsgev.h" #include "pop3d.h" #include "ssl.h" #define BACKLOG 5 static void auth_response(struct session *, int); static void pop3_accept(int, short, void *); static void pop3_listen(const char *); static void pop3_pause(int, short, void *); static void pop3d_imsgev(struct imsgev *, int, struct imsg *); static void needfd(struct imsgev *); static void sig_handler(int, short, void *); struct imsgev iev_pop3d; void *ssl_ctx; void pop3_main(int pair[2], struct passwd *pw, const char *cert, const char *key) { extern struct session_tree sessions; struct event ev_sigint, ev_sigterm; pid_t pid; pid = fork(); if (pid < 0) fatal("pop3e: fork"); if (pid > 0) return; close(pair[0]); setproctitle("pop3 engine"); SPLAY_INIT(&sessions); 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_pop3d, pair[1], NULL, pop3d_imsgev, needfd); pop3_listen("pop3"); ssl_init(); if ((ssl_ctx = ssl_setup(cert, key)) == NULL) fatal("ssl_setup failed"); pop3_listen("pop3s"); if (chroot(pw->pw_dir) == -1 || chdir("/") == -1) fatal("chroot"); 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"); if (event_dispatch() < 0) fatal("event_dispatch"); logit(LOG_INFO, "pop3 engine exiting"); _exit(0); } static void pop3_listen(const char *port) { struct listener *l = NULL; struct addrinfo hints, *res, *res0; int error, opt, serrno, s; const char *cause = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; error = getaddrinfo(NULL, port, &hints, &res0); if (error) errx(1, "%s", gai_strerror(error)); for (res = res0; res != NULL; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s == -1) { cause = "socket"; continue; } opt = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) fatal("listener setsockopt(SO_REUSEADDR)"); if (bind(s, res->ai_addr, res->ai_addrlen) == -1) { serrno = errno; cause = "bind"; close(s); errno = serrno; continue; } set_nonblocking(s); if (listen(s, BACKLOG) == -1) fatal("listen"); l = xcalloc(1, sizeof(*l), "pop3_listen"); l->sock = s; if (strcmp(port, "pop3s") == 0) l->flags |= POP3S; event_set(&l->ev, s, EV_READ|EV_PERSIST, pop3_accept, l); event_add(&l->ev, NULL); evtimer_set(&l->pause, pop3_pause, l); } if (l == NULL) errx(1, "%s", cause); freeaddrinfo(res0); } static void pop3_accept(int fd, short events, void *arg) { struct sockaddr_storage ss; struct listener *l = arg; struct timeval timeout = {1, 0}; socklen_t len; int s; len = sizeof(ss); s = accept(fd, (struct sockaddr *)&ss, &len); if (s == -1) { switch (errno) { case EINTR: case EWOULDBLOCK: case ECONNABORTED: return; case EMFILE: case ENFILE: event_del(&l->ev); evtimer_add(&l->pause, &timeout); return; default: fatalx("accept"); } } set_nonblocking(s); session_init(l, s, &ss); } static void pop3_pause(int fd, short events, void *arg) { struct listener *l = arg; event_add(&l->ev, NULL); } static void pop3d_imsgev(struct imsgev *iev, int code, struct imsg *imsg) { extern struct session_tree sessions; struct session key, *r; switch (code) { case IMSGEV_IMSG: key.id = imsg->hdr.peerid; r = SPLAY_FIND(session_tree, &sessions, &key); if (r == NULL) { logit(LOG_INFO, "%u: session not found", key.id); fatalx("pop3e: session lost"); } switch (imsg->hdr.type) { case IMSG_AUTH: auth_response(r, imsg->fd); break; default: logit(LOG_DEBUG, "%s: unexpected imsg %d", __func__, imsg->hdr.type); break; } break; case IMSGEV_EREAD: case IMSGEV_EWRITE: case IMSGEV_EIMSG: fatal("pop3e: imsgev read/write error"); break; case IMSGEV_DONE: event_loopexit(NULL); break; } } static void auth_response(struct session *s, int fd) { if (fd == -1) { session_reply(s, "%s", "-ERR auth failed"); io_set_write(&s->io); session_close(s, 1); return; } session_imsgev_init(s, fd); } static void needfd(struct imsgev *iev) { /* XXX can anything be done to handle fd exhaustion? */ fatalx("pop3e needs an fd"); } static void sig_handler(int sig, short event, void *arg) { switch (sig) { case SIGINT: case SIGTERM: event_loopexit(NULL); } }