/* Try an authentication method. */ int imap_pick_auth(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; /* Try CRAM-MD5, if server supports it and user allows it. */ if (!data->nocrammd5 && (data->capa & IMAP_CAPA_AUTH_CRAM_MD5)) { if (imap_putln(a, "%u AUTHENTICATE CRAM-MD5", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_cram_md5_auth; return (FETCH_BLOCK); } /* Fall back to LOGIN, unless config disallows it. */ if (!data->nologin) { if (imap_putln(a, "%u LOGIN {%zu}", ++data->tag, strlen(data->user)) != 0) return (FETCH_ERROR); fctx->state = imap_state_login; return (FETCH_BLOCK); } log_warnx("%s: no authentication methods", a->name); return (FETCH_ERROR); }
/* Search state 3. */ int imap_state_search3(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); /* Save the total. */ data->total = ARRAY_LENGTH(&data->wanted); /* Update grand total. */ data->folders_total += data->total; /* If no mails, or polling, stop here. */ if (data->total == 0 || fctx->flags & FETCH_POLL) { if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_close; return (FETCH_BLOCK); } fctx->state = imap_state_next; return (FETCH_AGAIN); }
/* Capability state 2. Check capabilities and choose login type. */ int imap_state_capability2(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); if (data->starttls) { if (!(data->capa & IMAP_CAPA_STARTTLS)) { log_warnx("%s: server doesn't support STARTTLS", a->name); return (FETCH_ERROR); } if (imap_putln(a, "%u STARTTLS", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_starttls; return (FETCH_BLOCK); } return (imap_pick_auth(a, fctx)); }
/* Connected state: wait for initial line from server. */ int imap_state_connected(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (strncmp(line, "* PREAUTH", 9) == 0) { fctx->state = imap_state_select1; return (FETCH_AGAIN); } if (data->user == NULL || data->pass == NULL) { log_warnx("%s: not PREAUTH and no user or password", a->name); return (FETCH_ERROR); } if (imap_putln(a, "%u CAPABILITY", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_capability1; return (FETCH_BLOCK); }
/* Select state 1. */ int imap_state_select1(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; if (imap_putln(a, "%u SELECT {%zu}", ++data->tag, strlen(ARRAY_ITEM(data->folders, data->folder))) != 0) return (FETCH_ERROR); fctx->state = imap_state_select2; return (FETCH_BLOCK); }
/* GMail extensions start state. */ int imap_state_gmext_start(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; struct mail *m = fctx->mail; struct fetch_imap_mail *aux = m->auxdata; if (imap_putln(a, "%u FETCH %u (X-GM-MSGID X-GM-THRID X-GM-LABELS)", ++data->tag, aux->uid) != 0) return (FETCH_ERROR); fctx->state = imap_state_gmext_body; return (FETCH_AGAIN); }
/* Login state. */ int imap_state_login(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; size_t passlen; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); passlen = strlen(data->pass); if (data->capa & IMAP_CAPA_NOSPACE) { if (imap_putln(a, "%s{%zu}", data->user, passlen) != 0) return (FETCH_ERROR); } else { if (imap_putln(a, "%s {%zu}", data->user, passlen) != 0) return (FETCH_ERROR); } fctx->state = imap_state_user; return (FETCH_BLOCK); }
/* Search state 1. Request list of mail required. */ int imap_state_search1(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; /* Search for a list of the mail UIDs to fetch. */ switch (data->only) { case FETCH_ONLY_NEW: if (imap_putln(a, "%u UID SEARCH UNSEEN", ++data->tag) != 0) return (FETCH_ERROR); break; case FETCH_ONLY_OLD: if (imap_putln(a, "%u UID SEARCH SEEN", ++data->tag) != 0) return (FETCH_ERROR); break; default: if (imap_putln(a, "%u UID SEARCH ALL", ++data->tag) != 0) return (FETCH_ERROR); break; } fctx->state = imap_state_search2; return (FETCH_BLOCK); }
/* Select state 2. Wait for continuation and send folder name. */ int imap_state_select2(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (imap_putln(a, "%s", ARRAY_ITEM(data->folders, data->folder)) != 0) return (FETCH_ERROR); fctx->state = imap_state_select3; return (FETCH_BLOCK); }
/* User state. */ int imap_state_user(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (imap_putln(a, "%s", data->pass) != 0) return (FETCH_ERROR); fctx->state = imap_state_pass; return (FETCH_BLOCK); }
/* CRAM-MD5 auth state. */ int imap_state_cram_md5_auth(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line, *ptr, *src, *b64; char out[EVP_MAX_MD_SIZE * 2 + 1]; u_char digest[EVP_MAX_MD_SIZE]; u_int i, n; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); ptr = line + 1; while (isspace((u_char) *ptr)) ptr++; if (*ptr == '\0') return (imap_invalid(a, line)); b64 = imap_base64_decode(ptr); HMAC(EVP_md5(), data->pass, strlen(data->pass), b64, strlen(b64), digest, &n); xfree(b64); for (i = 0; i < n; i++) xsnprintf(out + i * 2, 3, "%02hhx", digest[i]); xasprintf(&src, "%s %s", data->user, out); b64 = imap_base64_encode(src); xfree(src); if (imap_putln(a, "%s", b64) != 0) { xfree(b64); return (FETCH_ERROR); } xfree(b64); fctx->state = imap_state_pass; return (FETCH_BLOCK); }
/* User state. */ int imap_state_user(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; int tag; if (data->capa & IMAP_CAPA_NOSPACE) { if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); } else { for (;;) { if (imap_getln(a, fctx, IMAP_RAW, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); tag = imap_tag(line); if (tag == IMAP_TAG_NONE) continue; if (tag == IMAP_TAG_CONTINUE || tag == data->tag) break; return (FETCH_ERROR); } if (tag != IMAP_TAG_CONTINUE) { log_debug("%s: didn't accept user (%s); " "trying without space", a->name, line); data->capa |= IMAP_CAPA_NOSPACE; return (imap_pick_auth(a, fctx)); } } if (imap_putln(a, "%s", data->pass) != 0) return (FETCH_ERROR); fctx->state = imap_state_pass; return (FETCH_BLOCK); }
/* Close state. */ int imap_state_close(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); data->folder++; if (data->folder != ARRAY_LENGTH(data->folders)) { fctx->state = imap_state_select1; return (FETCH_AGAIN); } if (imap_putln(a, "%u LOGOUT", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_quit; return (FETCH_BLOCK); }
/* Select state 4. Hold until select completes. */ int imap_state_select4(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); /* If no mails, stop early. */ if (data->total == 0) { if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_close; return (FETCH_BLOCK); } fctx->state = imap_state_search1; return (FETCH_AGAIN); }
/* * Next state. Get next mail. This is also the idle state when completed, so * check for finished mail, exiting, and so on. */ int imap_state_next(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; /* Handle dropped and kept mail. */ if (!ARRAY_EMPTY(&data->dropped)) { if (imap_putln(a, "%u UID STORE %u +FLAGS.SILENT (\\Deleted)", ++data->tag, ARRAY_FIRST(&data->dropped)) != 0) return (FETCH_ERROR); ARRAY_REMOVE(&data->dropped, 0); fctx->state = imap_state_commit; return (FETCH_BLOCK); } if (!ARRAY_EMPTY(&data->kept)) { /* * GMail is broken and does not set the \Seen flag after mail * is fetched, so set it explicitly for kept mail. */ if (imap_putln(a, "%u UID STORE %u +FLAGS.SILENT (\\Seen)", ++data->tag, ARRAY_FIRST(&data->kept)) != 0) return (FETCH_ERROR); ARRAY_REMOVE(&data->kept, 0); fctx->state = imap_state_commit; return (FETCH_BLOCK); } /* Need to purge, switch to purge state. */ if (fctx->flags & FETCH_PURGE) { /* * If can't purge now, loop through this state until there is * no mail on the dropped queue and FETCH_EMPTY is set. Can't * have a seperate state to loop through without returning * here: mail could potentially be added to the dropped list * while in that state. */ if (fctx->flags & FETCH_EMPTY) { fctx->flags &= ~FETCH_PURGE; if (imap_putln(a, "%u EXPUNGE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_expunge; return (FETCH_BLOCK); } /* * Must be waiting for delivery, so permit blocking even though * we (fetch) aren't waiting for any data. */ return (FETCH_BLOCK); } /* If last mail, wait for everything to be committed then close down. */ if (ARRAY_EMPTY(&data->wanted)) { if (data->committed != data->total) return (FETCH_BLOCK); if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_close; return (FETCH_BLOCK); } /* Fetch the next mail. */ if (imap_putln(a, "%u " "UID FETCH %u BODY[]",++data->tag, ARRAY_FIRST(&data->wanted)) != 0) return (FETCH_ERROR); fctx->state = imap_state_body; return (FETCH_BLOCK); }
int deliver_imap_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_imap_data *data = ti->data; struct io *io; struct fetch_ctx fctx; struct fetch_imap_data fdata; char *cause, *folder, *ptr, *line; size_t len, maillen; u_int total, body; /* Connect to the IMAP server. */ io = connectproxy(&data->server, conf.verify_certs, conf.proxy, IO_CRLF, conf.timeout, &cause); if (io == NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (DELIVER_FAILURE); } if (conf.debug > 3 && !conf.syslog) io->dup_fd = STDOUT_FILENO; /* Work out the folder name. */ folder = replacestr(&data->folder, m->tags, m, &m->rml); if (folder == NULL || *folder == '\0') { log_warnx("%s: empty folder", a->name); goto error; } /* Fake up the fetch context for the fetch code. */ memset(&fdata, 0, sizeof fdata); fdata.user = data->user; fdata.pass = data->pass; fdata.nocrammd5 = data->nocrammd5; fdata.nologin = data->nologin; memcpy(&fdata.server, &data->server, sizeof fdata.server); fdata.io = io; fdata.only = FETCH_ONLY_ALL; a->data = &fdata; fetch_imap_state_init(a, &fctx); fctx.state = imap_state_connected; fctx.llen = IO_LINESIZE; fctx.lbuf = xmalloc(fctx.llen); /* Use the fetch code until the select1 state is reached. */ if (deliver_imap_pollto(imap_state_select1, a, io, &fctx) != 0) goto error; retry: /* Send an append command. */ if (imap_putln(a, "%u APPEND {%zu}", ++fdata.tag, strlen(folder)) != 0) goto error; switch (deliver_imap_waitappend(a, &fctx, io, &line)) { case IMAP_TAG_ERROR: if (line != NULL) imap_invalid(a, line); goto error; case IMAP_TAG_CONTINUE: break; default: if (imap_no(line) && strstr(line, "[TRYCREATE]") != NULL) goto try_create; imap_invalid(a, line); goto error; } /* * Send the mail size, not forgetting lines are CRLF terminated. The * Google IMAP server is written strangely, so send the size as if * every CRLF was a CR if the server has XYZZY. */ count_lines(m, &total, &body); maillen = m->size + total - 1; if (fdata.capa & IMAP_CAPA_XYZZY) { log_debug2("%s: adjusting size: actual %zu", a->name, maillen); maillen = m->size; } if (fdata.capa & IMAP_CAPA_NOSPACE) { if (imap_putln(a, "%s{%zu}", folder, maillen) != 0) goto error; } else { if (imap_putln(a, "%s {%zu}", folder, maillen) != 0) goto error; } switch (deliver_imap_waitappend(a, &fctx, io, &line)) { case IMAP_TAG_ERROR: if (line != NULL) imap_invalid(a, line); goto error; case IMAP_TAG_CONTINUE: break; default: if (imap_no(line) && strstr(line, "[TRYCREATE]") != NULL) goto try_create; imap_invalid(a, line); goto error; } /* Send the mail data. */ line_init(m, &ptr, &len); while (ptr != NULL) { if (len > 1) io_write(io, ptr, len - 1); io_writeline(io, NULL); /* Update if necessary. */ if (io_update(io, conf.timeout, &cause) != 1) { log_warnx("%s: %s", a->name, cause); xfree(cause); goto error; } line_next(m, &ptr, &len); } /* Wait for an okay from the server. */ switch (deliver_imap_waitappend(a, &fctx, io, &line)) { case IMAP_TAG_ERROR: case IMAP_TAG_CONTINUE: if (line != NULL) imap_invalid(a, line); goto error; default: if (imap_okay(line)) break; if (strstr(line, "[TRYCREATE]") != NULL) goto try_create; imap_invalid(a, line); goto error; } xfree(fctx.lbuf); xfree(folder); if (imap_putln(a, "%u LOGOUT", ++fdata.tag) != 0) goto error; if (deliver_imap_waitokay(a, &fctx, io, &line) != 0) goto error; fdata.disconnect(a); return (DELIVER_SUCCESS); try_create: /* XXX function? */ /* Try to create the folder. */ if (imap_putln(a, "%u CREATE {%zu}", ++fdata.tag, strlen(folder)) != 0) goto error; if (deliver_imap_waitcontinue(a, &fctx, io, &line) != 0) goto error; if (imap_putln(a, "%s", folder) != 0) goto error; if (deliver_imap_waitokay(a, &fctx, io, &line) != 0) goto error; goto retry; error: io_writeline(io, "QUIT"); io_flush(io, conf.timeout, NULL); xfree(fctx.lbuf); if (folder != NULL) xfree(folder); fdata.disconnect(a); return (DELIVER_FAILURE); }