X-Git-Url: http://git.madism.org/?p=apps%2Fmadmutt.git;a=blobdiff_plain;f=pop.c;h=a066f6e5dd05562de916dd3f760a506e089e4613;hp=cb35c70f09cfdff496ab87c92268e45ff3ee5140;hb=00f34116b32751764d42f81159c292d850c74bac;hpb=df70e07e24add1869bcc9b7af2277d9d0c09a281 diff --git a/pop.c b/pop.c index cb35c70..a066f6e 100644 --- a/pop.c +++ b/pop.c @@ -1,32 +1,815 @@ /* + * Copyright notice from original mutt: * Copyright (C) 2000-2002 Vsevolod Volkov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + * + * This file is part of mutt-ng, see http://www.muttng.org/. + * It's licensed under the GNU General Public License, + * please see the file GPL in the top level source directory. */ -#if HAVE_CONFIG_H -# include "config.h" -#endif +#include +#include +#include + +#include +#include + +#include "crypt.h" #include "mutt.h" -#include "mx.h" +#include "mutt_sasl.h" #include "pop.h" -#include "mutt_crypt.h" -#include -#include +#define POP_PORT 110 +#define POP_SSL_PORT 995 +#define POP_CACHE_LEN 10 +/* maximal length of the server response (RFC1939) */ +#define POP_CMD_RESPONSE 512 + +enum { + /* Status */ + POP_NONE = 0, + POP_CONNECTED, + POP_DISCONNECTED, + POP_BYE +}; + +typedef enum { + POP_A_SUCCESS = 0, + POP_A_SOCKET, + POP_A_FAILURE, + POP_A_UNAVAIL +} pop_auth_res_t; + +typedef struct { + int index; + char *path; +} POP_CACHE; + +typedef enum { + /* pop_fetch_data uses pop_query_status and this return value */ + PFD_FUNCT_ERROR = -3, + PQ_ERR = -2, + PQ_NOT_CONNECTED = -1, + PQ_OK = 0 +} pop_query_status; + +typedef enum { + CMD_NOT_AVAILABLE = 0, + CMD_AVAILABLE, + CMD_UNKNOWN /* unknown whether it is available or not */ +} cmd_status; + +typedef struct { + CONNECTION *conn; + + unsigned status : 2; + unsigned capabilities : 1; + unsigned use_stls : 2; + cmd_status cmd_capa : 2; /* optional command CAPA */ + cmd_status cmd_stls : 2; /* optional command STLS */ + cmd_status cmd_uidl : 2; /* optional command UIDL */ + cmd_status cmd_top : 2; /* optional command TOP */ + cmd_status cmd_user : 2; /* optional command USER */ + unsigned resp_codes : 1; /* server supports extended response codes */ + unsigned expire : 1; /* expire is greater than 0 */ + unsigned clear_cache : 1; + + ssize_t size; + time_t check_time; + char *auth_list; /* list of auth mechanisms */ + char *timestamp; + char err_msg[POP_CMD_RESPONSE]; + POP_CACHE cache[POP_CACHE_LEN]; +} pop_data_t; + +/* pop low level functions {{{ */ + +static void pop_error(pop_data_t *pop_data, const char *msg) +{ + if (!m_strncmp(msg, "-ERR ", 5)) { + const char *s = skipspaces(msg + 5); + if (*s) + msg = s; + } + + m_strcat(pop_data->err_msg, sizeof(pop_data->err_msg), msg); + m_strrtrim(pop_data->err_msg); +} + +/* + * Send data from buffer and receive answer to the same buffer + * 0 - successful, + * -1 - conection lost, + * -2 - invalid command or execution error. +*/ +static pop_query_status _pop_query(pop_data_t *pop_data, char *buf, ssize_t buflen) +{ + if (pop_data->status != POP_CONNECTED) + return PQ_NOT_CONNECTED; + + mutt_socket_write(pop_data->conn, buf); + snprintf(pop_data->err_msg, sizeof(pop_data->err_msg), "%.*s: ", + (int)(strpbrk(buf, " \r\n") - buf), buf); + + if (mutt_socket_readln(buf, buflen, pop_data->conn) < 0) { + pop_data->status = POP_DISCONNECTED; + return PQ_NOT_CONNECTED; + } + if (!m_strncmp(buf, "+OK", 3)) + return PQ_OK; + + pop_error(pop_data, buf); + return PQ_ERR; +} +#define pop_query(pd, b, l, fmt, ...) \ + (snprintf(b, l, fmt "\r\n", ##__VA_ARGS__), _pop_query(pd, b, l)) + +/* + * Open connection + * 0 - successful, + * -1 - conection lost, + * -2 - invalid response. +*/ +static pop_query_status pop_connect(pop_data_t * pop_data) +{ + char buf[LONG_STRING]; + const char *p, *q; + + if (mutt_socket_open(pop_data->conn) < 0 + || mutt_socket_readln(buf, sizeof(buf), pop_data->conn) < 0) + { + mutt_error(_("Error connecting to server: %s"), + pop_data->conn->account.host); + pop_data->status = POP_NONE; + return PQ_NOT_CONNECTED; + } + + pop_data->status = POP_CONNECTED; + if (m_strncmp(buf, "+OK", 3)) { + pop_data->err_msg[0] = '\0'; + pop_error(pop_data, buf); + mutt_error("%s", pop_data->err_msg); + return PQ_ERR; + } + + p_delete(&pop_data->timestamp); + if ((p = strchr(buf, '<')) && (q = strchr(p, '>'))) { + pop_data->timestamp = p_dupstr(p, q + 1 - p); + } + return PQ_OK; +} + +static void pop_logout (CONTEXT * ctx) +{ + pop_query_status ret = 0; + char buf[LONG_STRING]; + pop_data_t *pop_data = (pop_data_t *) ctx->data; + + if (pop_data->status == POP_CONNECTED) { + mutt_message _("Closing connection to POP server..."); + + if (ctx->readonly) { + ret = pop_query(pop_data, buf, sizeof(buf), "RSET"); + } + + if (ret != PQ_NOT_CONNECTED) { + pop_query(pop_data, buf, sizeof(buf), "QUIT"); + } + + mutt_clear_error (); + } + + pop_data->status = POP_DISCONNECTED; + return; +} + +/* }}} */ +/* Authentication {{{ */ + +static pop_auth_res_t pop_auth_sasl(pop_data_t *pop_data, const char *method) +{ + sasl_conn_t *saslconn; + sasl_interact_t *interaction = NULL; + + char buf[LONG_STRING], inbuf[LONG_STRING]; + const char *mech, *pc = NULL; + unsigned int len, olen; + unsigned char client_start; + int rc; + + if (mutt_sasl_client_new(pop_data->conn, &saslconn) < 0) { + return POP_A_FAILURE; + } + + if (!method) + method = pop_data->auth_list; + + for (;;) { + rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, + &mech); + if (rc != SASL_INTERACT) + break; + mutt_sasl_interact(interaction); + } + + if (rc != SASL_OK && rc != SASL_CONTINUE) { + return POP_A_UNAVAIL; + } + + client_start = (olen > 0); + mutt_message(_("Authenticating (SASL)...")); + snprintf(buf, sizeof (buf), "AUTH %s", mech); + olen = strlen (buf); + + /* looping protocol */ + for (;;) { + m_strcpy(buf + olen, sizeof(buf) - olen, "\r\n"); + mutt_socket_write(pop_data->conn, buf); + if (mutt_socket_readln(inbuf, sizeof(inbuf), pop_data->conn) < 0) { + sasl_dispose(&saslconn); + pop_data->status = POP_DISCONNECTED; + return POP_A_SOCKET; + } + + if (rc != SASL_CONTINUE) + break; + + if (!m_strncmp(inbuf, "+ ", 2) + && sasl_decode64(inbuf, strlen(inbuf), + buf, sizeof(buf) - 1, &len) != SASL_OK) + { + goto bail; + } + + if (!client_start) { + for (;;) { + rc = sasl_client_step(saslconn, buf, len, &interaction, &pc, + &olen); + if (rc != SASL_INTERACT) + break; + mutt_sasl_interact(interaction); + } + } else { + client_start = 0; + } + + if (rc != SASL_CONTINUE && (olen == 0 || rc != SASL_OK)) + break; + + /* send out response, or line break if none needed */ + if (pc) { + if (sasl_encode64(pc, olen, buf, sizeof(buf), &olen) != SASL_OK) { + goto bail; + } + p_delete((char **)&pc); + } + } + + if (rc != SASL_OK) + goto bail; + + if (!m_strncmp(inbuf, "+OK", 3)) { + mutt_sasl_setup_conn(pop_data->conn, saslconn); + return POP_A_SUCCESS; + } + + bail: + sasl_dispose(&saslconn); + p_delete((char **)&pc); + + /* terminate SASL session if the last responce is not +OK nor -ERR */ + if (!m_strncmp(inbuf, "+ ", 2)) { + if (pop_query(pop_data, buf, sizeof(buf), "*") == PQ_NOT_CONNECTED) + return POP_A_SOCKET; + } + + mutt_error(_("SASL authentication failed.")); + mutt_sleep(2); + + return POP_A_FAILURE; +} + +static pop_auth_res_t pop_auth_apop(pop_data_t *pop_data, const char *method) +{ + MD5_CTX mdContext; + unsigned char digest[16]; + char hash[33]; + char buf[LONG_STRING]; + int i; + + if (!pop_data->timestamp) + return POP_A_UNAVAIL; + + mutt_message _("Authenticating (APOP)..."); + + /* Compute the authentication hash to send to the server */ + MD5Init(&mdContext); + MD5Update(&mdContext, (unsigned char *)pop_data->timestamp, + strlen(pop_data->timestamp)); + MD5Update(&mdContext, (unsigned char *)pop_data->conn->account.pass, + strlen(pop_data->conn->account.pass)); + MD5Final(digest, &mdContext); + + for (i = 0; i < countof(digest); i++) + sprintf(hash + 2 * i, "%02x", digest[i]); + + switch (pop_query(pop_data, buf, sizeof(buf), "APOP %s %s", + pop_data->conn->account.user, hash)) + { + case PQ_OK: + return POP_A_SUCCESS; + case PQ_NOT_CONNECTED: + return POP_A_SOCKET; + default: + mutt_error("%s %s", _("APOP authentication failed."), pop_data->err_msg); + mutt_sleep(2); + return POP_A_FAILURE; + } +} + +static pop_auth_res_t pop_auth_user(pop_data_t *pop_data, const char *method) +{ + char buf[LONG_STRING]; + pop_query_status ret; + + if (pop_data->cmd_user == CMD_NOT_AVAILABLE) + return POP_A_UNAVAIL; + + mutt_message _("Authenticating (USER)..."); + ret = pop_query(pop_data, buf, sizeof(buf), "USER %s", + pop_data->conn->account.user); + + if (pop_data->cmd_user == CMD_UNKNOWN) { + if (ret == PQ_OK) + pop_data->cmd_user = CMD_AVAILABLE; + if (ret == PQ_ERR) + pop_data->cmd_user = CMD_NOT_AVAILABLE; + } + + if (ret == PQ_OK) { + ret = pop_query(pop_data, buf, sizeof(buf), "PASS %s", + pop_data->conn->account.pass); + } + + switch (ret) { + case PQ_OK: + return POP_A_SUCCESS; + case PQ_NOT_CONNECTED: + return POP_A_SOCKET; + default: + mutt_error("%s %s", _("USER authentication failed."), pop_data->err_msg); + mutt_sleep(2); + return POP_A_FAILURE; + } +} + +typedef struct { + pop_auth_res_t (*do_auth)(pop_data_t *, const char *); + const char *method; +} pop_auth_t; + +static pop_query_status pop_authenticate (pop_data_t * pop_data) +{ + static pop_auth_t const pop_authenticators[] = { + {pop_auth_sasl, NULL}, + {pop_auth_apop, "apop"}, + {pop_auth_user, "user"}, + {NULL, NULL} + }; + + ACCOUNT *act = &pop_data->conn->account; + const pop_auth_t *auth; + int attempts = 0; + + if (mutt_account_getuser(act) || !act->user[0] + || mutt_account_getpass(act) || !act->pass[0]) + { + return PFD_FUNCT_ERROR; + } + + if (!m_strisempty(PopAuthenticators)) { + const char *p, *q; + char buf[STRING]; + + for (p = PopAuthenticators; ; p = q) { + while (*p == ':') + p++; + if (!*p) + break; + + q = strchrnul(p, ':'); + m_strncpy(buf, sizeof(buf), p, q - p); + + for (auth = pop_authenticators; auth->do_auth; auth++) { + if (auth->method && ascii_strcasecmp(auth->method, buf)) + continue; + + switch (auth->do_auth(pop_data, buf)) { + case POP_A_SUCCESS: + return PQ_OK; + case POP_A_SOCKET: + return PQ_NOT_CONNECTED; + case POP_A_UNAVAIL: + break; + case POP_A_FAILURE: + attempts++; + break; + } + mutt_socket_close(pop_data->conn); + } + } + } else { + for (auth = pop_authenticators; auth->do_auth; auth++) { + switch (auth->do_auth(pop_data, auth->method)) { + case POP_A_SUCCESS: + return PQ_OK; + case POP_A_SOCKET: + return PQ_NOT_CONNECTED; + case POP_A_UNAVAIL: + break; + case POP_A_FAILURE: + attempts++; + break; + } + mutt_socket_close(pop_data->conn); + } + } + + if (!attempts) + mutt_error(_("No authenticators available")); + return PQ_ERR; +} + +/* }}} */ + +/* + * This function calls funct(*line, *data) for each received line, + * funct(NULL, *data) if rewind(*data) needs, exits when fail or done. + * Returned codes: + * 0 - successful, + * -1 - conection lost, + * -2 - invalid command or execution error, + * -3 - error in funct(*line, *data) + */ +static pop_query_status +pop_fetch_data(pop_data_t *pop_data, const char *query, progress_t *bar, + int (*funct)(char *, void *), void *data) +{ + pop_query_status ret; + char buf[LONG_STRING]; + buffer_t inbuf; + ssize_t pos = 0; + + buffer_init(&inbuf); + + m_strcpy(buf, sizeof(buf), query); + ret = _pop_query(pop_data, buf, sizeof(buf)); + if (ret != PQ_OK) + return ret; + + for (;;) { + int dot = 0; + + if (mutt_socket_readln2(&inbuf, pop_data->conn) < 0) { + pop_data->status = POP_DISCONNECTED; + ret = PQ_NOT_CONNECTED; + break; + } + + if (bar) { + mutt_progress_bar(bar, pos += inbuf.len); + } + + if (inbuf.data[0] == '.') { + if (inbuf.data[1] != '.') + break; + dot = 1; + } + + if (funct(inbuf.data + dot, data) < 0) { + buffer_wipe(&inbuf); + ret = PFD_FUNCT_ERROR; + break; + } + + buffer_reset(&inbuf); + } + + buffer_wipe(&inbuf); + return ret; +} + +static int fetch_capa (char *line, void *data) +{ + pop_data_t *pop_data = (pop_data_t *) data; + char *c; + + if (!ascii_strncasecmp (line, "SASL", 4)) { + p_delete(&pop_data->auth_list); + c = vskipspaces(line + 4); + pop_data->auth_list = m_strdup(c); + } + + else if (!ascii_strncasecmp (line, "STLS", 4)) + pop_data->cmd_stls = CMD_AVAILABLE; + + else if (!ascii_strncasecmp (line, "UIDL", 4)) + pop_data->cmd_uidl = CMD_AVAILABLE; + + else if (!ascii_strncasecmp (line, "TOP", 3)) + pop_data->cmd_top = CMD_AVAILABLE; + + return 0; +} + +static int fetch_auth (char *line, void *data) +{ + pop_data_t *pop_data = (pop_data_t *) data; + ssize_t auth_list_len; + + if (!pop_data->auth_list) { + auth_list_len = m_strlen(line) + 1; + pop_data->auth_list = p_new(char, auth_list_len); + } else { + auth_list_len = m_strlen(pop_data->auth_list) + m_strlen(line) + 2; + p_realloc(&pop_data->auth_list, auth_list_len); + m_strcat(pop_data->auth_list, auth_list_len, " "); + } + m_strcat(pop_data->auth_list, auth_list_len, line); + + return 0; +} + +/* + * Get capabilities + * 0 - successful, + * -1 - conection lost, + * -2 - execution error. +*/ +static pop_query_status pop_capabilities(pop_data_t * pop_data, int mode) +{ + /* don't check capabilities on reconnect */ + if (pop_data->capabilities) + return 0; + + /* init capabilities */ + if (mode == 0) { + pop_data->cmd_capa = CMD_NOT_AVAILABLE; + pop_data->cmd_stls = CMD_NOT_AVAILABLE; + pop_data->cmd_uidl = CMD_NOT_AVAILABLE; + pop_data->cmd_top = CMD_NOT_AVAILABLE; + pop_data->cmd_user = CMD_NOT_AVAILABLE; + pop_data->resp_codes = 0; + pop_data->expire = 1; + p_delete(&pop_data->auth_list); + } + + /* Execute CAPA command */ + if (mode == 0 || pop_data->cmd_capa != CMD_NOT_AVAILABLE) { + switch (pop_fetch_data(pop_data, "CAPA\r\n", NULL, fetch_capa, pop_data)) { + case PQ_OK: + pop_data->cmd_capa = CMD_AVAILABLE; + break; + case PFD_FUNCT_ERROR: + case PQ_ERR: + pop_data->cmd_capa = CMD_NOT_AVAILABLE; + break; + case PQ_NOT_CONNECTED: + return PQ_NOT_CONNECTED; + } + } + + /* CAPA not supported, use defaults */ + if (mode == 0 && pop_data->cmd_capa == CMD_NOT_AVAILABLE) { + pop_data->cmd_uidl = CMD_UNKNOWN; + pop_data->cmd_top = CMD_UNKNOWN; + + if (pop_fetch_data(pop_data, "AUTH\r\n", NULL, fetch_auth, pop_data) == + PQ_NOT_CONNECTED) + return PQ_NOT_CONNECTED; + } + + /* Check capabilities */ + if (mode == 2) { + const char *msg = NULL; + + if (!pop_data->expire) + msg = _("Unable to leave messages on server."); + if (pop_data->cmd_top == CMD_NOT_AVAILABLE) + msg = _("Command TOP is not supported by server."); + if (pop_data->cmd_uidl == CMD_NOT_AVAILABLE) + msg = _("Command UIDL is not supported by server."); + if (msg && pop_data->cmd_capa != CMD_AVAILABLE) { + mutt_error (msg); + return PQ_ERR; + } + pop_data->capabilities = 1; + } + + return PQ_OK; +} + +/* given an POP mailbox name, return host, port, username and password */ +static int pop_parse_path (const char *path, ACCOUNT * act) +{ + ciss_url_t url; + char *c; + int ret = -1; + + /* Defaults */ + act->flags = 0; + act->port = POP_PORT; + act->type = M_ACCT_TYPE_POP; + + c = m_strdup(path); + url_parse_ciss (&url, c); + + if (url.scheme == U_POP || url.scheme == U_POPS) { + if (url.scheme == U_POPS) { + act->has_ssl = 1; + act->port = POP_SSL_PORT; + } + + if ((!url.path || !*url.path) && mutt_account_fromurl (act, &url) == 0) + ret = 0; + } + + p_delete(&c); + return ret; +} + +/* + * Open connection and authenticate + * 0 - successful, + * -1 - conection lost, + * -2 - invalid command or execution error, + * -3 - authentication canceled. +*/ +static pop_query_status pop_open_connection (pop_data_t * pop_data) +{ + pop_query_status ret; + int n, size; + char buf[LONG_STRING]; + + ret = pop_connect(pop_data); + if (ret != PQ_OK) { + mutt_sleep (2); + return ret; + } + + ret = pop_capabilities (pop_data, 0); + if (ret == PQ_NOT_CONNECTED) + goto err_conn; + if (ret == PQ_ERR) { + mutt_sleep (2); + return PQ_ERR; + } + + /* Attempt STLS if available and desired. */ + if (!pop_data->conn->ssf && (pop_data->cmd_stls || mod_ssl.force_tls)) { + if (mod_ssl.force_tls) + pop_data->use_stls = 2; + if (pop_data->use_stls == 0) { + pop_data->use_stls = 1; + if (mod_ssl.starttls) + pop_data->use_stls = 2; + } + if (pop_data->use_stls == 2) { + ret = pop_query(pop_data, buf, sizeof(buf), "STLS"); + if (ret == PQ_NOT_CONNECTED) + goto err_conn; + if (ret != PQ_OK) { + mutt_error ("%s", pop_data->err_msg); + mutt_sleep (2); + } + else if (mutt_ssl_starttls (pop_data->conn)) + { + mutt_error (_("Could not negotiate TLS connection")); + mutt_sleep (2); + return PQ_ERR; + } + else { + /* recheck capabilities after STLS completes */ + ret = pop_capabilities (pop_data, 1); + if (ret == PQ_NOT_CONNECTED) + goto err_conn; + if (ret == PQ_ERR) { + mutt_sleep (2); + return PQ_ERR; + } + } + } + } + + if (mod_ssl.force_tls && !pop_data->conn->ssf) { + mutt_error _("Encrypted connection unavailable"); + mutt_sleep (1); + return -2; + } + + ret = pop_authenticate (pop_data); + if (ret == PQ_NOT_CONNECTED) + goto err_conn; + if (ret == PFD_FUNCT_ERROR) + mutt_clear_error (); + if (ret != PQ_OK) + return ret; + + /* recheck capabilities after authentication */ + ret = pop_capabilities (pop_data, 2); + if (ret == PQ_NOT_CONNECTED) + goto err_conn; + if (ret == PQ_ERR) { + mutt_sleep (2); + return PQ_ERR; + } + + /* get total size of mailbox */ + ret = pop_query(pop_data, buf, sizeof(buf), "STAT"); + if (ret == PQ_NOT_CONNECTED) + goto err_conn; + if (ret == PQ_ERR) { + mutt_error ("%s", pop_data->err_msg); + mutt_sleep (2); + return ret; + } + + sscanf (buf, "+OK %u %u", &n, &size); + pop_data->size = size; + return PQ_OK; + +err_conn: + pop_data->status = POP_DISCONNECTED; + mutt_error _("Server closed connection!"); + + mutt_sleep (2); + return PQ_NOT_CONNECTED; +} + +/* find message with this UIDL and set refno */ +static int check_uidl (char *line, void *data) +{ + int i, idx; + CONTEXT *ctx = (CONTEXT *)data; + + sscanf (line, "%u %s", &idx, line); + for (i = 0; i < ctx->msgcount; i++) { + if (!m_strcmp(ctx->hdrs[i]->data, line)) { + ctx->hdrs[i]->refno = idx; + break; + } + } + + return 0; +} + +/* reconnect and verify indexes if connection was lost */ +static pop_query_status pop_reconnect (CONTEXT * ctx) +{ + pop_query_status ret; + pop_data_t *pop_data = (pop_data_t *) ctx->data; + progress_t bar; + + if (pop_data->status == POP_CONNECTED) + return PQ_OK; + if (pop_data->status == POP_BYE) + return PQ_NOT_CONNECTED; + + for (;;) { + mutt_socket_close (pop_data->conn); + + ret = pop_open_connection(pop_data); + if (ret == PQ_OK) { + int i; + + bar.msg = _("Verifying message indexes..."); + bar.size = 0; + mutt_progress_bar (&bar, 0); + + for (i = 0; i < ctx->msgcount; i++) + ctx->hdrs[i]->refno = -1; + + ret = pop_fetch_data(pop_data, "UIDL\r\n", &bar, check_uidl, ctx); + if (ret == PQ_ERR) { + mutt_error ("%s", pop_data->err_msg); + mutt_sleep (2); + } + } + if (ret == PQ_OK) + return PQ_OK; + + pop_logout (ctx); + + if (ret == PQ_ERR) + return PQ_NOT_CONNECTED; + + if (query_quadoption (OPT_POPRECONNECT, + _("Connection lost. Reconnect to POP server?")) != + M_YES) + return PQ_NOT_CONNECTED; + } +} /* write line to file */ static int fetch_message (char *line, void *file) @@ -48,39 +831,34 @@ static int fetch_message (char *line, void *file) * -2 - invalid command or execution error, * -3 - error writing to tempfile */ -static int pop_read_header (POP_DATA * pop_data, HEADER * h) +static pop_query_status pop_read_header (pop_data_t * pop_data, HEADER * h) { FILE *f; - int ret, index; + int idx; + pop_query_status ret; long length; char buf[LONG_STRING]; - char tempfile[_POSIX_PATH_MAX]; - mutt_mktemp (tempfile); - if (!(f = safe_fopen (tempfile, "w+"))) { - mutt_perror (tempfile); - return -3; + f = tmpfile(); + if (!f) { + mutt_error(_("Could not create temporary file")); + return PFD_FUNCT_ERROR; } - snprintf (buf, sizeof (buf), "LIST %d\r\n", h->refno); - ret = pop_query (pop_data, buf, sizeof (buf)); - if (ret == 0) { - sscanf (buf, "+OK %d %ld", &index, &length); + ret = pop_query(pop_data, buf, sizeof(buf), "LIST %d", h->refno); + if (ret == PQ_OK) { + sscanf (buf, "+OK %d %ld", &idx, &length); snprintf (buf, sizeof (buf), "TOP %d 0\r\n", h->refno); - ret = pop_fetch_data (pop_data, buf, NULL, fetch_message, f); - - if (pop_data->cmd_top == 2) { - if (ret == 0) { - pop_data->cmd_top = 1; + ret = pop_fetch_data(pop_data, buf, NULL, fetch_message, f); - dprint (1, (debugfile, "pop_read_header: set TOP capability\n")); + if (pop_data->cmd_top == CMD_UNKNOWN) { + if (ret == PQ_OK) { + pop_data->cmd_top = CMD_AVAILABLE; } - if (ret == -2) { - pop_data->cmd_top = 0; - - dprint (1, (debugfile, "pop_read_header: unset TOP capability\n")); + if (ret == PQ_ERR) { + pop_data->cmd_top = CMD_NOT_AVAILABLE; snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), _("Command TOP is not supported by server.")); } @@ -88,7 +866,7 @@ static int pop_read_header (POP_DATA * pop_data, HEADER * h) } switch (ret) { - case 0: + case PQ_OK: { rewind (f); h->env = mutt_read_rfc822_header (f, h, 0, 0); @@ -100,53 +878,53 @@ static int pop_read_header (POP_DATA * pop_data, HEADER * h) } break; } - case -2: + case PQ_ERR: { mutt_error ("%s", pop_data->err_msg); break; } - case -3: + case PFD_FUNCT_ERROR: { mutt_error _("Can't write header to temporary file!"); + break; + } + case PQ_NOT_CONNECTED: + { + mutt_error _("Can't fetch header: Not connected!"); break; } } - fclose (f); - unlink (tempfile); + m_fclose(&f); return ret; } /* parse UIDL */ static int fetch_uidl (char *line, void *data) { - int i, index; + int i, idx; CONTEXT *ctx = (CONTEXT *) data; - POP_DATA *pop_data = (POP_DATA *) ctx->data; + pop_data_t *pop_data = (pop_data_t *) ctx->data; - sscanf (line, "%d %s", &index, line); + sscanf (line, "%d %s", &idx, line); for (i = 0; i < ctx->msgcount; i++) - if (!mutt_strcmp (line, ctx->hdrs[i]->data)) + if (!m_strcmp(line, ctx->hdrs[i]->data)) break; if (i == ctx->msgcount) { - dprint (1, - (debugfile, "pop_fetch_headers: new header %d %s\n", index, - line)); - if (i >= ctx->hdrmax) mx_alloc_memory (ctx); ctx->msgcount++; - ctx->hdrs[i] = mutt_new_header (); - ctx->hdrs[i]->data = safe_strdup (line); + ctx->hdrs[i] = header_new(); + ctx->hdrs[i]->data = m_strdup(line); } - else if (ctx->hdrs[i]->index != index - 1) + else if (ctx->hdrs[i]->index != idx - 1) pop_data->clear_cache = 1; - ctx->hdrs[i]->refno = index; - ctx->hdrs[i]->index = index - 1; + ctx->hdrs[i]->refno = idx; + ctx->hdrs[i]->index = idx - 1; return 0; } @@ -161,8 +939,9 @@ static int fetch_uidl (char *line, void *data) */ static int pop_fetch_headers (CONTEXT * ctx) { - int i, ret, old_count, new_count; - POP_DATA *pop_data = (POP_DATA *) ctx->data; + int i, old_count, new_count; + pop_query_status ret; + pop_data_t *pop_data = (pop_data_t *) ctx->data; time (&pop_data->check_time); pop_data->clear_cache = 0; @@ -171,27 +950,24 @@ static int pop_fetch_headers (CONTEXT * ctx) ctx->hdrs[i]->refno = -1; old_count = ctx->msgcount; - ret = pop_fetch_data (pop_data, "UIDL\r\n", NULL, fetch_uidl, ctx); + ret = pop_fetch_data(pop_data, "UIDL\r\n", NULL, fetch_uidl, ctx); new_count = ctx->msgcount; ctx->msgcount = old_count; - if (pop_data->cmd_uidl == 2) { - if (ret == 0) { - pop_data->cmd_uidl = 1; - - dprint (1, (debugfile, "pop_fetch_headers: set UIDL capability\n")); + if (pop_data->cmd_uidl == CMD_UNKNOWN) { + if (ret == PQ_OK) { + pop_data->cmd_uidl = CMD_AVAILABLE; } - if (ret == -2 && pop_data->cmd_uidl == 2) { - pop_data->cmd_uidl = 0; + if (ret == PQ_ERR && pop_data->cmd_uidl == CMD_UNKNOWN) { + pop_data->cmd_uidl = CMD_NOT_AVAILABLE; - dprint (1, (debugfile, "pop_fetch_headers: unset UIDL capability\n")); snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), _("Command UIDL is not supported by server.")); } } - if (ret == 0) { + if (ret == PQ_OK) { for (i = 0; i < old_count; i++) if (ctx->hdrs[i]->refno == -1) ctx->hdrs[i]->deleted = 1; @@ -201,7 +977,7 @@ static int pop_fetch_headers (CONTEXT * ctx) i + 1 - old_count, new_count - old_count); ret = pop_read_header (pop_data, ctx->hdrs[i]); - if (ret < 0) + if (ret != PQ_OK) break; ctx->msgcount++; @@ -211,9 +987,9 @@ static int pop_fetch_headers (CONTEXT * ctx) mx_update_context (ctx, i - old_count); } - if (ret < 0) { + if (ret != PQ_OK) { for (i = ctx->msgcount; i < new_count; i++) - mutt_free_header (&ctx->hdrs[i]); + header_delete(&ctx->hdrs[i]); return ret; } @@ -221,49 +997,70 @@ static int pop_fetch_headers (CONTEXT * ctx) return (new_count - old_count); } -/* open POP mailbox - fetch only headers */ -int pop_open_mailbox (CONTEXT * ctx) +/* delete all cached messages */ +static void pop_clear_cache (pop_data_t * pop_data) +{ + int i; + + if (!pop_data->clear_cache) + return; + + for (i = 0; i < POP_CACHE_LEN; i++) { + if (pop_data->cache[i].path) { + unlink (pop_data->cache[i].path); + p_delete(&pop_data->cache[i].path); + } + } +} + +/* pop_mx functions {{{ */ + +static int pop_is_magic(const char* path, struct stat* st) +{ + url_scheme_t s = url_check_scheme(NONULL(path)); + return s == U_POP || s == U_POPS ? M_POP : -1; +} + +static int pop_open_mailbox (CONTEXT * ctx) { int ret; char buf[LONG_STRING]; CONNECTION *conn; - ACCOUNT acct; - POP_DATA *pop_data; + ACCOUNT act; + pop_data_t *pop_data; ciss_url_t url; - if (pop_parse_path (ctx->path, &acct)) { + if (pop_parse_path (ctx->path, &act)) { mutt_error (_("%s is an invalid POP path"), ctx->path); mutt_sleep (2); return -1; } - mutt_account_tourl (&acct, &url); + mutt_account_tourl (&act, &url); url.path = NULL; url_ciss_tostring (&url, buf, sizeof (buf), 0); - conn = mutt_conn_find (NULL, &acct); + conn = mutt_conn_find (NULL, &act); if (!conn) return -1; - FREE (&ctx->path); - ctx->path = safe_strdup (buf); + p_delete(&ctx->path); + ctx->path = m_strdup(buf); - pop_data = safe_calloc (1, sizeof (POP_DATA)); + pop_data = p_new(pop_data_t, 1); pop_data->conn = conn; ctx->data = pop_data; - if (pop_open_connection (pop_data) < 0) + if (pop_open_connection(pop_data) != PQ_OK) return -1; conn->data = pop_data; - FOREVER { - if (pop_reconnect (ctx) < 0) + for (;;) { + if (pop_reconnect (ctx) != PQ_OK) return -1; - ctx->size = pop_data->size; - mutt_message _("Fetching list of messages..."); - + ctx->size = pop_data->size; ret = pop_fetch_headers (ctx); if (ret >= 0) @@ -276,28 +1073,51 @@ int pop_open_mailbox (CONTEXT * ctx) } } -/* delete all cached messages */ -static void pop_clear_cache (POP_DATA * pop_data) +static int pop_acl_check(CONTEXT *ctx, int bit) { - int i; + switch (bit) { + case ACL_DELETE: /* (un)deletion */ + case ACL_SEEN: /* mark as read */ + return 1; + case ACL_INSERT: /* editing messages */ + case ACL_WRITE: /* change importance */ + default: + return 0; + } +} - if (!pop_data->clear_cache) - return; +static int pop_check_mailbox(CONTEXT * ctx, int *index_hint, int unused) +{ + int ret; + pop_data_t *pop_data = (pop_data_t *) ctx->data; - dprint (1, (debugfile, "pop_clear_cache: delete cached messages\n")); + if ((pop_data->check_time + PopCheckTimeout) > time (NULL)) + return 0; - for (i = 0; i < POP_CACHE_LEN; i++) { - if (pop_data->cache[i].path) { - unlink (pop_data->cache[i].path); - FREE (&pop_data->cache[i].path); - } - } + pop_logout (ctx); + + mutt_socket_close (pop_data->conn); + + if (pop_open_connection (pop_data) < 0) + return -1; + + mutt_message _("Checking for new messages..."); + ctx->size = pop_data->size; + ret = pop_fetch_headers (ctx); + pop_clear_cache (pop_data); + + if (ret < 0) + return -1; + + if (ret > 0) + return M_NEW_MAIL; + + return 0; } -/* close POP mailbox */ -void pop_close_mailbox (CONTEXT * ctx) +static void pop_close_mailbox (CONTEXT * ctx) { - POP_DATA *pop_data = (POP_DATA *) ctx->data; + pop_data_t *pop_data = (pop_data_t *) ctx->data; if (!pop_data) return; @@ -318,230 +1138,110 @@ void pop_close_mailbox (CONTEXT * ctx) return; } -/* fetch message from POP server */ -int pop_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno) -{ - int ret; - void *uidl; - char buf[LONG_STRING]; - char path[_POSIX_PATH_MAX]; - char *m = _("Fetching message..."); - POP_DATA *pop_data = (POP_DATA *) ctx->data; - POP_CACHE *cache; - HEADER *h = ctx->hdrs[msgno]; - - /* see if we already have the message in our cache */ - cache = &pop_data->cache[h->index % POP_CACHE_LEN]; - - if (cache->path) { - if (cache->index == h->index) { - /* yes, so just return a pointer to the message */ - msg->fp = fopen (cache->path, "r"); - if (msg->fp) - return 0; - - mutt_perror (cache->path); - mutt_sleep (2); - return -1; - } - else { - /* clear the previous entry */ - unlink (cache->path); - FREE (&cache->path); - } - } - - FOREVER { - if (pop_reconnect (ctx) < 0) - return -1; - - /* verify that massage index is correct */ - if (h->refno < 0) { - mutt_error - _("The message index is incorrect. Try reopening the mailbox."); - mutt_sleep (2); - return -1; - } - - mutt_message (m); - - mutt_mktemp (path); - msg->fp = safe_fopen (path, "w+"); - if (!msg->fp) { - mutt_perror (path); - mutt_sleep (2); - return -1; - } - - snprintf (buf, sizeof (buf), "RETR %d\r\n", h->refno); - - ret = pop_fetch_data (pop_data, buf, m, fetch_message, msg->fp); - if (ret == 0) - break; - - safe_fclose (&msg->fp); - unlink (path); - - if (ret == -2) { - mutt_error ("%s", pop_data->err_msg); - mutt_sleep (2); - return -1; - } - - if (ret == -3) { - mutt_error _("Can't write message to temporary file!"); - - mutt_sleep (2); - return -1; - } - } - - /* Update the header information. Previously, we only downloaded a - * portion of the headers, those required for the main display. - */ - cache->index = h->index; - cache->path = safe_strdup (path); - rewind (msg->fp); - uidl = h->data; - mutt_free_envelope (&h->env); - h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0); - h->data = uidl; - h->lines = 0; - fgets (buf, sizeof (buf), msg->fp); - while (!feof (msg->fp)) { - ctx->hdrs[msgno]->lines++; - fgets (buf, sizeof (buf), msg->fp); - } - - h->content->length = ftell (msg->fp) - h->content->offset; - - /* This needs to be done in case this is a multipart message */ - if (!WithCrypto) - h->security = crypt_query (h->content); - - mutt_clear_error (); - rewind (msg->fp); - - return 0; -} - -/* update POP mailbox - delete messages from server */ -int pop_sync_mailbox (CONTEXT * ctx, int *index_hint) +static int pop_sync_mailbox(CONTEXT * ctx, int unused, int *index_hint) { - int i, ret; + int i; + pop_query_status ret; char buf[LONG_STRING]; - POP_DATA *pop_data = (POP_DATA *) ctx->data; + pop_data_t *pop_data = (pop_data_t *) ctx->data; pop_data->check_time = 0; - FOREVER { - if (pop_reconnect (ctx) < 0) - return -1; + for (;;) { + if (pop_reconnect (ctx) != PQ_OK) + return PQ_NOT_CONNECTED; mutt_message (_("Marking %d messages deleted..."), ctx->deleted); for (i = 0, ret = 0; ret == 0 && i < ctx->msgcount; i++) { if (ctx->hdrs[i]->deleted) { - snprintf (buf, sizeof (buf), "DELE %d\r\n", ctx->hdrs[i]->refno); - ret = pop_query (pop_data, buf, sizeof (buf)); + ret = pop_query(pop_data, buf, sizeof(buf), "DELE %d", + ctx->hdrs[i]->refno); } } - if (ret == 0) { - strfcpy (buf, "QUIT\r\n", sizeof (buf)); - ret = pop_query (pop_data, buf, sizeof (buf)); + if (ret == PQ_OK) { + ret = pop_query(pop_data, buf, sizeof(buf), "QUIT"); } - if (ret == 0) { + if (ret == PQ_OK) { pop_data->clear_cache = 1; pop_clear_cache (pop_data); pop_data->status = POP_DISCONNECTED; - return 0; + return PQ_OK; } - if (ret == -2) { + if (ret == PQ_ERR) { mutt_error ("%s", pop_data->err_msg); mutt_sleep (2); - return -1; + return PQ_NOT_CONNECTED; } } } -/* Check for new messages and fetch headers */ -int pop_check_mailbox (CONTEXT * ctx, int *index_hint) -{ - int ret; - POP_DATA *pop_data = (POP_DATA *) ctx->data; - - if ((pop_data->check_time + PopCheckTimeout) > time (NULL)) - return 0; - - pop_logout (ctx); - - mutt_socket_close (pop_data->conn); - - if (pop_open_connection (pop_data) < 0) - return -1; - - ctx->size = pop_data->size; - - mutt_message _("Checking for new messages..."); - - ret = pop_fetch_headers (ctx); - pop_clear_cache (pop_data); - - if (ret < 0) - return -1; - - if (ret > 0) - return M_NEW_MAIL; - - return 0; -} +/* }}} */ + +mx_t const pop_mx = { + M_POP, + 0, + pop_is_magic, + NULL, + NULL, + pop_open_mailbox, + NULL, + pop_acl_check, + pop_check_mailbox, + pop_close_mailbox, + pop_sync_mailbox, + NULL, +}; + +/* public API {{{ */ -/* Fetch messages and save them in $spoolfile */ void pop_fetch_mail (void) { char buffer[LONG_STRING]; - char msgbuf[SHORT_STRING]; + char msgbuf[STRING]; char *url, *p; - int i, delanswer, last = 0, msgs, bytes, rset = 0, ret; + int i, delanswer, last = 0, msgs, bytes, rset = 0; + pop_query_status ret; CONNECTION *conn; CONTEXT ctx; MESSAGE *msg = NULL; - ACCOUNT acct; - POP_DATA *pop_data; + ACCOUNT act; + pop_data_t *pop_data; + ssize_t plen; - if (!PopHost) { + if (m_strisempty(PopHost)) { mutt_error _("POP host is not defined."); - return; } - url = p = safe_calloc (strlen (PopHost) + 7, sizeof (char)); + plen = m_strlen(PopHost) + 7; + url = p = p_new(char, plen); if (url_check_scheme (PopHost) == U_UNKNOWN) { - strcpy (url, "pop://"); /* __STRCPY_CHECKED__ */ - p = strchr (url, '\0'); + snprintf(p, plen, "pop://%s", PopHost); + } else { + m_strcpy(p, plen, PopHost); } - strcpy (p, PopHost); /* __STRCPY_CHECKED__ */ - ret = pop_parse_path (url, &acct); - FREE (&url); + ret = pop_parse_path (url, &act); + p_delete(&url); if (ret) { mutt_error (_("%s is an invalid POP path"), PopHost); return; } - conn = mutt_conn_find (NULL, &acct); + conn = mutt_conn_find (NULL, &act); if (!conn) return; - pop_data = safe_calloc (1, sizeof (POP_DATA)); + pop_data = p_new(pop_data_t, 1); pop_data->conn = conn; if (pop_open_connection (pop_data) < 0) { mutt_socket_free (pop_data->conn); - FREE (&pop_data); + p_delete(&pop_data); return; } @@ -550,11 +1250,10 @@ void pop_fetch_mail (void) mutt_message _("Checking for new messages..."); /* find out how many messages are in the mailbox. */ - strfcpy (buffer, "STAT\r\n", sizeof (buffer)); - ret = pop_query (pop_data, buffer, sizeof (buffer)); - if (ret == -1) + ret = pop_query(pop_data, buffer, sizeof(buffer), "STAT"); + if (ret == PQ_NOT_CONNECTED) goto fail; - if (ret == -2) { + if (ret == PQ_ERR) { mutt_error ("%s", pop_data->err_msg); goto finish; } @@ -563,11 +1262,10 @@ void pop_fetch_mail (void) /* only get unread messages */ if (msgs > 0 && option (OPTPOPLAST)) { - strfcpy (buffer, "LAST\r\n", sizeof (buffer)); - ret = pop_query (pop_data, buffer, sizeof (buffer)); - if (ret == -1) + ret = pop_query(pop_data, buffer, sizeof(buffer), "LAST"); + if (ret == PQ_NOT_CONNECTED) goto fail; - if (ret == 0) + if (ret == PQ_OK) sscanf (buffer, "+OK %d", &last); } @@ -592,33 +1290,31 @@ void pop_fetch_mail (void) ret = -3; else { snprintf (buffer, sizeof (buffer), "RETR %d\r\n", i); - ret = pop_fetch_data (pop_data, buffer, NULL, fetch_message, msg->fp); - if (ret == -3) + ret = pop_fetch_data(pop_data, buffer, NULL, fetch_message, msg->fp); + if (ret == PFD_FUNCT_ERROR) rset = 1; - if (ret == 0 && mx_commit_message (msg, &ctx) != 0) { + if (ret == PQ_OK && mx_commit_message (msg, &ctx) != 0) { rset = 1; - ret = -3; + ret = PFD_FUNCT_ERROR; } mx_close_message (&msg); } - if (ret == 0 && delanswer == M_YES) { - /* delete the message on the server */ - snprintf (buffer, sizeof (buffer), "DELE %d\r\n", i); - ret = pop_query (pop_data, buffer, sizeof (buffer)); + if (ret == PQ_OK && delanswer == M_YES) { + ret = pop_query(pop_data, buffer, sizeof(buffer), "DELE %d", i); } - if (ret == -1) { + if (ret == PQ_NOT_CONNECTED) { mx_close_mailbox (&ctx, NULL); goto fail; } - if (ret == -2) { + if (ret == PQ_ERR) { mutt_error ("%s", pop_data->err_msg); break; } - if (ret == -3) { + if (ret == -3) { /* this is -3 when ret != 0, because it will keep the value from before *gna* */ mutt_error _("Error while writing mailbox!"); break; @@ -632,22 +1328,128 @@ void pop_fetch_mail (void) if (rset) { /* make sure no messages get deleted */ - strfcpy (buffer, "RSET\r\n", sizeof (buffer)); - if (pop_query (pop_data, buffer, sizeof (buffer)) == -1) + if (pop_query(pop_data, buffer, sizeof(buffer), "RSET") == + PQ_NOT_CONNECTED) goto fail; } finish: - /* exit gracefully */ - strfcpy (buffer, "QUIT\r\n", sizeof (buffer)); - if (pop_query (pop_data, buffer, sizeof (buffer)) == -1) + if (pop_query(pop_data, buffer, sizeof(buffer), "QUIT") == PQ_NOT_CONNECTED) goto fail; mutt_socket_close (conn); - FREE (&pop_data); + p_delete(&pop_data); return; fail: mutt_error _("Server closed connection!"); mutt_socket_close (conn); - FREE (&pop_data); + p_delete(&pop_data); } + +/* fetch message from POP server */ +int pop_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno) +{ + int ret; + void *uidl; + char buf[LONG_STRING]; + char path[_POSIX_PATH_MAX]; + progress_t bar; + pop_data_t *pop_data = (pop_data_t *) ctx->data; + POP_CACHE *cache; + HEADER *h = ctx->hdrs[msgno]; + + /* see if we already have the message in our cache */ + cache = &pop_data->cache[h->index % POP_CACHE_LEN]; + + if (cache->path) { + if (cache->index == h->index) { + /* yes, so just return a pointer to the message */ + msg->fp = fopen (cache->path, "r"); + if (msg->fp) + return 0; + + mutt_perror (cache->path); + mutt_sleep (2); + return -1; + } + else { + /* clear the previous entry */ + unlink (cache->path); + p_delete(&cache->path); + } + } + + for (;;) { + if (pop_reconnect (ctx) != PQ_OK) + return -1; + + /* verify that massage index is correct */ + if (h->refno < 0) { + mutt_error + _("The message index is incorrect. Try reopening the mailbox."); + mutt_sleep (2); + return -1; + } + + bar.size = h->content->length + h->content->offset - 1; + bar.msg = _("Fetching message..."); + mutt_progress_bar (&bar, 0); + + msg->fp = m_tempfile(path, sizeof(path), NONULL(mod_core.tmpdir), NULL); + if (!msg->fp) { + mutt_error(_("Could not create temporary file")); + mutt_sleep(2); + return -1; + } + + snprintf(buf, sizeof(buf), "RETR %d\r\n", h->refno); + ret = pop_fetch_data(pop_data, buf, &bar, fetch_message, msg->fp); + if (ret == PQ_OK) + break; + + m_fclose(&msg->fp); + unlink (path); + + if (ret == PQ_ERR) { + mutt_error ("%s", pop_data->err_msg); + mutt_sleep (2); + return -1; + } + + if (ret == PFD_FUNCT_ERROR) { + mutt_error _("Can't write message to temporary file!"); + + mutt_sleep (2); + return -1; + } + } + + /* Update the header information. Previously, we only downloaded a + * portion of the headers, those required for the main display. + */ + cache->index = h->index; + cache->path = m_strdup(path); + rewind (msg->fp); + uidl = h->data; + envelope_delete(&h->env); + h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0); + h->data = uidl; + h->lines = 0; + fgets (buf, sizeof (buf), msg->fp); + while (!feof (msg->fp)) { + ctx->hdrs[msgno]->lines++; + fgets (buf, sizeof (buf), msg->fp); + } + + h->content->length = ftello (msg->fp) - h->content->offset; + + /* This needs to be done in case this is a multipart message */ + h->security = crypt_query (h->content); + + mutt_clear_error (); + rewind (msg->fp); + + return 0; +} + +/* }}} */