/* $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 "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; char *folder; oop_source *oop; int fdpipe; struct forkresp forkresp; int nread; int debug; nm_query_cb_t cb; void *user; pid_t child; }; static int _imap_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, LOGIN, STATUS, LOGOUT } state = HELLO; char *tag = "* "; int r = -1; static char star[256] = ""; status->cur = status->new = -1; if (!(sw = sockwrap(data->hostname, data->port, data->tls))) goto finish; for (;;) { int finished = 0; static char response[256]; static char request[256]; char *p; 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 (strncmp(response, tag, strlen(tag))) { if (!strncmp(response, "* ", 2)) strcpy(star, response); continue; } p = response + strlen(tag); if (strncmp(p, "OK ", 3)) { char *e; if ((e = strchr(p, ' '))) e++; else e = p; nm_error(NM_ERROR_SERVFAIL|NM_ERROR_EXPLANATION, e); goto finish; } switch (state) { case HELLO: state = LOGIN; break; case LOGIN: state = STATUS; star[0] = 0; break; case STATUS: if (sscanf(star, "%*s %*s %*s %*s %d %*s %d", &status->cur, &status->new) != 2) { status->cur = status->new = -1; nm_error(NM_ERROR_SERVFAIL, NULL); goto finish; } state = LOGOUT; break; case LOGOUT: finished = 1; break; } if (finished) break; switch (state) { case LOGIN: snprintf(request, sizeof(request), "%sLOGIN \"%s\" \"%s\"\n", tag = "A ", data->username, data->password); break; case STATUS: snprintf(request, sizeof(request), "%sSTATUS \"%s\" (MESSAGES UNSEEN)\n", tag = "B ", data->folder); break; case LOGOUT: snprintf(request, sizeof(request), "%sLOGOUT\n", tag = "C "); 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(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 _check_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 = _imap_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 _check(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 _imap_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 == 143) snprintf(i->text, sizeof(i->text), "IMAP mailbox %s@%s/%s", data->username, data->hostname, data->folder); else snprintf(i->text, sizeof(i->text), "IMAP mailbox %s@%s:%i/%s", data->username, data->hostname, data->port, data->folder); 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 = _check; s->query_submit = _check_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->folder = nm_strdup(nm_specials(nm_config_get(s->config, "Folder", "INBOX"))); 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 ? 993 : 143); data->debug = nm_config_get_bool(s->config, "Debug", 0); data->fdpipe = -1; data->child = (pid_t) -1; s->data = (void*) data; return 0; }