728 lines
20 KiB
C
728 lines
20 KiB
C
/* rewrite of the IMAP code by Neil Spring
|
|
* (nspring@cs.washington.edu) to support gnutls and
|
|
* persistent connections to servers. */
|
|
|
|
/* Originally written by Yong-iL Joh (tolkien@mizi.com),
|
|
* modified by Jorge Garcia (Jorge.Garcia@uv.es), and
|
|
* modified by Jay Francis (jtf@u880.org) to support
|
|
* CRAM-MD5 */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "Client.h"
|
|
#include "charutil.h"
|
|
#include "tlsComm.h"
|
|
#include "passwordMgr.h"
|
|
#include "regulo.h"
|
|
#include "MessageList.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <ctype.h>
|
|
#include <strings.h>
|
|
|
|
#ifdef USE_DMALLOC
|
|
#include <dmalloc.h>
|
|
#endif
|
|
|
|
#define PCU (pc->u).pop_imap
|
|
|
|
extern int Relax;
|
|
|
|
#define IMAP_DM(pc, lvl, args...) DM(pc, lvl, "imap4: " args)
|
|
|
|
#ifdef HAVE_MEMFROB
|
|
#define DEFROB(x) memfrob(x, x ## _len)
|
|
#define ENFROB(x) memfrob(x, x ## _len)
|
|
#else
|
|
#define DEFROB(x)
|
|
#define ENFROB(x)
|
|
#endif
|
|
|
|
/* this array maps server:port pairs to file descriptors, so
|
|
that when more than one mailbox is queried from a server,
|
|
we only use one socket. It's limited in size by the
|
|
number of different mailboxes displayed. */
|
|
#define FDMAP_SIZE 5
|
|
static struct fdmap_struct {
|
|
char *user_server_port; /* tuple, in string form */
|
|
/*@owned@ */ struct connection_state *cs;
|
|
} fdmap[FDMAP_SIZE];
|
|
|
|
static void ask_user_for_password( /*@notnull@ */ Pop3 pc,
|
|
int bFlushCache);
|
|
|
|
/* authentication callbacks */
|
|
#ifdef HAVE_GCRYPT_H
|
|
static int authenticate_md5( /*@notnull@ */ Pop3 pc,
|
|
struct connection_state *scs,
|
|
const char *capabilities);
|
|
#endif
|
|
static int authenticate_plaintext( /*@notnull@ */ Pop3 pc,
|
|
struct connection_state *scs,
|
|
const char *capabilities);
|
|
|
|
/* the auth_methods array maps authentication identifiers
|
|
to the callback that will attempt to authenticate */
|
|
static struct imap_authentication_method {
|
|
const char *name;
|
|
/* callback returns 1 if successful, 0 if failed */
|
|
int (*auth_callback) ( /*@notnull@ */ Pop3 pc,
|
|
struct connection_state * scs,
|
|
const char *capabilities);
|
|
} auth_methods[] = {
|
|
{
|
|
#ifdef HAVE_GCRYPT_H
|
|
"cram-md5", authenticate_md5}, {
|
|
#endif
|
|
"plaintext", authenticate_plaintext}, {
|
|
NULL, NULL}
|
|
};
|
|
|
|
|
|
/* recover a socket from the connection cache */
|
|
/*@null@*/
|
|
/*@dependent@*/
|
|
static struct connection_state *state_for_pcu(Pop3 pc)
|
|
{
|
|
char *connection_id;
|
|
struct connection_state *retval = NULL;
|
|
int i;
|
|
connection_id =
|
|
malloc(strlen(PCU.userName) + strlen(PCU.serverName) + 22);
|
|
sprintf(connection_id, "%s|%s|%d", PCU.userName, PCU.serverName,
|
|
PCU.serverPort);
|
|
for (i = 0; i < FDMAP_SIZE; i++)
|
|
if (fdmap[i].user_server_port != NULL &&
|
|
(strcmp(connection_id, fdmap[i].user_server_port) == 0)) {
|
|
retval = fdmap[i].cs;
|
|
}
|
|
free(connection_id);
|
|
return (retval);
|
|
}
|
|
|
|
/* bind to the connection cache */
|
|
static void bind_state_to_pcu(Pop3 pc,
|
|
/*@owned@ */ struct connection_state *scs)
|
|
{
|
|
char *connection_id;
|
|
int i;
|
|
if (scs == NULL) {
|
|
abort();
|
|
}
|
|
connection_id =
|
|
malloc(strlen(PCU.userName) + strlen(PCU.serverName) + 22);
|
|
sprintf(connection_id, "%s|%s|%d", PCU.userName, PCU.serverName,
|
|
PCU.serverPort);
|
|
for (i = 0; i < FDMAP_SIZE && fdmap[i].cs != NULL; i++);
|
|
if (i == FDMAP_SIZE) {
|
|
/* should never happen */
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"Tried to open too many IMAP connections. Sorry!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
fdmap[i].user_server_port = connection_id;
|
|
fdmap[i].cs = scs;
|
|
}
|
|
|
|
/* remove from the connection cache */
|
|
static
|
|
/*@owned@*/
|
|
/*@null@*/
|
|
struct connection_state *unbind(
|
|
/*@returned@*/ struct connection_state
|
|
*scs)
|
|
{
|
|
int i;
|
|
struct connection_state *retval = NULL;
|
|
assert(scs != NULL);
|
|
|
|
for (i = 0; i < FDMAP_SIZE && fdmap[i].cs != scs; i++);
|
|
if (i < FDMAP_SIZE) {
|
|
free(fdmap[i].user_server_port);
|
|
fdmap[i].user_server_port = NULL;
|
|
retval = fdmap[i].cs;
|
|
fdmap[i].cs = NULL;
|
|
}
|
|
return (retval);
|
|
}
|
|
|
|
/* creates a connection to the server, if a matching one doesn't exist. */
|
|
/* *always* returns null, just declared this wasy to match other protocols. */
|
|
/*@null@*/
|
|
FILE *imap_open(Pop3 pc)
|
|
{
|
|
static int complained_already; /* we have to succeed once before
|
|
complaining again about failure */
|
|
struct connection_state *scs;
|
|
struct imap_authentication_method *a;
|
|
char *connection_name;
|
|
int sd;
|
|
char capabilities[BUF_SIZE];
|
|
char buf[BUF_SIZE];
|
|
|
|
|
|
if (state_for_pcu(pc) != NULL) {
|
|
/* don't need to open. */
|
|
return NULL;
|
|
}
|
|
|
|
/* got this far; we're going to create a connection_state
|
|
structure, although it might be a blacklist entry */
|
|
connection_name = malloc(strlen(PCU.serverName) + 20);
|
|
sprintf(connection_name, "%s:%d", PCU.serverName, PCU.serverPort);
|
|
|
|
assert(pc != NULL);
|
|
|
|
/* no cached connection */
|
|
sd = sock_connect((const char *) PCU.serverName, PCU.serverPort);
|
|
if (sd == -1) {
|
|
if (complained_already == 0) {
|
|
IMAP_DM(pc, DEBUG_ERROR, "Couldn't connect to %s:%d: %s\n",
|
|
PCU.serverName, PCU.serverPort,
|
|
errno ? strerror(errno) : "");
|
|
complained_already = 1;
|
|
}
|
|
if (errno == ETIMEDOUT) {
|
|
/* temporarily bump the interval, in a crude way:
|
|
fast forward time so that the mailbox isn't
|
|
checked for a while. */
|
|
pc->prevtime = time(0) + 60 * 5; /* now + 60 seconds per min * 5 minutes */
|
|
/* TCP's retry (how much time has elapsed while
|
|
the connect times out) is around 3 minutes;
|
|
here we just try to allow checking local
|
|
mailboxes more often while remote things are
|
|
unavailable or disconnected. */
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* build the connection using STARTTLS */
|
|
if (PCU.dossl != 0 && (PCU.serverPort == 143)) {
|
|
/* setup an unencrypted binding long enough to invoke STARTTLS */
|
|
scs = initialize_unencrypted(sd, connection_name, pc);
|
|
|
|
/* can we? */
|
|
tlscomm_printf(scs, "a000 CAPABILITY\r\n");
|
|
if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) ==
|
|
0)
|
|
goto communication_failure;
|
|
|
|
if (!strstr(capabilities, "STARTTLS")) {
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"server doesn't support ssl imap on port 143.");
|
|
goto communication_failure;
|
|
}
|
|
|
|
/* we sure can! */
|
|
IMAP_DM(pc, DEBUG_INFO, "Negotiating TLS within IMAP");
|
|
tlscomm_printf(scs, "a001 STARTTLS\r\n");
|
|
|
|
if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0)
|
|
goto communication_failure;
|
|
|
|
if (strstr(buf, "a001 OK") == 0) {
|
|
/* we didn't see the success message in the response */
|
|
IMAP_DM(pc, DEBUG_ERROR, "couldn't negotiate tls. :(\n");
|
|
goto communication_failure;
|
|
}
|
|
|
|
/* we don't need the unencrypted state anymore */
|
|
/* note that communication_failure will close the
|
|
socket and free via tls_close() */
|
|
free(scs); /* fall through will scs = initialize_gnutls(sd); */
|
|
}
|
|
|
|
/* either we've negotiated ssl from starttls, or
|
|
we're starting an encrypted connection now */
|
|
if (PCU.dossl != 0) {
|
|
scs = initialize_gnutls(sd, connection_name, pc, PCU.serverName);
|
|
if (scs == NULL) {
|
|
IMAP_DM(pc, DEBUG_ERROR, "Failed to initialize TLS\n");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
scs = initialize_unencrypted(sd, connection_name, pc);
|
|
}
|
|
|
|
/* authenticate; first find out how */
|
|
/* note that capabilities may have changed since last
|
|
time we may have asked, if we called STARTTLS, my
|
|
server will allow plain password login within an
|
|
encrypted session. */
|
|
tlscomm_printf(scs, "a000 CAPABILITY\r\n");
|
|
if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) == 0) {
|
|
IMAP_DM(pc, DEBUG_ERROR, "unable to query capability string");
|
|
goto communication_failure;
|
|
}
|
|
|
|
/* try each authentication method in turn. */
|
|
for (a = auth_methods; a->name != NULL; a++) {
|
|
/* was it specified or did the user leave it up to us? */
|
|
if (PCU.authList[0] == '\0'
|
|
|| strstr(PCU.authList, a->name) != NULL)
|
|
/* try the authentication method */
|
|
if ((a->auth_callback(pc, scs, capabilities)) != 0) {
|
|
/* store this well setup connection in the cache */
|
|
bind_state_to_pcu(pc, scs);
|
|
complained_already = 0;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* if authentication worked, we won't get here */
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"All authentication methods failed for '%s@%s:%d'\n",
|
|
PCU.userName, PCU.serverName, PCU.serverPort);
|
|
communication_failure:
|
|
tlscomm_printf(scs, "a002 LOGOUT\r\n");
|
|
tlscomm_close(scs);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
void imap_cacheHeaders( /*@notnull@ */ Pop3 pc);
|
|
|
|
int imap_checkmail( /*@notnull@ */ Pop3 pc)
|
|
{
|
|
/* recover connection state from the cache */
|
|
struct connection_state *scs = state_for_pcu(pc);
|
|
char buf[BUF_SIZE];
|
|
static int command_id;
|
|
|
|
/* if it's not in the cache, try to open */
|
|
if (scs == NULL) {
|
|
IMAP_DM(pc, DEBUG_INFO, "Need new connection to %s@%s\n",
|
|
PCU.userName, PCU.serverName);
|
|
(void) imap_open(pc);
|
|
scs = state_for_pcu(pc);
|
|
}
|
|
if (scs == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (tlscomm_is_blacklisted(scs) != 0) {
|
|
/* unresponsive server, don't bother. */
|
|
return -1;
|
|
}
|
|
|
|
/* if we've got it by now, try the status query */
|
|
command_id++;
|
|
tlscomm_printf(scs, "a%03d STATUS %s (MESSAGES UNSEEN)\r\n",
|
|
command_id % 1000, pc->path);
|
|
if (tlscomm_expect(scs, "* STATUS", buf, 127) != 0) {
|
|
/* a valid response? */
|
|
// doesn't support spaces: (void) sscanf(buf, "* STATUS %*s (MESSAGES %d UNSEEN %d)",
|
|
const char *msg;
|
|
msg = strstr(buf, "(MESSAGES");
|
|
if (msg != NULL)
|
|
(void) sscanf(msg, "(MESSAGES %d UNSEEN %d)",
|
|
&(pc->TotalMsgs), &(pc->UnreadMsgs));
|
|
/* update the cached headers if evidence that change
|
|
has occurred; not necessarily complete. */
|
|
if (pc->UnreadMsgs != pc->OldUnreadMsgs ||
|
|
pc->TotalMsgs != pc->OldMsgs) {
|
|
if (PCU.wantCacheHeaders) {
|
|
imap_cacheHeaders(pc);
|
|
}
|
|
}
|
|
} else {
|
|
/* something went wrong. bail. */
|
|
tlscomm_close(unbind(scs));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
imap_releaseHeaders(Pop3 pc __attribute__ ((unused)), struct msglst *h)
|
|
{
|
|
assert(h != NULL);
|
|
/* allow the list to be released next time around */
|
|
if (h->in_use <= 0) {
|
|
/* free the old one */
|
|
while (h != NULL) {
|
|
struct msglst *n = h->next;
|
|
free(h);
|
|
h = n;
|
|
}
|
|
} else {
|
|
h->in_use--;
|
|
}
|
|
}
|
|
|
|
void imap_cacheHeaders( /*@notnull@ */ Pop3 pc)
|
|
{
|
|
struct connection_state *scs = state_for_pcu(pc);
|
|
char *msgid;
|
|
char buf[BUF_SIZE];
|
|
|
|
if (scs == NULL) {
|
|
(void) imap_open(pc);
|
|
scs = state_for_pcu(pc);
|
|
}
|
|
if (scs == NULL) {
|
|
return;
|
|
}
|
|
if (tlscomm_is_blacklisted(scs) != 0) {
|
|
return;
|
|
}
|
|
|
|
if (pc->headerCache != NULL) {
|
|
/* decrement the reference count, and free our version */
|
|
imap_releaseHeaders(pc, pc->headerCache);
|
|
pc->headerCache = NULL;
|
|
}
|
|
|
|
IMAP_DM(pc, DEBUG_INFO, "working headers\n");
|
|
|
|
tlscomm_printf(scs, "a004 EXAMINE %s\r\n", pc->path);
|
|
if (tlscomm_expect(scs, "a004 OK", buf, 127) == 0) {
|
|
tlscomm_close(unbind(scs));
|
|
return;
|
|
}
|
|
IMAP_DM(pc, DEBUG_INFO, "examine ok\n");
|
|
|
|
/* if we've got it by now, try the status query */
|
|
tlscomm_printf(scs, "a005 SEARCH UNSEEN\r\n");
|
|
if (tlscomm_expect(scs, "* SEARCH", buf, 127) == 0) {
|
|
tlscomm_close(unbind(scs));
|
|
return;
|
|
}
|
|
IMAP_DM(pc, DEBUG_INFO, "search: %s", buf);
|
|
if (strlen(buf) < 9)
|
|
return; /* search turned up nothing */
|
|
msgid = strtok(buf + 9, " \r\n");
|
|
pc->headerCache = NULL;
|
|
/* the isdigit cruft is to deal with EOL */
|
|
if (msgid != NULL && isdigit(msgid[0]))
|
|
do {
|
|
struct msglst *m = malloc(sizeof(struct msglst));
|
|
char hdrbuf[128];
|
|
int fetch_command_done = FALSE;
|
|
tlscomm_printf(scs, "a04 FETCH %s (FLAGS "
|
|
"BODY[HEADER.FIELDS (FROM SUBJECT)])\r\n",
|
|
msgid);
|
|
if (tlscomm_expect(scs, "* ", hdrbuf, 127)) {
|
|
m->subj[0] = '\0';
|
|
m->from[0] = '\0';
|
|
while (m->subj[0] == '\0' || m->from[0] == '\0') {
|
|
if (tlscomm_expect(scs, "", hdrbuf, 127)) {
|
|
if (strncasecmp(hdrbuf, "Subject:", 8) == 0) {
|
|
strncpy(m->subj, hdrbuf + 9, SUBJ_LEN - 1);
|
|
m->subj[SUBJ_LEN - 1] = '\0';
|
|
} else if (strncasecmp(hdrbuf, "From: ", 5) == 0) {
|
|
strncpy(m->from, hdrbuf + 6, FROM_LEN - 1);
|
|
m->from[FROM_LEN - 1] = '\0';
|
|
} else if (strncasecmp
|
|
(hdrbuf, "a04 OK FETCH", 5) == 0) {
|
|
/* server says we're done getting this header, which
|
|
may occur if the message has no subject */
|
|
if (m->from[0] == '\0') {
|
|
strcpy(m->from, " ");
|
|
}
|
|
if (m->subj[0] == '\0') {
|
|
strcpy(m->subj, "(no subject)");
|
|
}
|
|
fetch_command_done = TRUE;
|
|
}
|
|
} else {
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"timedout looking for headers.: %s",
|
|
hdrbuf);
|
|
strcpy(m->from, "wmbiff");
|
|
strcpy(m->subj, "failure");
|
|
}
|
|
}
|
|
IMAP_DM(pc, DEBUG_INFO, "From: '%s' Subj: '%s'\n",
|
|
m->from, m->subj);
|
|
m->next = pc->headerCache;
|
|
pc->headerCache = m;
|
|
pc->headerCache->in_use = 0; /* initialize that it isn't locked */
|
|
} else {
|
|
IMAP_DM(pc, DEBUG_ERROR, "error fetching: %s", hdrbuf);
|
|
}
|
|
if (!fetch_command_done) {
|
|
tlscomm_expect(scs, "a04 OK", hdrbuf, 127);
|
|
}
|
|
}
|
|
while ((msgid = strtok(NULL, " \r\n")) != NULL
|
|
&& isdigit(msgid[0]));
|
|
|
|
tlscomm_printf(scs, "a06 CLOSE\r\n"); /* return to polling state */
|
|
/* may be unneeded tlscomm_expect(scs, "a06 OK CLOSE\r\n" ); see if it worked? */
|
|
IMAP_DM(pc, DEBUG_INFO, "worked headers\n");
|
|
}
|
|
|
|
/* a client is asking for the headers, hand em a reference, increase the
|
|
one-bit reference counter */
|
|
struct msglst *imap_getHeaders( /*@notnull@ */ Pop3 pc)
|
|
{
|
|
if (pc->headerCache == NULL)
|
|
imap_cacheHeaders(pc);
|
|
if (pc->headerCache != NULL)
|
|
pc->headerCache->in_use = 1;
|
|
return pc->headerCache;
|
|
}
|
|
|
|
/* parse the config line to setup the Pop3 structure */
|
|
int imap4Create( /*@notnull@ */ Pop3 pc, const char *const str)
|
|
{
|
|
int i;
|
|
int matchedchars;
|
|
/* special characters aren't allowed in hostnames, rfc 1034 */
|
|
const char *regexes[] = {
|
|
// type : username : password @ hostname (/ name)?(:port)?
|
|
".*imaps?:([^: ]{1,256}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
|
|
".*imaps?:([^: ]{1,256}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
|
|
NULL
|
|
};
|
|
char *unaliased_str;
|
|
|
|
struct regulo regulos[] = {
|
|
{1, PCU.userName, regulo_strcpy},
|
|
{2, PCU.password, regulo_strcpy},
|
|
{3, PCU.serverName, regulo_strcpy},
|
|
{4, pc->path, regulo_strcpy_skip1},
|
|
{7, &PCU.serverPort, regulo_atoi},
|
|
{9, PCU.authList, regulo_strcpy_tolower},
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
if (Relax) {
|
|
regexes[0] =
|
|
".*imaps?:([^: ]{1,256}):([^@]{0,32})@([^/: ]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *(.*))?$";
|
|
regexes[1] =
|
|
".*imaps?:([^: ]{1,256}) ([^ ]{1,32}) ([^/: ]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *(.*))?$";
|
|
}
|
|
|
|
|
|
/* IMAP4 format: imap:user:password@server/mailbox[:port] */
|
|
/* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
|
|
if (strncmp("sslimap:", str, 8) == 0 || strncmp("imaps:", str, 6) == 0) {
|
|
#ifdef HAVE_GNUTLS_GNUTLS_H
|
|
PCU.dossl = 1;
|
|
#else
|
|
printf("This copy of wmbiff was not compiled with gnutls;\n"
|
|
"imaps is unavailable. Exiting to protect your\n"
|
|
"passwords and privacy.\n");
|
|
exit(EXIT_FAILURE);
|
|
#endif
|
|
} else {
|
|
PCU.dossl = 0;
|
|
}
|
|
|
|
/* defaults */
|
|
PCU.serverPort = (PCU.dossl != 0) ? 993 : 143;
|
|
PCU.authList[0] = '\0';
|
|
|
|
/* argh, str and pc->path are aliases, so we can't just write the default
|
|
value into the string we're about to parse. */
|
|
unaliased_str = strdup(str);
|
|
strcpy(pc->path, "INBOX");
|
|
|
|
for (matchedchars = 0, i = 0;
|
|
regexes[i] != NULL && matchedchars <= 0; i++) {
|
|
matchedchars = regulo_match(regexes[i], unaliased_str, regulos);
|
|
}
|
|
|
|
/* failed to match either regex */
|
|
if (matchedchars <= 0) {
|
|
pc->label[0] = '\0';
|
|
IMAP_DM(pc, DEBUG_ERROR, "Couldn't parse line %s (%d)\n"
|
|
" If this used to work, run wmbiff with the -relax option, and\n"
|
|
" send mail to "PACKAGE_BUGREPORT" with the hostname\n"
|
|
" of your mail server.\n", unaliased_str, matchedchars);
|
|
return -1;
|
|
}
|
|
|
|
PCU.password_len = strlen(PCU.password);
|
|
if (PCU.password[0] == '\0') {
|
|
PCU.interactive_password = 1;
|
|
} else {
|
|
ENFROB(PCU.password);
|
|
}
|
|
|
|
// grab_authList(unaliased_str + matchedchars, PCU.authList);
|
|
|
|
free(unaliased_str);
|
|
|
|
IMAP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
|
|
IMAP_DM(pc, DEBUG_INFO, "password is %d characters long\n",
|
|
(int) PCU.password_len);
|
|
IMAP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
|
|
IMAP_DM(pc, DEBUG_INFO, "serverPath= '%s'\n", pc->path);
|
|
IMAP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
|
|
IMAP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
|
|
|
|
if (strcmp(pc->action, "msglst") == 0 ||
|
|
strcmp(pc->fetchcmd, "msglst") == 0 ||
|
|
strcmp(pc->button2, "msglst") == 0) {
|
|
PCU.wantCacheHeaders = 1;
|
|
} else {
|
|
PCU.wantCacheHeaders = 0;
|
|
}
|
|
pc->checkMail = imap_checkmail;
|
|
pc->getHeaders = imap_getHeaders;
|
|
pc->releaseHeaders = imap_releaseHeaders;
|
|
pc->TotalMsgs = 0;
|
|
pc->UnreadMsgs = 0;
|
|
pc->OldMsgs = -1;
|
|
pc->OldUnreadMsgs = -1;
|
|
return 0;
|
|
}
|
|
|
|
static int authenticate_plaintext( /*@notnull@ */ Pop3 pc,
|
|
struct connection_state *scs,
|
|
const char *capabilities)
|
|
{
|
|
char buf[BUF_SIZE];
|
|
/* is login prohibited? */
|
|
/* "An IMAP client which complies with [rfc2525, section 3.2]
|
|
* MUST NOT issue the LOGIN command if this capability is present.
|
|
*/
|
|
if (strstr(capabilities, "LOGINDISABLED")) {
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"Plaintext auth prohibited by server: (LOGINDISABLED).\n");
|
|
goto plaintext_failed;
|
|
}
|
|
|
|
ask_user_for_password(pc, 0);
|
|
do {
|
|
/* login */
|
|
DEFROB(PCU.password);
|
|
tlscomm_printf(scs, "a001 LOGIN %s \"%s\"\r\n", PCU.userName,
|
|
PCU.password);
|
|
ENFROB(PCU.password);
|
|
if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0) {
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"Did not get a response to the LOGIN command.\n");
|
|
goto plaintext_failed;
|
|
}
|
|
|
|
if (buf[5] != 'O') {
|
|
IMAP_DM(pc, DEBUG_ERROR, "IMAP Login failed: %s\n", buf);
|
|
/* if we're prompting the user, ask again, else fail */
|
|
if (PCU.interactive_password) {
|
|
PCU.password[0] = '\0';
|
|
ask_user_for_password(pc, 1); /* 1=overwrite the cache */
|
|
} else {
|
|
goto plaintext_failed;
|
|
}
|
|
} else {
|
|
return (1);
|
|
}
|
|
}
|
|
while (1);
|
|
|
|
plaintext_failed:
|
|
return (0);
|
|
}
|
|
|
|
#ifdef HAVE_GCRYPT_H
|
|
static int
|
|
authenticate_md5(Pop3 pc,
|
|
struct connection_state *scs, const char *capabilities)
|
|
{
|
|
char buf[BUF_SIZE];
|
|
char buf2[BUF_SIZE];
|
|
unsigned char *md5;
|
|
gcry_md_hd_t gmh;
|
|
gcry_error_t rc;
|
|
|
|
if (!strstr(capabilities, "AUTH=CRAM-MD5")) {
|
|
/* server doesn't support cram-md5. */
|
|
return 0;
|
|
}
|
|
|
|
tlscomm_printf(scs, "a007 AUTHENTICATE CRAM-MD5\r\n");
|
|
if (tlscomm_expect(scs, "+ ", buf, BUF_SIZE) == 0)
|
|
goto expect_failure;
|
|
|
|
Decode_Base64(buf + 2, buf2);
|
|
IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 challenge: %s\n", buf2);
|
|
|
|
strcpy(buf, PCU.userName);
|
|
strcat(buf, " ");
|
|
ask_user_for_password(pc, 0);
|
|
rc = gcry_md_open(&gmh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
|
|
if (rc != 0) {
|
|
IMAP_DM(pc, DEBUG_INFO, "unable to initialize gcrypt md5\n");
|
|
return 0;
|
|
}
|
|
DEFROB(PCU.password);
|
|
gcry_md_setkey(gmh, PCU.password, strlen(PCU.password));
|
|
ENFROB(PCU.password);
|
|
gcry_md_write(gmh, (unsigned char *) buf2, strlen(buf2));
|
|
gcry_md_final(gmh);
|
|
md5 = gcry_md_read(gmh, 0);
|
|
Bin2Hex(md5, 16, buf2);
|
|
gcry_md_close(gmh);
|
|
|
|
strcat(buf, buf2);
|
|
IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 response: %s\n", buf);
|
|
Encode_Base64(buf, buf2);
|
|
|
|
tlscomm_printf(scs, "%s\r\n", buf2);
|
|
if (tlscomm_expect(scs, "a007 ", buf, BUF_SIZE) == 0)
|
|
goto expect_failure;
|
|
|
|
if (!strncmp(buf, "a007 OK", 7))
|
|
return 1; /* AUTH successful */
|
|
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
|
|
PCU.userName, PCU.serverName, PCU.serverPort);
|
|
IMAP_DM(pc, DEBUG_INFO, "It said %s", buf);
|
|
return 0;
|
|
|
|
expect_failure:
|
|
IMAP_DM(pc, DEBUG_ERROR,
|
|
"tlscomm_expect failed during cram-md5 auth: %s", buf);
|
|
IMAP_DM(pc, DEBUG_ERROR, "failed to authenticate using cram-md5.");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void ask_user_for_password( /*@notnull@ */ Pop3 pc, int bFlushCache)
|
|
{
|
|
/* see if we already have a password, as provided in the config file, or
|
|
already requested from the user. */
|
|
if (PCU.interactive_password) {
|
|
if (strlen(PCU.password) == 0) {
|
|
/* we need to grab the password from the user. */
|
|
char *password;
|
|
IMAP_DM(pc, DEBUG_INFO, "asking for password %d\n",
|
|
bFlushCache);
|
|
password =
|
|
passwordFor(PCU.userName, PCU.serverName, pc, bFlushCache);
|
|
if (password != NULL) {
|
|
if (strlen(password) + 1 > BUF_SMALL) {
|
|
DMA(DEBUG_ERROR, "Password is too long.\n");
|
|
memset(PCU.password, 0, BUF_SMALL - 1);
|
|
} else {
|
|
strncpy(PCU.password, password, BUF_SMALL - 1);
|
|
PCU.password_len = strlen(PCU.password);
|
|
}
|
|
free(password);
|
|
ENFROB(PCU.password);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* vim:set ts=4: */
|
|
/*
|
|
* Local Variables:
|
|
* tab-width: 4
|
|
* c-indent-level: 4
|
|
* c-basic-offset: 4
|
|
* End:
|
|
*/
|