/* $Id$ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/md5.h>
#include "fdm.h"
#include "fetch.h"
int pop3_putln(struct account *, const char *, ...);
int pop3_getln(struct account *, struct fetch_ctx *, char **);
int pop3_commit(struct account *, struct mail *);
void pop3_abort(struct account *);
u_int pop3_total(struct account *);
int pop3_load(struct account *);
int pop3_save(struct account *);
int pop3_cmp(struct fetch_pop3_mail *, struct fetch_pop3_mail *);
void pop3_free(void *);
int pop3_okay(const char *);
void pop3_freequeue(struct fetch_pop3_queue *);
void pop3_freetree(struct fetch_pop3_tree *);
int pop3_bad(struct account *, const char *);
int pop3_invalid(struct account *, const char *);
int pop3_state_connect(struct account *, struct fetch_ctx *);
int pop3_state_starttls(struct account *, struct fetch_ctx *);
int pop3_state_connected(struct account *, struct fetch_ctx *);
int pop3_state_user(struct account *, struct fetch_ctx *);
int pop3_state_cache1(struct account *, struct fetch_ctx *);
int pop3_state_cache2(struct account *, struct fetch_ctx *);
int pop3_state_cache3(struct account *, struct fetch_ctx *);
int pop3_state_stat(struct account *, struct fetch_ctx *);
int pop3_state_first(struct account *, struct fetch_ctx *);
int pop3_state_next(struct account *, struct fetch_ctx *);
int pop3_state_delete(struct account *, struct fetch_ctx *);
int pop3_state_reconnect(struct account *, struct fetch_ctx *);
int pop3_state_list(struct account *, struct fetch_ctx *);
int pop3_state_uidl(struct account *, struct fetch_ctx *);
int pop3_state_retr(struct account *, struct fetch_ctx *);
int pop3_state_line(struct account *, struct fetch_ctx *);
int pop3_state_quit(struct account *, struct fetch_ctx *);
RB_PROTOTYPE(fetch_pop3_tree, fetch_pop3_mail, tentry, pop3_cmp);
RB_GENERATE(fetch_pop3_tree, fetch_pop3_mail, tentry, pop3_cmp);
/* Put line to server. */
int
pop3_putln(struct account *a, const char *fmt, ...)
{
struct fetch_pop3_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, a NULL line if out of data. */
int
pop3_getln(struct account *a, struct fetch_ctx *fctx, char **line)
{
struct fetch_pop3_data *data = a->data;
if (data->getln(a, fctx, line) != 0)
return (-1);
return (0);
}
int
pop3_cmp(struct fetch_pop3_mail *aux1, struct fetch_pop3_mail *aux2)
{
return (strcmp(aux1->uid, aux2->uid));
}
/*
* Free a POP3 mail aux structure. All such structures are always on at least
* one queue or tree so this is always called explicitly rather than via the
* mail auxfree member.
*/
void
pop3_free(void *ptr)
{
struct fetch_pop3_mail *aux = ptr;
if (aux->uid != NULL)
xfree(aux->uid);
xfree(aux);
}
int
pop3_okay(const char *line)
{
return (strncmp(line, "+OK", 3) == 0);
}
void
pop3_freequeue(struct fetch_pop3_queue *q)
{
struct fetch_pop3_mail *aux;
while (!TAILQ_EMPTY(q)) {
aux = TAILQ_FIRST(q);
TAILQ_REMOVE(q, aux, qentry);
pop3_free(aux);
}
}
void
pop3_freetree(struct fetch_pop3_tree *t)
{
struct fetch_pop3_mail *aux;
while (!RB_EMPTY(t)) {
aux = RB_ROOT(t);
RB_REMOVE(fetch_pop3_tree, t, aux);
pop3_free(aux);
}
}
int
pop3_bad(struct account *a, const char *line)
{
log_warnx("%s: unexpected data: %s", a->name, line);
return (FETCH_ERROR);
}
int
pop3_invalid(struct account *a, const char *line)
{
log_warnx("%s: invalid response: %s", a->name, line);
return (FETCH_ERROR);
}
/* Load POP3 cache file into the cache queue. */
int
pop3_load(struct account *a)
{
struct fetch_pop3_data *data = a->data;
struct fetch_pop3_mail *aux;
int fd;
FILE *f = NULL;
char *uid;
size_t uidlen;
u_int n;
if (data->path == NULL)
return (0);
if ((fd = openlock(data->path, O_RDONLY, conf.lock_types)) == -1) {
if (errno == ENOENT)
return (0);
log_warn("%s: %s", a->name, data->path);
goto error;
}
if ((f = fdopen(fd, "r")) == NULL) {
log_warn("%s: %s", a->name, data->path);
goto error;
}
n = 0;
for (;;) {
if (fscanf(f, "%zu ", &uidlen) != 1) {
/* EOF is allowed only at the start of a line. */
if (feof(f))
break;
goto invalid;
}
uid = xmalloc(uidlen + 1);
if (fread(uid, uidlen, 1, f) != 1)
goto invalid;
uid[uidlen] = '\0';
log_debug3("%s: found UID in cache: %s", a->name, uid);
aux = xcalloc(1, sizeof *aux);
aux->uid = uid;
RB_INSERT(fetch_pop3_tree, &data->cacheq, aux);
}
fclose(f);
closelock(fd, data->path, conf.lock_types);
return (0);
invalid:
log_warnx("%s: invalid cache entry", a->name);
error:
if (f != NULL)
fclose(f);
if (fd != -1)
closelock(fd, data->path, conf.lock_types);
return (-1);
}
/* Save POP3 cache file. */
int
pop3_save(struct account *a)
{
struct fetch_pop3_data *data = a->data;
struct fetch_pop3_mail *aux;
char *path = NULL, tmp[MAXPATHLEN];
int fd = -1;
FILE *f = NULL;
u_int n;
if (data->path == NULL)
return (0);
if (ppath(tmp, sizeof tmp, "%s.XXXXXXXXXX", data->path) != 0)
goto error;
if ((fd = mkstemp(tmp)) == -1)
goto error;
path = tmp;
cleanup_register(path);
if ((f = fdopen(fd, "r+")) == NULL)
goto error;
fd = -1;
n = 0;
RB_FOREACH(aux, fetch_pop3_tree, &data->cacheq) {
fprintf(f, "%zu %s\n", strlen(aux->uid), aux->uid);
n++;
}
log_debug2("%s: saved cache %s: %u entries", a->name, data->path, n);
if (fflush(f) != 0)
goto error;
if (fsync(fileno(f)) != 0)
goto error;
fclose(f);
f = NULL;
if (rename(path, data->path) == -1)
goto error;
cleanup_deregister(path);
return (0);
error:
log_warn("%s: %s", a->name, data->path);
if (f != NULL)
fclose(f);
if (fd != -1)
close(fd);
if (path != NULL) {
if (unlink(tmp) != 0)
fatal("unlink failed");
cleanup_deregister(path);
}
return (-1);
}
/* Commit mail. */
int
pop3_commit(struct account *a, struct mail *m)
{
struct fetch_pop3_data *data = a->data;
struct fetch_pop3_mail *aux = m->auxdata;
if (m->decision == DECISION_DROP) {
/* Insert to tail of the drop queue; reading is from head. */
TAILQ_INSERT_TAIL(&data->dropq, aux, qentry);
m->auxdata = NULL;
} else {
/* If not already in the cache, add it. */
if (RB_FIND(fetch_pop3_tree, &data->cacheq, aux) == NULL)
RB_INSERT(fetch_pop3_tree, &data->cacheq, aux);
else
pop3_free(aux);
m->auxdata = NULL;
data->committed++;
if (data->only != FETCH_ONLY_OLD && pop3_save(a) != 0)
return (FETCH_ERROR);
}
return (FETCH_AGAIN);
}
/* Close and free everything. Used for abort and after quit. */
void
pop3_abort(struct account *a)
{
struct fetch_pop3_data *data = a->data;
pop3_freetree(&data->serverq);
pop3_freetree(&data->cacheq);
pop3_freequeue(&data->wantq);
pop3_freequeue(&data->dropq);
data->disconnect(a);
}
/* Return total mails. */
u_int
pop3_total(struct account *a)
{
struct fetch_pop3_data *data = a->data;
return (data->total);
}
/*
* Initial state. This is separate from connect as it is necessary to reconnect
* without wiping the dropped/kept list and counters.
*/
int
pop3_state_init(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
RB_INIT(&data->serverq);
RB_INIT(&data->cacheq);
TAILQ_INIT(&data->wantq);
TAILQ_INIT(&data->dropq);
data->total = data->committed = 0;
fctx->state = pop3_state_connect;
return (FETCH_AGAIN);
}
/* Connect state. */
int
pop3_state_connect(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
if (data->connect(a) != 0)
return (FETCH_ERROR);
if (data->starttls)
fctx->state = pop3_state_starttls;
else
fctx->state = pop3_state_connected;
return (FETCH_BLOCK);
}
/* STARTTLS state. */
int
pop3_state_starttls(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
if (pop3_putln(a, "STLS") != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_connected;
return (FETCH_BLOCK);
}
/* Connected state: wait for initial +OK line from server. */
int
pop3_state_connected(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
char *line, *ptr, *src, *cause;
char out[MD5_DIGEST_LENGTH * 2 + 1];
u_char digest[MD5_DIGEST_LENGTH];
u_int i;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
if (data->starttls) {
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);
}
}
if (data->apop && (line = strchr(line, '<')) != NULL) {
if ((ptr = strchr(line + 1, '>')) != NULL) {
*++ptr = '\0';
xasprintf(&src, "%s%s", line, data->pass);
MD5(src, strlen(src), digest);
xfree(src);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
xsnprintf(out + i * 2, 3, "%02hhx", digest[i]);
if (pop3_putln(a, "APOP %s %s", data->user, out) != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_stat;
return (FETCH_BLOCK);
}
}
if (pop3_putln(a, "USER %s", data->user) != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_user;
return (FETCH_BLOCK);
}
/* User state. */
int
pop3_state_user(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
if (pop3_putln(a, "PASS %s", data->pass) != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_stat;
return (FETCH_BLOCK);
}
/* Stat state. */
int
pop3_state_stat(struct account *a, struct fetch_ctx *fctx)
{
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
if (pop3_putln(a, "STAT") != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_first;
return (FETCH_BLOCK);
}
/* First state. Wait for +OK then switch to get first mail. */
int
pop3_state_first(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct fetch_pop3_mail *aux;
char *line;
u_int n;
u_int i;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
if (sscanf(line, "+OK %u %u", &data->num, &n) != 2)
return (pop3_invalid(a, line));
data->cur = 0;
/*
* If no mail, we can skip UIDL and either quit (if polling or not
* reconnecting) or skip to wait in next state.
*/
if (data->num == 0) {
if (data->total != 0) {
fctx->state = pop3_state_next;
return (FETCH_AGAIN);
}
if (pop3_putln(a, "QUIT") != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_quit;
return (FETCH_BLOCK);
}
if (!data->uidl) {
/*
* Broken pop3, directly create wantq instead of using UIDL
* result.
*/
for (i = 1; i <= data->num; i++) {
aux = xcalloc(1, sizeof *aux);
aux->idx = i;
aux->uid = xstrdup("");
TAILQ_INSERT_TAIL(&data->wantq, aux, qentry);
data->total++;
}
fctx->state = pop3_state_next;
return (FETCH_AGAIN);
}
if (pop3_putln(a, "UIDL") != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_cache1;
return (FETCH_BLOCK);
}
/* Cache state 1. */
int
pop3_state_cache1(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
/* Free the server queue. */
pop3_freetree(&data->serverq);
fctx->state = pop3_state_cache2;
return (FETCH_AGAIN);
}
/* Cache state 2. */
int
pop3_state_cache2(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct fetch_pop3_mail *aux;
char *line, *ptr;
u_int n;
/* Parse response and add to server queue. */
while (data->cur != data->num) {
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (sscanf(line, "%u %*s", &n) != 1)
return (pop3_invalid(a, line));
if (n != data->cur + 1)
return (pop3_bad(a, line));
line = strchr(line, ' ') + 1;
/*
* Check UID validity. We are intolerant about validity since
* accepting bad UIDs could potentially end up with UIDs that
* conflict.
*/
if (*line == '\0') {
log_warnx("%s: empty UID", a->name);
return (FETCH_ERROR);
}
for (ptr = line; *ptr != '\0'; ptr++) {
if (*ptr < 0x21 || *ptr > 0x7e) {
log_warnx("%s: invalid UID: %s", a->name, line);
return (FETCH_ERROR);
}
}
if (ptr > line + 70) {
log_warnx("%s: UID too big: %s", a->name, line);
return (FETCH_ERROR);
}
aux = xcalloc(1, sizeof *aux);
aux->idx = n;
aux->uid = xstrdup(line);
/*
* If this is already in the queue, the mailbox has multiple
* identical messages. This is one of the more stupid aspects
* of the POP3 protocol (a unique id that isn't unique? great!).
* At the moment we just abort with an error.
* XXX what can we do to about this?
*/
if (RB_FIND(fetch_pop3_tree, &data->serverq, aux)) {
xfree(aux);
log_warnx("%s: UID collision: %s", a->name, line);
return (FETCH_ERROR);
}
RB_INSERT(fetch_pop3_tree, &data->serverq, aux);
data->cur++;
}
fctx->state = pop3_state_cache3;
return (FETCH_AGAIN);
}
/* Cache state 3. */
int
pop3_state_cache3(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct fetch_pop3_mail *aux1, *aux2, *aux3, *before;
char *line;
u_int n;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (line[0] != '.' && line[1] != '\0')
return (pop3_bad(a, line));
/*
* Resolve the caches.
*
* At this point: serverq holds a list of all mails on the server and
* their indexes; cacheq holds a list of all mails in the cache file; if
* connecting for the first time, wantq is empty, otherwise it has the
* list of mails we want.
*/
n = 0;
if (data->total == 0) {
/*
* If not reconnecting, build the wantq list based on the
* serverq and the cache.
*/
/* Load the cache and weed out any mail that doesn't exist. */
if (pop3_load(a) != 0)
return (FETCH_ERROR);
aux1 = RB_MIN(fetch_pop3_tree, &data->cacheq);
while (aux1 != NULL) {
aux2 = aux1;
aux1 = RB_NEXT(fetch_pop3_tree, &data->cacheq, aux1);
if (RB_FIND(
fetch_pop3_tree, &data->serverq, aux2) != NULL)
continue;
RB_REMOVE(fetch_pop3_tree, &data->cacheq, aux2);
pop3_free(aux2);
}
/* Build the want queue from the server queue. */
RB_FOREACH(aux1, fetch_pop3_tree, &data->serverq) {
switch (data->only) {
case FETCH_ONLY_ALL:
/* Get all mails. */
break;
case FETCH_ONLY_NEW:
/* Get only mails not in the cache. */
if (RB_FIND(fetch_pop3_tree,
&data->cacheq, aux1) != NULL)
continue;
break;
case FETCH_ONLY_OLD:
/* Get only mails in the cache. */
if (RB_FIND(fetch_pop3_tree,
&data->cacheq, aux1) == NULL)
continue;
break;
}
/*
* Copy the mail to the want queue. Keep the want
* queue sorted by UIDL order from the server.
*/
aux2 = xcalloc(1, sizeof *aux2);
aux2->idx = aux1->idx;
aux2->uid = xstrdup(aux1->uid);
/* Find the first item higher than this one. */
before = NULL;
TAILQ_FOREACH(aux3, &data->wantq, qentry) {
if (aux3->idx > aux2->idx) {
before = aux3;
break;
}
}
if (before != NULL)
TAILQ_INSERT_BEFORE(before, aux2, qentry);
else
TAILQ_INSERT_TAIL(&data->wantq, aux2, qentry);
data->total++;
}
/*
* If there are no actual mails to fetch now, or if polling,
* stop.
*/
if (data->total == 0 || fctx->flags & FETCH_POLL) {
if (pop3_putln(a, "QUIT") != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_quit;
return (FETCH_BLOCK);
}
} else {
/*
* Reconnecting. The want queue already exists but the
* indexes need to be updated from the server queue.
*/
aux1 = TAILQ_FIRST(&data->wantq);
while (aux1 != NULL) {
aux2 = aux1;
aux1 = TAILQ_NEXT(aux1, qentry);
/*
* Check the server queue. Mails now not on the server
* are removed.
*/
aux3 = RB_FIND(
fetch_pop3_tree, &data->serverq, aux2);
if (aux3 == NULL) {
TAILQ_REMOVE(&data->wantq, aux2, qentry);
pop3_free(aux2);
data->total--;
} else
aux2->idx = aux3->idx;
}
}
fctx->state = pop3_state_next;
return (FETCH_AGAIN);
}
/* Next state. */
int
pop3_state_next(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct mail *m = fctx->mail;
struct fetch_pop3_mail *aux;
/* Handle dropped mail here. */
if (!TAILQ_EMPTY(&data->dropq)) {
aux = TAILQ_FIRST(&data->dropq);
if (pop3_putln(a, "DELE %u", aux->idx) != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_delete;
return (FETCH_BLOCK);
}
/*
* If no more mail, wait until everything has been committed, then
* quit.
*/
if (TAILQ_EMPTY(&data->wantq)) {
if (data->committed != data->total)
return (FETCH_BLOCK);
if (pop3_putln(a, "QUIT") != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_quit;
return (FETCH_BLOCK);
}
/*
* Try to purge if requested. This must be after dropped mail is
* flushed otherwise it might use the wrong indexes after reconnect.
*/
if (fctx->flags & FETCH_PURGE) {
/*
* If can't purge now, loop through this state not fetching
* mail until we can: there is no mail on the dropped queue,
* and FETCH_EMPTY is set. Used to have a seperate state to
* loop through without returning here, but that is wrong:
* mail could potentially be added to the dropped list while
* in that state.
*/
if (fctx->flags & FETCH_EMPTY) {
fctx->flags &= ~FETCH_PURGE;
if (pop3_putln(a, "QUIT") != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_reconnect;
return (FETCH_BLOCK);
}
/*
* Must be waiting for delivery, so permit blocking even though
* we (fetch) aren't waiting for any data.
*/
return (FETCH_BLOCK);
}
/* Find the next mail. */
aux = TAILQ_FIRST(&data->wantq);
m->auxdata = aux;
/* And list it. */
if (pop3_putln(a, "LIST %u", aux->idx) != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_list;
return (FETCH_BLOCK);
}
/* Delete state. */
int
pop3_state_delete(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct fetch_pop3_mail *aux;
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
aux = TAILQ_FIRST(&data->dropq);
/* Remove from the drop queue. */
TAILQ_REMOVE(&data->dropq, aux, qentry);
/* If not already in the cache, add it. */
if (RB_FIND(fetch_pop3_tree, &data->cacheq, aux) == NULL)
RB_INSERT(fetch_pop3_tree, &data->cacheq, aux);
else
pop3_free(aux);
/* Update counter and save the cache. */
data->committed++;
if (data->only != FETCH_ONLY_OLD && pop3_save(a) != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_next;
return (FETCH_AGAIN);
}
/* Reconnect state. */
int
pop3_state_reconnect(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
data->disconnect(a);
fctx->state = pop3_state_connect;
return (FETCH_AGAIN);
}
/* List state. */
int
pop3_state_list(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct mail *m = fctx->mail;
struct fetch_pop3_mail *aux = m->auxdata;
char *line;
u_int n;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
if (sscanf(line, "+OK %u %zu", &n, &data->size) != 2)
return (pop3_invalid(a, line));
if (n != aux->idx)
return (pop3_bad(a, line));
if (pop3_putln(a, "RETR %u", aux->idx) != 0)
return (FETCH_ERROR);
fctx->state = pop3_state_retr;
return (FETCH_BLOCK);
}
/* Retr state. */
int
pop3_state_retr(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct mail *m = fctx->mail;
struct fetch_pop3_mail *aux = m->auxdata;
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
/* 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", "%s", aux->uid);
data->flushing = 0;
fctx->state = pop3_state_line;
return (FETCH_AGAIN);
}
/* Line state. */
int
pop3_state_line(struct account *a, struct fetch_ctx *fctx)
{
struct fetch_pop3_data *data = a->data;
struct mail *m = fctx->mail;
struct fetch_pop3_mail *aux = m->auxdata;
char *line;
for (;;) {
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (line[0] == '.') {
if (line[1] == '\0')
break;
line++;
}
if (data->flushing)
continue;
if (append_line(m, line, strlen(line)) != 0) {
log_warn("%s: failed to resize mail", a->name);
return (FETCH_ERROR);
}
if (m->size > conf.max_size)
data->flushing = 1;
}
/* Pull from the want queue. */
TAILQ_REMOVE(&data->wantq, aux, qentry);
fctx->state = pop3_state_next;
return (FETCH_MAIL);
}
/* Quit state. */
int
pop3_state_quit(struct account *a, struct fetch_ctx *fctx)
{
char *line;
if (pop3_getln(a, fctx, &line) != 0)
return (FETCH_ERROR);
if (line == NULL)
return (FETCH_BLOCK);
if (!pop3_okay(line))
return (pop3_bad(a, line));
pop3_abort(a);
return (FETCH_EXIT);
}