diff -r 000000000000 -r 9e2cb1ed20b1 pop3e.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pop3e.c Thu Mar 27 09:53:52 2014 +0500 @@ -0,0 +1,255 @@ +/* + * 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; + +pid_t +pop3_main(int pair[2], struct passwd *pw) +{ + 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 (pid); + + 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()) == 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); + l->ss = ss; + session_init(l, s); +} + +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); + } +} +