/* 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); }
/* 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); }
/* 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); }
/* 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); }
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); }