/*
* Copyright notice from original mutt:
* Copyright (C) 2000-2003 Vsevolod Volkov <vvv@mutt.org.ua>
*
* 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 "mutt.h"
#include "ascii.h"
#include "mx.h"
#include "url.h"
#include "pop.h"
#if defined (USE_SSL) || defined (USE_GNUTLS)
# include "mutt_ssl.h"
#endif
#include "lib/mem.h"
#include "lib/intl.h"
#include "lib/debug.h"
#include "lib/str.h"
#include <string.h>
#include <unistd.h>
#include <ctype.h>
/* given an POP mailbox name, return host, port, username and password */
int pop_parse_path (const char *path, ACCOUNT * acct)
{
ciss_url_t url;
char *c;
int ret = -1;
/* Defaults */
acct->flags = 0;
acct->port = POP_PORT;
acct->type = M_ACCT_TYPE_POP;
c = str_dup (path);
url_parse_ciss (&url, c);
if (url.scheme == U_POP || url.scheme == U_POPS) {
if (url.scheme == U_POPS) {
acct->flags |= M_ACCT_SSL;
acct->port = POP_SSL_PORT;
}
if ((!url.path || !*url.path) && mutt_account_fromurl (acct, &url) == 0)
ret = 0;
}
mem_free (&c);
return ret;
}
/* Copy error message to err_msg buffer */
void pop_error (POP_DATA * pop_data, char *msg)
{
char *t, *c, *c2;
t = strchr (pop_data->err_msg, '\0');
c = msg;
if (!str_ncmp (msg, "-ERR ", 5)) {
c2 = msg + 5;
SKIPWS (c2);
if (*c2)
c = c2;
}
strfcpy (t, c, sizeof (pop_data->err_msg) - strlen (pop_data->err_msg));
str_skip_trailws (pop_data->err_msg);
}
/* Parse CAPA output */
static int fetch_capa (char *line, void *data)
{
POP_DATA *pop_data = (POP_DATA *) data;
char *c;
if (!ascii_strncasecmp (line, "SASL", 4)) {
mem_free (&pop_data->auth_list);
c = line + 4;
SKIPWS (c);
pop_data->auth_list = str_dup (c);
}
else if (!ascii_strncasecmp (line, "STLS", 4))
pop_data->cmd_stls = CMD_AVAILABLE;
else if (!ascii_strncasecmp (line, "USER", 4))
pop_data->cmd_user = 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;
}
/* Fetch list of the authentication mechanisms */
static int fetch_auth (char *line, void *data)
{
POP_DATA *pop_data = (POP_DATA *) data;
if (!pop_data->auth_list) {
pop_data->auth_list = mem_malloc (strlen (line) + 1);
*pop_data->auth_list = '\0';
}
else {
mem_realloc (&pop_data->auth_list,
strlen (pop_data->auth_list) + strlen (line) + 2);
strcat (pop_data->auth_list, " "); /* __STRCAT_CHECKED__ */
}
strcat (pop_data->auth_list, line); /* __STRCAT_CHECKED__ */
return 0;
}
/*
* Get capabilities
* 0 - successful,
* -1 - conection lost,
* -2 - execution error.
*/
static pop_query_status pop_capabilities (POP_DATA * pop_data, int mode)
{
char buf[LONG_STRING];
/* 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_user = CMD_NOT_AVAILABLE;
pop_data->cmd_uidl = CMD_NOT_AVAILABLE;
pop_data->cmd_top = CMD_NOT_AVAILABLE;
pop_data->resp_codes = 0;
pop_data->expire = 1;
pop_data->login_delay = 0;
mem_free (&pop_data->auth_list);
}
/* Execute CAPA command */
if (mode == 0 || pop_data->cmd_capa != CMD_NOT_AVAILABLE) {
strfcpy (buf, "CAPA\r\n", sizeof (buf));
switch (pop_fetch_data (pop_data, buf, 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_user = CMD_UNKNOWN;
pop_data->cmd_uidl = CMD_UNKNOWN;
pop_data->cmd_top = CMD_UNKNOWN;
strfcpy (buf, "AUTH\r\n", sizeof (buf));
if (pop_fetch_data (pop_data, buf, NULL, fetch_auth, pop_data) == PQ_NOT_CONNECTED)
return PQ_NOT_CONNECTED;
}
/* Check capabilities */
if (mode == 2) {
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;
}
/*
* Open connection
* 0 - successful,
* -1 - conection lost,
* -2 - invalid response.
*/
pop_query_status pop_connect (POP_DATA * pop_data)
{
char buf[LONG_STRING];
pop_data->status = POP_NONE;
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);
return PQ_NOT_CONNECTED;
}
pop_data->status = POP_CONNECTED;
if (str_ncmp (buf, "+OK", 3)) {
*pop_data->err_msg = '\0';
pop_error (pop_data, buf);
mutt_error ("%s", pop_data->err_msg);
return PQ_ERR;
}
pop_apop_timestamp (pop_data, buf);
return PQ_OK;
}
/*
* Open connection and authenticate
* 0 - successful,
* -1 - conection lost,
* -2 - invalid command or execution error,
* -3 - authentication canceled.
*/
pop_query_status pop_open_connection (POP_DATA * pop_data)
{
pop_query_status ret;
unsigned 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;
}
#if (defined(USE_SSL) || defined(USE_GNUTLS))
/* Attempt STLS if available and desired. */
if (!pop_data->conn->ssf && (pop_data->cmd_stls || option(OPTSSLFORCETLS))) {
if (option (OPTSSLFORCETLS))
pop_data->use_stls = 2;
if (pop_data->use_stls == 0) {
ret = query_quadoption (OPT_SSLSTARTTLS,
_("Secure connection with TLS?"));
if (ret == -1)
return PQ_ERR;
pop_data->use_stls = 1;
if (ret == M_YES)
pop_data->use_stls = 2;
}
if (pop_data->use_stls == 2) {
strfcpy (buf, "STLS\r\n", sizeof (buf));
ret = pop_query (pop_data, buf, sizeof (buf));
if (ret == PQ_NOT_CONNECTED)
goto err_conn;
if (ret != PQ_OK) {
mutt_error ("%s", pop_data->err_msg);
mutt_sleep (2);
}
#if defined (USE_SSL) || defined (USE_GNUTLS)
else if (mutt_ssl_starttls (pop_data->conn))
#endif
{
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 (option(OPTSSLFORCETLS) && !pop_data->conn->ssf) {
mutt_error _("Encrypted connection unavailable");
mutt_sleep (1);
return -2;
}
#endif
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 */
strfcpy (buf, "STAT\r\n", sizeof (buf));
ret = pop_query (pop_data, buf, sizeof (buf));
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;
}
/* logout from POP server */
void pop_logout (CONTEXT * ctx)
{
pop_query_status ret = 0;
char buf[LONG_STRING];
POP_DATA *pop_data = (POP_DATA *) ctx->data;
if (pop_data->status == POP_CONNECTED) {
mutt_message _("Closing connection to POP server...");
if (ctx->readonly) {
strfcpy (buf, "RSET\r\n", sizeof (buf));
ret = pop_query (pop_data, buf, sizeof (buf));
}
if (ret != PQ_NOT_CONNECTED) {
strfcpy (buf, "QUIT\r\n", sizeof (buf));
pop_query (pop_data, buf, sizeof (buf));
}
mutt_clear_error ();
}
pop_data->status = POP_DISCONNECTED;
return;
}
/*
* Send data from buffer and receive answer to the same buffer
* 0 - successful,
* -1 - conection lost,
* -2 - invalid command or execution error.
*/
pop_query_status pop_query_d (POP_DATA * pop_data, char *buf, size_t buflen, char *msg)
{
int dbg = M_SOCK_LOG_CMD;
char *c;
if (pop_data->status != POP_CONNECTED)
return PQ_NOT_CONNECTED;
#ifdef DEBUG
/* print msg instaed of real command */
if (msg) {
dbg = M_SOCK_LOG_FULL;
debug_print (M_SOCK_LOG_CMD, ("> %s", msg));
}
#endif
mutt_socket_write_d (pop_data->conn, buf, dbg);
c = strpbrk (buf, " \r\n");
*c = '\0';
snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), "%s: ", buf);
if (mutt_socket_readln (buf, buflen, pop_data->conn) < 0) {
pop_data->status = POP_DISCONNECTED;
return PQ_NOT_CONNECTED;
}
if (!str_ncmp (buf, "+OK", 3))
return PQ_OK;
pop_error (pop_data, buf);
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)
*/
pop_query_status pop_fetch_data (POP_DATA * pop_data, char *query, progress_t* bar,
int (*funct) (char *, void *), void *data)
{
char buf[LONG_STRING];
char *inbuf;
char *p;
pop_query_status ret;
int chunk = 0;
long pos = 0;
size_t lenbuf = 0;
strfcpy (buf, query, sizeof (buf));
ret = pop_query (pop_data, buf, sizeof (buf));
if (ret != PQ_OK)
return ret;
inbuf = mem_malloc (sizeof (buf));
FOREVER {
chunk =
mutt_socket_readln_d (buf, sizeof (buf), pop_data->conn,
M_SOCK_LOG_HDR);
if (chunk < 0) {
pop_data->status = POP_DISCONNECTED;
ret = PQ_NOT_CONNECTED;
break;
}
p = buf;
if (!lenbuf && buf[0] == '.') {
if (buf[1] != '.')
break;
p++;
}
strfcpy (inbuf + lenbuf, p, sizeof (buf));
pos += chunk;
if (chunk >= sizeof (buf)) {
lenbuf += strlen (p);
}
else {
if (bar)
mutt_progress_bar (bar, pos);
if (ret == 0 && funct (inbuf, data) < 0)
ret = PFD_FUNCT_ERROR;
lenbuf = 0;
}
mem_realloc (&inbuf, lenbuf + sizeof (buf));
}
mem_free (&inbuf);
return ret;
}
/* find message with this UIDL and set refno */
static int check_uidl (char *line, void *data)
{
int i;
unsigned int index;
CONTEXT *ctx = (CONTEXT *) data;
sscanf (line, "%u %s", &index, line);
for (i = 0; i < ctx->msgcount; i++) {
if (!str_cmp (ctx->hdrs[i]->data, line)) {
ctx->hdrs[i]->refno = index;
break;
}
}
return 0;
}
/* reconnect and verify indexes if connection was lost */
pop_query_status pop_reconnect (CONTEXT * ctx)
{
pop_query_status ret;
POP_DATA *pop_data = (POP_DATA *) ctx->data;
progress_t bar;
if (pop_data->status == POP_CONNECTED)
return PQ_OK;
if (pop_data->status == POP_BYE)
return PQ_NOT_CONNECTED;
FOREVER {
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;
}
}
syntax highlighted by Code2HTML, v. 0.9.1