/* 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); }
/* 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); }
/* 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)); }
/* Capability state 1. Parse capabilities and set flags. */ int imap_state_capability1(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line, *ptr; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); /* Convert to uppercase. */ for (ptr = line; *ptr != '\0'; ptr++) *ptr = toupper((u_char) *ptr); if (strstr(line, "IMAP4REV1") == NULL) { log_warnx("%s: no IMAP4rev1 capability: %s", a->name, line); return (FETCH_ERROR); } data->capa = 0; if (strstr(line, "AUTH=CRAM-MD5") != NULL) data->capa |= IMAP_CAPA_AUTH_CRAM_MD5; /* Use XYZZY to detect Google brokenness. */ if (strstr(line, "XYZZY") != NULL) data->capa |= IMAP_CAPA_XYZZY; if (strstr(line, "STARTTLS") != NULL) data->capa |= IMAP_CAPA_STARTTLS; fctx->state = imap_state_capability2; return (FETCH_AGAIN); }
/* Line state. */ int imap_state_line(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; struct mail *m = fctx->mail; char *line; size_t used, size, left; for (;;) { if (imap_getln(a, fctx, IMAP_RAW, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (data->flushing) continue; /* Check if this line would exceed the expected size. */ used = m->size + data->lines; size = strlen(line); if (used + size + 2 > data->size) break; if (append_line(m, line, size) != 0) { log_warnx("%s: failed to resize mail", a->name); return (FETCH_ERROR); } data->lines++; } /* * Calculate the number of bytes still needed. The current line must * include at least that much data. Some servers include UID or FLAGS * after the message: we don't care about these so just ignore them and * make sure there is a terminating ). */ left = data->size - used; if (line[size - 1] != ')' && size <= left) return (imap_invalid(a, line)); /* If there was data left, add it as a new line without trailing \n. */ if (left > 0) { if (append_line(m, line, left) != 0) { log_warnx("%s: failed to resize mail", a->name); return (FETCH_ERROR); } data->lines++; /* Wipe out the trailing \n. */ m->size--; } fctx->state = imap_state_mail; return (FETCH_AGAIN); }
/* Body state. */ int imap_state_body(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; struct mail *m = fctx->mail; struct fetch_imap_mail *aux; char *line, *ptr; u_int n; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (sscanf(line, "* %u FETCH (", &n) != 1) return (imap_invalid(a, line)); if ((ptr = strstr(line, "BODY[] {")) == NULL) return (imap_invalid(a, line)); if (sscanf(ptr, "BODY[] {%zu}", &data->size) != 1) return (imap_invalid(a, line)); data->lines = 0; /* Fill in local data. */ aux = xcalloc(1, sizeof *aux); aux->uid = ARRAY_FIRST(&data->wanted); m->auxdata = aux; m->auxfree = imap_free; ARRAY_REMOVE(&data->wanted, 0); /* Open the mail. */ if (mail_open(m, data->size) != 0) { log_warnx("%s: failed to create mail", a->name); return (FETCH_ERROR); } m->size = 0; /* Tag mail. */ default_tags(&m->tags, data->src); if (data->server.host != NULL) { add_tag(&m->tags, "server", "%s", data->server.host); add_tag(&m->tags, "port", "%s", data->server.port); } add_tag(&m->tags, "server_uid", "%u", aux->uid); add_tag(&m->tags, "folder", "%s", ARRAY_ITEM(data->folders, data->folder)); /* If we already know the mail is oversize, start off flushing it. */ data->flushing = data->size > conf.max_size; fctx->state = imap_state_line; return (FETCH_AGAIN); }
/* Wait for continuation. */ int deliver_imap_waitcontinue(struct account *a, struct fetch_ctx *fctx, struct io *io, char **line) { do { if (deliver_imap_poll(a, io) != 0) return (1); if (imap_getln(a, fctx, IMAP_CONTINUE, line) != 0) return (1); } while (*line == NULL); return (0); }
/* 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); }
/* Expunge state. */ int imap_state_expunge(struct account *a, struct fetch_ctx *fctx) { 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)); fctx->state = imap_state_next; return (FETCH_AGAIN); }
/* Quit state. */ int imap_state_quit(struct account *a, struct fetch_ctx *fctx) { 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)); imap_abort(a); return (FETCH_EXIT); }
/* 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); }
/* GMail extensions body state. */ int imap_state_gmext_body(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; struct mail *m = fctx->mail; char *line, *lb; int tag; u_int n; uint64_t thrid, msgid; size_t lblen; 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) break; if (tag == data->tag) { fctx->state = imap_state_next; return (FETCH_MAIL); } return (FETCH_ERROR); } if (sscanf(line, "* %u FETCH (X-GM-THRID %llu X-GM-MSGID %llu ", &n, &thrid, &msgid) != 3) return (imap_invalid(a, line)); if ((lb = strstr(line, "X-GM-LABELS")) == NULL) return (imap_invalid(a, line)); if ((lb = strchr(lb, '(')) == NULL) return (imap_invalid(a, line)); lb++; /* drop '(' */ lblen = strlen(lb); if (lblen < 2 || lb[lblen - 1] != ')' || lb[lblen - 2] != ')') return (imap_invalid(a, line)); lblen -= 2; /* drop '))' from the end */ add_tag(&m->tags, "gmail_msgid", "%llu", msgid); add_tag(&m->tags, "gmail_thrid", "%llu", thrid); add_tag(&m->tags, "gmail_labels", "%.*s", (int)lblen, lb); fctx->state = imap_state_gmext_done; return (FETCH_AGAIN); }
/* Wait for okay. */ int deliver_imap_waitokay(struct account *a, struct fetch_ctx *fctx, struct io *io, char **line) { do { if (deliver_imap_poll(a, io) != 0) return (1); if (imap_getln(a, fctx, IMAP_TAGGED, line) != 0) return (1); } while (*line == NULL); if (!imap_okay(*line)) { imap_bad(a, *line); return (1); } return (0); }
/* Commit state. */ int imap_state_commit(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->committed++; fctx->state = imap_state_next; return (FETCH_AGAIN); }
/* Select state 3. Hold until select returns message count. */ int imap_state_select3(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; for (;;) { if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (sscanf(line, "* %u EXISTS", &data->total) == 1) break; } fctx->state = imap_state_select4; return (FETCH_AGAIN); }
/* Mail state. */ int imap_state_mail(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->capa & IMAP_CAPA_GMEXT) { fctx->state = imap_state_gmext_start; return (FETCH_AGAIN); } fctx->state = imap_state_next; return (FETCH_MAIL); }
/* 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); }
/* Search state 2. */ int imap_state_search2(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line, *ptr; u_int uid; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); /* Skip the header. */ if (strncasecmp(line, "* SEARCH", 8) != 0) return (imap_bad(a, line)); line += 8; /* Read each UID and save it. */ do { while (isspace((u_char) *line)) line++; ptr = strchr(line, ' '); if (ptr == NULL) ptr = strchr(line, '\0'); if (ptr == line) break; if (sscanf(line, "%u", &uid) != 1) return (imap_bad(a, line)); ARRAY_ADD(&data->wanted, uid); log_debug3("%s: fetching UID: %u", a->name, uid); line = ptr; } while (*line == ' '); fctx->state = imap_state_search3; return (FETCH_AGAIN); }
/* STARTTLS state. */ int imap_state_starttls(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line, *cause; 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->io->ssl = makessl(&data->server, data->io->fd, conf.verify_certs && data->server.verify, conf.timeout, &cause); if (data->io->ssl == NULL) { log_warnx("%s: STARTTLS failed: %s", a->name, cause); xfree(cause); return (FETCH_ERROR); } return (imap_pick_auth(a, fctx)); }
/* 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); }
/* 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); }