/* $Id: imap-common.c,v 1.66 2007/09/25 21:34:51 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" int imap_putln(struct account *, const char *, ...); int imap_getln(struct account *, struct fetch_ctx *, int, char **); void imap_free(void *); int imap_okay(char *); int imap_parse(struct account *, int, char *); int imap_tag(char *); char *imap_base64_encode(char *); char *imap_base64_decode(char *); int imap_bad(struct account *, const char *); int imap_invalid(struct account *, const char *); int imap_state_connect(struct account *, struct fetch_ctx *); int imap_state_connected(struct account *, struct fetch_ctx *); int imap_state_capability1(struct account *, struct fetch_ctx *); int imap_state_capability2(struct account *, struct fetch_ctx *); int imap_state_cram_md5_auth(struct account *, struct fetch_ctx *); int imap_state_login(struct account *, struct fetch_ctx *); int imap_state_user(struct account *, struct fetch_ctx *); int imap_state_pass(struct account *, struct fetch_ctx *); int imap_state_select1(struct account *, struct fetch_ctx *); int imap_state_select2(struct account *, struct fetch_ctx *); int imap_state_select3(struct account *, struct fetch_ctx *); int imap_state_select4(struct account *, struct fetch_ctx *); int imap_state_search1(struct account *, struct fetch_ctx *); int imap_state_search2(struct account *, struct fetch_ctx *); int imap_state_search3(struct account *, struct fetch_ctx *); int imap_state_next(struct account *, struct fetch_ctx *); int imap_state_uid1(struct account *, struct fetch_ctx *); int imap_state_uid2(struct account *, struct fetch_ctx *); int imap_state_body(struct account *, struct fetch_ctx *); int imap_state_line(struct account *, struct fetch_ctx *); int imap_state_mail(struct account *, struct fetch_ctx *); int imap_state_delete(struct account *, struct fetch_ctx *); int imap_state_expunge(struct account *, struct fetch_ctx *); int imap_state_quit1(struct account *, struct fetch_ctx *); int imap_state_quit2(struct account *, struct fetch_ctx *); #define IMAP_TAG_NONE -1 #define IMAP_TAG_CONTINUE -2 #define IMAP_TAG_ERROR -3 #define IMAP_TAGGED 0 #define IMAP_CONTINUE 1 #define IMAP_UNTAGGED 2 #define IMAP_RAW 3 #define IMAP_CAPA_AUTH_CRAM_MD5 0x1 /* Put line to server. */ int imap_putln(struct account *a, const char *fmt, ...) { struct fetch_imap_data *data = a->data; va_list ap; int n; va_start(ap, fmt); n = data->putln(a, fmt, ap); va_end(ap); return (n); } /* * Get line from server. Returns -1 on error, 0 on success, a NULL line when * out of data. */ int imap_getln(struct account *a, struct fetch_ctx *fctx, int type, char **line) { struct fetch_imap_data *data = a->data; int n; do { if (data->getln(a, fctx, line) != 0) return (-1); if (*line == NULL) return (0); } while ((n = imap_parse(a, type, *line)) == 1); return (n); } /* Free auxiliary data. */ void imap_free(void *ptr) { xfree(ptr); } /* Check for okay from server. */ int imap_okay(char *line) { char *ptr; ptr = strchr(line, ' '); if (ptr == NULL || strncmp(ptr + 1, "OK ", 3) != 0) return (0); return (1); } /* * Parse line based on type. Returns -1 on error, 0 on success, 1 to ignore * this line. */ int imap_parse(struct account *a, int type, char *line) { struct fetch_imap_data *data = a->data; int tag; if (type == IMAP_RAW) return (0); tag = imap_tag(line); switch (type) { case IMAP_TAGGED: if (tag == IMAP_TAG_NONE) return (1); if (tag == IMAP_TAG_CONTINUE) goto invalid; if (tag != data->tag) goto invalid; break; case IMAP_UNTAGGED: if (tag != IMAP_TAG_NONE) goto invalid; break; case IMAP_CONTINUE: if (tag == IMAP_TAG_NONE) return (1); if (tag != IMAP_TAG_CONTINUE) goto invalid; break; } return (0); invalid: imap_bad(a, line); return (-1); } /* Parse IMAP tag. */ int imap_tag(char *line) { int tag; const char *errstr; char *ptr; if (line[0] == '*' && line[1] == ' ') return (IMAP_TAG_NONE); if (line[0] == '+') return (IMAP_TAG_CONTINUE); if ((ptr = strchr(line, ' ')) == NULL) return (IMAP_TAG_ERROR); *ptr = '\0'; tag = strtonum(line, 0, INT_MAX, &errstr); *ptr = ' '; if (errstr != NULL) return (IMAP_TAG_ERROR); return (tag); } /* Base64 encode string. */ char * imap_base64_encode(char *in) { char *out; size_t size; size = (strlen(in) * 2) + 1; out = xcalloc(1, size); if (b64_ntop(in, strlen(in), out, size) < 0) { xfree(out); return (NULL); } return (out); } /* Base64 decode string. */ char * imap_base64_decode(char *in) { char *out; size_t size; size = (strlen(in) * 4) + 1; out = xcalloc(1, size); if (b64_pton(in, out, size) < 0) { xfree(out); return (NULL); } return (out); } int imap_bad(struct account *a, const char *line) { log_warnx("%s: unexpected data: %s", a->name, line); return (FETCH_ERROR); } int imap_invalid(struct account *a, const char *line) { log_warnx("%s: invalid response: %s", a->name, line); return (FETCH_ERROR); } /* Commit mail. */ int imap_commit(struct account *a, struct mail *m) { struct fetch_imap_data *data = a->data; struct fetch_imap_mail *aux = m->auxdata; if (m->decision == DECISION_DROP) { TAILQ_INSERT_TAIL(&data->dropped, aux, entry); } else { ARRAY_ADD(&data->kept, aux->uid); xfree(aux); data->committed++; } m->auxdata = m->auxfree = NULL; return (FETCH_AGAIN); } /* Abort fetch. */ void imap_abort(struct account *a) { struct fetch_imap_data *data = a->data; struct fetch_imap_mail *aux; while (!TAILQ_EMPTY(&data->dropped)) { aux = TAILQ_FIRST(&data->dropped); TAILQ_REMOVE(&data->dropped, aux, entry); imap_free(aux); } ARRAY_FREE(&data->kept); data->disconnect(a); } /* Return total mails available. */ u_int imap_total(struct account *a) { struct fetch_imap_data *data = a->data; return (data->total); } /* Common initialisatio state. */ int imap_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; TAILQ_INIT(&data->dropped); ARRAY_INIT(&data->kept); data->tag = 0; fctx->state = imap_state_connect; return (FETCH_AGAIN); } /* Connect state. */ int imap_state_connect(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; if (data->connect(a) != 0) return (FETCH_ERROR); fctx->state = imap_state_connected; return (FETCH_BLOCK); } /* 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 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; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); data->capa = 0; if (strstr(line, "AUTH=CRAM-MD5") != NULL) data->capa |= IMAP_CAPA_AUTH_CRAM_MD5; fctx->state = imap_state_capability2; 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->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); } if (imap_putln(a, "%u LOGIN {%zu}", ++data->tag, strlen(data->user)) != 0) return (FETCH_ERROR); fctx->state = imap_state_login; 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); } /* Login state. */ int imap_state_login(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 {%zu}", data->user, strlen(data->pass)) != 0) return (FETCH_ERROR); fctx->state = imap_state_user; 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); } /* Pass state. */ int imap_state_pass(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_select1; return (FETCH_AGAIN); } /* 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(data->folder)) != 0) return (FETCH_ERROR); fctx->state = imap_state_select2; 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", data->folder) != 0) return (FETCH_ERROR); fctx->state = imap_state_select3; return (FETCH_BLOCK); } /* 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->num) == 1) break; } data->cur = 0; /* Save total, if no previous total. */ if (data->total == 0) { data->total = data->num; /* * If not reconnecting and a subset of mail is required, * skip to search for the right flags. */ if (data->only != FETCH_ONLY_ALL) { fctx->state = imap_state_search1; return (FETCH_AGAIN); } } fctx->state = imap_state_select4; return (FETCH_AGAIN); } /* Select state 4. Hold until select completes then get next mail. */ 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 polling, stop here. */ if (fctx->flags & FETCH_POLL) { if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_quit1; return (FETCH_BLOCK); } fctx->state = imap_state_next; return (FETCH_AGAIN); } /* 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; 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)); /* Search for a list of the mail UIDs we want to ignore. */ if (data->only == FETCH_ONLY_NEW) { if (imap_putln(a, "%u UID SEARCH SEEN", ++data->tag) != 0) return (FETCH_ERROR); } else { if (imap_putln(a, "%u UID SEARCH UNSEEN", ++data->tag) != 0) return (FETCH_ERROR); } fctx->state = imap_state_search2; 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->kept, uid); log_debug3("%s: skipping UID: %u", a->name, uid); line = ptr; } while (*line == ' '); fctx->state = imap_state_search3; return (FETCH_AGAIN); } /* 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)); /* Adjust the total. */ data->total -= ARRAY_LENGTH(&data->kept); /* If no mails left, 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_quit1; return (FETCH_BLOCK); } fctx->state = imap_state_next; 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; struct fetch_imap_mail *aux; /* Handle dropped mail. */ if (!TAILQ_EMPTY(&data->dropped)) { aux = TAILQ_FIRST(&data->dropped); if (imap_putln(a, "%u STORE %u +FLAGS \\Deleted", ++data->tag, aux->idx) != 0) return (FETCH_ERROR); fctx->state = imap_state_delete; 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); } /* Move to the next mail if possible. */ if (data->cur <= data->num) data->cur++; /* If last mail, wait for everything to be committed then close down. */ if (data->cur > data->num) { if (data->committed != data->total) return (FETCH_BLOCK); if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_quit1; return (FETCH_BLOCK); } /* List the next mail. */ if (imap_putln(a, "%u FETCH %u UID", ++data->tag, data->cur) != 0) return (FETCH_ERROR); fctx->state = imap_state_uid1; return (FETCH_BLOCK); } /* UID state 1. */ int imap_state_uid1(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; 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 (UID %u)", &n, &data->uid) != 2) return (imap_invalid(a, line)); if (n != data->cur) return (imap_bad(a, line)); fctx->state = imap_state_uid2; return (FETCH_AGAIN); } /* UID state 2. */ int imap_state_uid2(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; u_int i; 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)); for (i = 0; i < ARRAY_LENGTH(&data->kept); i++) { if (ARRAY_ITEM(&data->kept, i) == data->uid) { /* Had this message before and kept, so skip. */ fctx->state = imap_state_next; return (FETCH_AGAIN); } } if (imap_putln(a, "%u FETCH %u BODY[]", ++data->tag, data->cur) != 0) return (FETCH_ERROR); fctx->state = imap_state_body; return (FETCH_BLOCK); } /* 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)); if (n != data->cur) return (imap_bad(a, line)); data->lines = 0; /* Fill in local data. */ aux = xcalloc(1, sizeof *aux); aux->idx = data->cur; aux->uid = data->uid; m->auxdata = aux; m->auxfree = imap_free; /* Open the mail. */ if (mail_open(m, data->size) != 0) { log_warn("%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", data->uid); add_tag(&m->tags, "folder", "%s", 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); } /* 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_warn("%s: failed to resize mail", a->name); return (FETCH_ERROR); } data->lines++; } /* * Calculate the number of bytes still needed. The current line must be * those bytes plus a trailing close bracket. */ left = data->size - used; if (size != left + 1) return (imap_invalid(a, line)); if (line[left] != ')' || line[left + 1] != '\0') 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_warn("%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); } /* Mail state. */ int imap_state_mail(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_MAIL); } /* Delete state. */ int imap_state_delete(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; struct fetch_imap_mail *aux; 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)); aux = TAILQ_FIRST(&data->dropped); TAILQ_REMOVE(&data->dropped, aux, entry); imap_free(aux); data->committed++; fctx->state = imap_state_next; return (FETCH_AGAIN); } /* 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_select1; return (FETCH_AGAIN); } /* Quit state 1. */ int imap_state_quit1(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 (imap_putln(a, "%u LOGOUT", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_quit2; return (FETCH_BLOCK); } /* Quit state 2. */ int imap_state_quit2(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); }