/* $Id$ */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "bbs.h" static struct timeval tv = {600, 0}; static struct event ev_listen; static int clients = 0; #define READ_BLOCK 512 #define MAX_CLIENTS 500 struct client_state { struct event ev; int state; int pop3_state; struct evbuffer *evb_read; struct evbuffer *evb_write; char userid[32]; int uid; }; enum { POP3_AUTH, POP3_TRANS, POP3_UPDATE, POP3_CLEANUP }; typedef struct { char *cmd; void (*func)(struct client_state *cs, const char * arg); } CMD; void cmd_unknown(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "-ERR unknown command\r\n"); } void cmd_capa(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "+OK\r\nUSER\r\n.\r\n"); } void cmd_user(struct client_state *cs, const char * arg) { cs->uid = searchuser(arg, cs->userid); if (cs->uid < 1 || cs->uid > MAX_USERS) evbuffer_add_printf(cs->evb_write, "-ERR user not found\r\n"); else evbuffer_add_printf(cs->evb_write, "+OK\r\n"); } void cmd_pass(struct client_state *cs, const char * arg) { userec_t cuser; char * pw; if (passwd_query(cs->uid, &cuser) < 0) { evbuffer_add_printf(cs->evb_write, "-ERR user not found\r\n"); return; } pw = crypt(arg, cuser.passwd); if (strcmp(pw, cuser.passwd) == 0) { evbuffer_add_printf(cs->evb_write, "+OK\r\n"); cs->pop3_state = POP3_TRANS; } else evbuffer_add_printf(cs->evb_write, "-ERR password incorrect \r\n"); } void cmd_quit(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "+OK bye\r\n"); if (cs->pop3_state == POP3_TRANS) cs->pop3_state = POP3_UPDATE; else cs->pop3_state = POP3_CLEANUP; } static const CMD auth_cmdlist[] = { {"user", cmd_user}, {"pass", cmd_pass}, {"quit", cmd_quit}, {"capa", cmd_capa}, {NULL, cmd_unknown} }; /* Transaction state commands not implemented */ void cmd_stat(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "+OK 0 0\r\n"); } void cmd_list(struct client_state *cs, const char * arg) { if (*arg) evbuffer_add_printf(cs->evb_write, "-ERR no such message\r\n"); else evbuffer_add_printf(cs->evb_write, "+OK\r\n.\r\n"); } void cmd_retr(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "-ERR no such message\r\n"); } void cmd_dele(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "-ERR no such message\r\n"); } void cmd_noop(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "+OK\r\n"); } void cmd_rset(struct client_state *cs, const char * arg) { evbuffer_add_printf(cs->evb_write, "+OK\r\n"); } static const CMD trans_cmdlist[] = { {"stat", cmd_stat}, {"list", cmd_list}, {"retr", cmd_retr}, {"dele", cmd_dele}, {"noop", cmd_noop}, {"rset", cmd_rset}, {"capa", cmd_capa}, {"quit", cmd_quit}, {NULL, cmd_unknown} }; void cb_client(int cfd, short event, void *arg) { struct client_state *cs = arg; char *p, *line = NULL; int i; // ignore clients that timeout if (event & EV_TIMEOUT) cs->pop3_state = POP3_CLEANUP; if (event & EV_READ) if (evbuffer_read(cs->evb_read, cfd, READ_BLOCK) <= 0) cs->pop3_state = POP3_CLEANUP; if (event & EV_WRITE) if (evbuffer_write(cs->evb_write, cfd) <= 0) cs->pop3_state = POP3_CLEANUP; if ((line = evbuffer_readline(cs->evb_read)) == NULL) goto out; for (p = line; *p != ' ' && *p != '\0'; p++); if (*p == ' ') *p++ = '\0'; if (cs->pop3_state == POP3_AUTH) { for (i = 0; auth_cmdlist[i].cmd; i++) if (strcasecmp(line, auth_cmdlist[i].cmd) == 0) break; (auth_cmdlist[i].func)(cs, p); } else if (cs->pop3_state == POP3_TRANS) { for (i = 0; trans_cmdlist[i].cmd; i++) if (strcasecmp(line, trans_cmdlist[i].cmd) == 0) break; (trans_cmdlist[i].func)(cs, p); } out: evbuffer_write(cs->evb_write, cfd); if (cs->pop3_state == POP3_UPDATE) { cs->pop3_state = POP3_CLEANUP; } if (cs->pop3_state == POP3_CLEANUP) { if (clients == MAX_CLIENTS) event_add(&ev_listen, NULL); close(cfd); evbuffer_free(cs->evb_write); evbuffer_free(cs->evb_read); free(cs); clients--; return; } if (EVBUFFER_LENGTH(cs->evb_write) > 0) event_set(&cs->ev, cfd, EV_WRITE, (void *) cb_client, cs); else event_set(&cs->ev, cfd, EV_READ, (void *) cb_client, cs); event_add(&cs->ev, &tv); } void setup_client(int fd) { fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); struct client_state *cs = calloc(1, sizeof(struct client_state)); cs->state = EV_WRITE; cs->pop3_state = POP3_AUTH; cs->evb_read = evbuffer_new(); cs->evb_write = evbuffer_new(); evbuffer_add_printf(cs->evb_write, "+OK PttBBS POP3 ready\r\n"); event_set(&cs->ev, fd, EV_WRITE, (void *) cb_client, cs); event_add(&cs->ev, &tv); } void cb_listen(int fd, short event, void *arg) { struct sockaddr_in clientaddr; socklen_t len = sizeof(clientaddr); int cfd; if ((cfd = accept(fd, (struct sockaddr *)&clientaddr, &len)) < 0 ) return; setup_client(cfd); clients++; if (clients > MAX_CLIENTS) event_del(&ev_listen); } int main(int argc, char *argv[]) { int ch, sfd, inetd = 0, daemon = 1; char *iface_ip = "127.0.0.1:5140"; Signal(SIGPIPE, SIG_IGN); while ((ch = getopt(argc, argv, "Dil:h")) != -1) switch (ch) { case 'D': daemon = 0; break; case 'i': inetd = 1; break; case 'l': iface_ip = optarg; break; case 'h': default: fprintf(stderr, "usage: bpop3d [-D] [-i] [-l [interface_ip]:port]\n"); return 1; } if (!inetd) if ((sfd = tobind(iface_ip)) < 0) return 1; setuid(BBSUID); setgid(BBSGID); srandom(getpid() + time(NULL)); attach_SHM(); if (daemon) daemonize(BBSHOME "/run/bpop3d.pid", NULL); event_init(); if (!inetd) { event_set(&ev_listen, sfd, EV_READ | EV_PERSIST, cb_listen, &ev_listen); event_add(&ev_listen, NULL); } else setup_client(0); event_dispatch(); return 0; }