/* $Id: imap-common.c,v 1.66 2007/09/25 21:34:51 nicm Exp $ */
/*
* Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net>
*
* 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 <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <ctype.h>
#include <resolv.h>
#include <string.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#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);
}
syntax highlighted by Code2HTML, v. 0.9.1