/* $Id$ */ /*** This file is part of libnewmail libnewmail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. libnewmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libnewmail; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***/ #include #include #include #include #include #include #include #include #include "module.h" #include "newmail.h" #include "util.h" #include "md5.h" #include "sockwrap.h" struct forkresp { struct nm_status status; int ret; int nm_errno; int system_errno; char nm_explanation[128]; }; struct data { char *username; char *password; char *hostname; int port; int tls; oop_source *oop; int fdpipe; struct forkresp forkresp; int nread; int debug; nm_query_cb_t cb; void *user; pid_t child; }; static int _pop3_process(struct nm_spool *s, enum nm_query query, struct nm_status *status) { struct sockwrap *sw = NULL; struct data *data = (struct data*) s->data; enum { HELLO, APOP, USER, PASS, STAT, LAST, QUIT } state = HELLO; int r = -1; status->cur = status->new = -1; if (!(sw = sockwrap(data->hostname, data->port, data->tls))) goto finish; for (;;) { int finished = 0; static char response[128]; static char request[128]; char *apop_token = 0; if (sockwrap_readln(sw, response, sizeof(response)) < 0) { nm_error(NM_ERROR_SERVFAIL, NULL); goto finish; } nm_chomp(response); if (data->debug) fprintf(stderr, "RECV: %s\n", response); if (state != LAST && response[0] != '+') { char *e; if (strlen(response) >= 5) e = response+5; else e = response; nm_error(NM_ERROR_SERVFAIL|NM_ERROR_EXPLANATION, e); goto finish; } switch (state) { case HELLO: if ((apop_token = strchr(response, '<'))) { char *e; if ((e = strchr(apop_token, '>'))) { *(e+1) = 0; state = APOP; break; } } state = USER; break; case USER: state = PASS; break; case PASS: case APOP: state = STAT; break; case STAT: if (strlen(response) < 5) { nm_error(NM_ERROR_SERVFAIL, response); goto finish; } status->cur = atoi(&response[4]); state = LAST; break; case LAST: if (response[0] == '+') { int i; if (strlen(response) < 5) { nm_error(NM_ERROR_SERVFAIL, response); goto finish; } i = atoi(&response[4]); if (status->cur <= i) status->new = 0; else status->new = status->cur - i; } state = QUIT; break; case QUIT: finished = 1; break; } if (finished) break; switch (state) { case USER: snprintf(request, sizeof(request), "USER %s\n", data->username); break; case PASS: snprintf(request, sizeof(request), "PASS %s\n", data->password); break; case APOP: { static char m[256]; md5_state_t st; unsigned char sum[16]; md5_init(&st); snprintf(m, sizeof(m), "%s%s", apop_token, data->password); md5_append(&st, m, strlen(m)); md5_finish(&st, sum); snprintf(request, sizeof(request), "APOP %s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", data->username, sum[0], sum[1], sum[2], sum[3], sum[4], sum[5], sum[6], sum[7], sum[8], sum[9], sum[10], sum[11], sum[12], sum[13], sum[14], sum[15]); break; } case STAT: snprintf(request, sizeof(request), "STAT\n"); break; case LAST: snprintf(request, sizeof(request), "LAST\n"); break; case QUIT: snprintf(request, sizeof(request), "QUIT\n"); break; case HELLO: // Completely useless, however, this will remove GCC's warning break; } if (data->debug) fprintf(stderr, "SEND: %s", request); if (sockwrap_writeln(sw, request) < 0) { nm_error(NM_ERROR_SERVFAIL, NULL); goto finish; } } r = 0; finish: if (sw) sockwrap_close(sw); return r; } static void* _callback(oop_source *source, int fd, oop_event event, void *user) { struct data *data = (struct data*) ((struct nm_spool*) user)->data; ssize_t r; if ((r = read(data->fdpipe, ((char*) &data->forkresp) + data->nread, sizeof(struct forkresp)-data->nread)) < 0) { nm_error(NM_ERROR_INTERNAL|NM_ERROR_SYSTEM, NULL); goto fail; } data->nread += r; if (data->nread >= sizeof(struct forkresp)) { if (data->forkresp.nm_errno == NM_ERROR_SUCCESS) { data->cb((struct nm_spool*) user, &data->forkresp.status, data->user); goto finish; } else { nm_error(data->forkresp.nm_errno, data->forkresp.nm_explanation[0] ? data->forkresp.nm_explanation : NULL); errno = data->forkresp.system_errno; goto fail; } } return OOP_CONTINUE; fail: data->cb((struct nm_spool*) user, NULL, data->user); finish: if (data->oop) data->oop->cancel_fd(data->oop, data->fdpipe, OOP_READ); close(data->fdpipe); data->fdpipe = -1; waitpid(data->child, 0, 0); data->child = (pid_t) -1; return OOP_CONTINUE; } static int _query_submit(struct nm_spool *s, enum nm_query query, oop_source* oop, nm_query_cb_t cb, void *user) { struct data *data = (struct data*) s->data; int pf[2] = { -1, -1}; pid_t pid; if (data->fdpipe >= 0) { nm_error(NM_ERROR_ALREADY, NULL); goto fail; } if (pipe(pf) < 0) { nm_error(NM_ERROR_FORK|NM_ERROR_SYSTEM, NULL); goto fail; } if ((pid = fork()) < 0) { nm_error(NM_ERROR_FORK|NM_ERROR_SYSTEM, NULL); goto fail; } else if (!pid) { struct forkresp forkresp = { { -1, -1 }, -1, 0, 0, "" }; FILE *f; signal(SIGPIPE, SIG_IGN); close(pf[0]); nm_error(NM_ERROR_SUCCESS, NULL); if ((forkresp.ret = _pop3_process(s, query, &forkresp.status)) < 0) { forkresp.system_errno = errno; forkresp.nm_errno = nm_errno; if (nm_explanation[0]) snprintf(forkresp.nm_explanation, sizeof(forkresp.nm_explanation), "%s", nm_explanation); else forkresp.nm_explanation[0] = 0; } f = fdopen(pf[1], "w"); fwrite(&forkresp, sizeof(forkresp), 1, f); fclose(f); close(pf[1]); exit(0); } else { close(pf[1]); data->fdpipe = pf[0]; data->nread = 0; data->cb = cb; data->user = user; data->oop = oop; data->child = pid; oop->on_fd(oop, data->fdpipe, OOP_READ, _callback, s); return 0; } fail: if (pf[0] >= 0) close(pf[0]); if (pf[1] >= 1) close(pf[1]); return -1; } static int _query(struct nm_spool *s, enum nm_query query, struct nm_status *status) { struct data *data = (struct data*) s->data; if (!s || !status) { nm_error(NM_ERROR_INVPAR, NULL); return -1; } if (data->fdpipe >= 0) { nm_error(NM_ERROR_ALREADY, NULL); return -1; } return _pop3_process(s, query, status); } static int _configure(struct nm_spool *s) { return -1; } static int _info(struct nm_spool *s, struct nm_info *i) { struct data *data = (struct data*) s->data; if (data->port == 110) snprintf(i->text, sizeof(i->text), "POP3 mailbox %s@%s", data->username, data->hostname); else snprintf(i->text, sizeof(i->text), "POP3 mailbox %s@%s:%i", data->username, data->hostname, data->port); i->flags = NM_FLAG_ASYNCHRONOUS; return 0; } static void _done(struct nm_spool *s) { struct data *data = (struct data*) s->data; if (data) { if (data->fdpipe >= 0) { if (data->oop) data->oop->cancel_fd(data->oop, data->fdpipe, OOP_READ); close(data->fdpipe); } nm_free(data->hostname); nm_free(data->username); nm_free(data->password); nm_free(data); s->data = NULL; } } int nm_init(struct nm_spool *s) { struct data *data; s->query = _query; s->query_submit = _query_submit; s->configure = _configure; s->info = _info; s->done = _done; data = nm_malloc(sizeof(struct data)); memset(data, 0, sizeof(struct data)); data->hostname = nm_strdup(nm_specials(nm_config_get(s->config, "Hostname", "localhost"))); data->username = nm_strdup(nm_specials(nm_config_get(s->config, "Username", "%u"))); data->password = nm_strdup(nm_config_get(s->config, "Password", "secret")); data->tls = nm_config_get_bool(s->config, "UseSSL", 0) || nm_config_get_bool(s->config, "UseTLS", 0); data->port = nm_config_get_int(s->config, "Port", data->tls ? 995 : 110); data->debug = nm_config_get_bool(s->config, "Debug", 0); data->fdpipe = -1; data->child = (pid_t) -1; s->data = (void*) data; return 0; }