dockapps/wmbiff/wmbiff/Pop3Client.c
2014-10-05 19:18:49 +01:00

532 lines
14 KiB
C

/* $Id: Pop3Client.c,v 1.23 2004/12/12 00:01:53 bluehal Exp $ */
/* Author : Scott Holden ( scotth@thezone.net )
Modified : Yong-iL Joh ( tolkien@mizi.com )
Modified : Jorge García ( Jorge.Garcia@uv.es )
Modified ; Mark Hurley ( debian4tux@telocity.com )
Modified : Neil Spring ( nspring@cs.washington.edu )
*
* Pop3 Email checker.
*
* Last Updated : Tue Nov 13 13:45:23 PST 2001
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "Client.h"
#include "charutil.h"
#include "regulo.h"
#include "MessageList.h"
#include <strings.h>
#include "tlsComm.h"
#include "passwordMgr.h"
#ifdef USE_DMALLOC
#include <dmalloc.h>
#endif
extern int Relax;
/* temp */
static void ask_user_for_password( /*@notnull@ */ Pop3 pc, int bFlushCache);
#define PCU (pc->u).pop_imap
#define POP_DM(pc, lvl, args...) DM(pc, lvl, "pop3: " args)
#ifdef HAVE_GCRYPT_H
static struct connection_state *authenticate_md5( /*@notnull@ */ Pop3 pc, struct connection_state * scs,
char *unused);
static struct connection_state *authenticate_apop( /*@notnull@ */ Pop3 pc, struct connection_state * scs,
char *apop_str);
#endif
static struct connection_state *authenticate_plaintext( /*@notnull@ */ Pop3 pc, struct connection_state * scs,
char *unused);
void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc);
extern void imap_releaseHeaders(Pop3 pc
__attribute__ ((unused)),
struct msglst *h);
extern struct connection_state *state_for_pcu(Pop3 pc);
static struct authentication_method {
const char *name;
/* callback returns the connection state pointer if successful,
NULL if failed */
struct connection_state *(*auth_callback) (Pop3 pc, struct connection_state * scs, char *apop_str);
} auth_methods[] = {
{
#ifdef HAVE_GCRYPT_H
"cram-md5", authenticate_md5}, {
"apop", authenticate_apop}, {
#endif
"plaintext", authenticate_plaintext}, {
NULL, NULL}
};
/*@null@*/
struct connection_state *pop3Login(Pop3 pc)
{
int fd;
char buf[BUF_SIZE];
char apop_str[BUF_SIZE];
char *ptr1, *ptr2;
struct authentication_method *a;
struct connection_state *scs;
char *connection_name;
apop_str[0] = '\0'; /* if defined, server supports apop */
if ((fd = sock_connect(PCU.serverName, PCU.serverPort)) == -1) {
POP_DM(pc, DEBUG_ERROR, "Not Connected To Server '%s:%d'\n",
PCU.serverName, PCU.serverPort);
return NULL;
}
connection_name = malloc(strlen(PCU.serverName) + 20);
sprintf(connection_name, "%s:%d", PCU.serverName, PCU.serverPort);
if (PCU.dossl != 0) {
scs = initialize_gnutls(fd, connection_name, pc, PCU.serverName);
if (scs == NULL) {
POP_DM(pc, DEBUG_ERROR, "Failed to initialize TLS\n");
return NULL;
}
} else {
scs = initialize_unencrypted(fd, connection_name, pc);
}
tlscomm_gets(buf, BUF_SIZE, scs);
POP_DM(pc, DEBUG_INFO, "%s", buf);
/* Detect APOP, copy challenge into apop_str */
for (ptr1 = buf + strlen(buf), ptr2 = NULL; ptr1 > buf; --ptr1) {
if (*ptr1 == '>') {
ptr2 = ptr1;
} else if (*ptr1 == '<') {
if (ptr2) {
*(ptr2 + 1) = 0;
strncpy(apop_str, ptr1, BUF_SIZE);
}
break;
}
}
/* 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))
/* did it work? */
if ((a->auth_callback(pc, scs, apop_str)) != NULL)
return (scs);
}
/* if authentication worked, we won't get here */
POP_DM(pc, DEBUG_ERROR,
"All Pop3 authentication methods failed for '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
tlscomm_printf(scs, "QUIT\r\n");
tlscomm_close(scs);
return NULL;
}
int pop3CheckMail( /*@notnull@ */ Pop3 pc)
{
struct connection_state *scs;
int read;
char buf[BUF_SIZE];
scs = pop3Login(pc);
if (scs == NULL)
return -1;
tlscomm_printf(scs, "STAT\r\n");
if( ! tlscomm_expect(scs, "+", buf, BUF_SIZE) ) {
POP_DM(pc, DEBUG_ERROR,
"Error Receiving Stats '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
POP_DM(pc, DEBUG_INFO, "It said: %s\n", buf);
return -1;
} else {
sscanf(buf, "+OK %d", &(pc->TotalMsgs));
}
/* - Updated - Mark Hurley - debian4tux@telocity.com
* In compliance with RFC 1725
* which removed the LAST command, any servers
* which follow this spec will return:
* -ERR unimplimented
* We will leave it here for those servers which haven't
* caught up with the spec.
*/
tlscomm_printf(scs, "LAST\r\n");
tlscomm_gets(buf, BUF_SIZE, scs);
if (buf[0] != '+') {
/* it is not an error to receive this according to RFC 1725 */
/* no error should be returned */
pc->UnreadMsgs = pc->TotalMsgs;
// there's also a LIST command... not sure how to make use of it. */
} else {
sscanf(buf, "+OK %d", &read);
pc->UnreadMsgs = pc->TotalMsgs - read;
}
tlscomm_printf(scs, "QUIT\r\n");
tlscomm_close(scs);
return 0;
}
struct msglst *pop_getHeaders( /*@notnull@ */ Pop3 pc)
{
if (pc->headerCache == NULL)
pop3_cacheHeaders(pc);
if (pc->headerCache != NULL)
pc->headerCache->in_use = 1;
return pc->headerCache;
}
int pop3Create(Pop3 pc, const char *str)
{
/* POP3 format: pop3:user:password@server[:port] */
/* new POP3 format: pop3:user password server [port] */
/* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
int i;
int matchedchars;
/* ([^: ]+) user
([^@]+) or ([^ ]+) password
([^: ]+) server
([: ][0-9]+)? optional port
' *' gobbles trailing whitespace before authentication types.
use separate regexes for old and new types to permit
use of '@' in passwords
*/
const char *regexes[] = {
"pop3s?:([^: ]{1,32}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
"pop3s?:([^: ]{1,32}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
// "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^: ]+)( [0-9]+)? *",
// "pop3:([^: ]{1,32}):([^@]{0,32})@([^: ]+)(:[0-9]+)? *",
NULL
};
struct regulo regulos[] = {
{1, PCU.userName, regulo_strcpy},
{2, PCU.password, regulo_strcpy},
{3, PCU.serverName, regulo_strcpy},
{4, &PCU.serverPort, regulo_atoi},
{6, PCU.authList, regulo_strcpy_tolower},
{0, NULL, NULL}
};
if (Relax) {
regexes[0] =
"pop3:([^: ]{1,32}):([^@]{0,32})@([^/: ]+)(:[0-9]+)?( *(.*))?$";
regexes[1] =
"pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^/: ]+)( [0-9]+)?( *(.*))?$";
}
if (strncmp("pop3s:", 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) ? 995 : 110;
PCU.authList[0] = '\0';
for (matchedchars = 0, i = 0;
regexes[i] != NULL && matchedchars <= 0; i++) {
matchedchars = regulo_match(regexes[i], str, regulos);
}
/* failed to match either regex */
if (matchedchars <= 0) {
pc->label[0] = '\0';
POP_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 wmbiff-devel@lists.sourceforge.net with the hostname\n"
" of your mail server.\n", str, matchedchars);
return -1;
}
// grab_authList(str + matchedchars, PCU.authList);
PCU.password_len = strlen(PCU.password);
if (PCU.password[0] == '\0') {
PCU.interactive_password = 1;
} else {
// ENFROB(PCU.password);
}
POP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
POP_DM(pc, DEBUG_INFO, "password is %ld chars long\n",
strlen(PCU.password));
POP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
POP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
POP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
pc->checkMail = pop3CheckMail;
pc->getHeaders = pop_getHeaders;
pc->TotalMsgs = 0;
pc->UnreadMsgs = 0;
pc->OldMsgs = -1;
pc->OldUnreadMsgs = -1;
return 0;
}
#ifdef HAVE_GCRYPT_H
static struct connection_state *authenticate_md5(Pop3 pc, struct connection_state * scs, char *apop_str
__attribute__ ((unused)))
{
char buf[BUF_SIZE];
char buf2[BUF_SIZE];
unsigned char *md5;
gcry_md_hd_t gmh;
gcry_error_t rc;
/* See if MD5 is supported */
tlscomm_printf(scs, "AUTH CRAM-MD5\r\n");
tlscomm_gets(buf, BUF_SIZE, scs);
POP_DM(pc, DEBUG_INFO, "%s", buf);
if (buf[0] != '+' || buf[1] != ' ') {
/* nope, not supported. */
return NULL;
}
Decode_Base64(buf + 2, buf2);
POP_DM(pc, DEBUG_INFO, "CRAM-MD5 challenge: %s\n", buf2);
strcpy(buf, PCU.userName);
strcat(buf, " ");
rc = gcry_md_open(&gmh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
if (rc != 0) {
POP_DM(pc, DEBUG_ERROR, "unable to initialize gcrypt md5.\n");
return NULL;
}
gcry_md_setkey(gmh, PCU.password, strlen(PCU.password));
gcry_md_write(gmh, (unsigned char *) buf2, strlen(buf2));
gcry_md_final(gmh);
md5 = gcry_md_read(gmh, 0);
/* hmac_md5(buf2, strlen(buf2), PCU.password,
strlen(PCU.password), md5); */
Bin2Hex(md5, 16, buf2);
gcry_md_close(gmh);
strcat(buf, buf2);
POP_DM(pc, DEBUG_INFO, "CRAM-MD5 response: %s\n", buf);
Encode_Base64(buf, buf2);
tlscomm_printf(scs, "%s\r\n", buf2);
tlscomm_gets(buf, BUF_SIZE, scs);
if (!strncmp(buf, "+OK", 3))
return scs; /* AUTH successful */
else {
POP_DM(pc, DEBUG_ERROR,
"CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
fprintf(stderr, "It said %s", buf);
return NULL;
}
}
static struct connection_state *authenticate_apop(Pop3 pc, struct connection_state * scs, char *apop_str)
{
gcry_md_hd_t gmh;
gcry_error_t rc;
char buf[BUF_SIZE];
unsigned char *md5;
if (apop_str[0] == '\0') {
/* server doesn't support apop. */
return (NULL);
}
POP_DM(pc, DEBUG_INFO, "APOP challenge: %s\n", apop_str);
strcat(apop_str, PCU.password);
rc = gcry_md_open(&gmh, GCRY_MD_MD5, 0);
if (rc != 0) {
POP_DM(pc, DEBUG_ERROR, "unable to initialize gcrypt md5.\n");
return NULL;
}
gcry_md_write(gmh, (unsigned char *) apop_str, strlen(apop_str));
gcry_md_final(gmh);
md5 = gcry_md_read(gmh, 0);
Bin2Hex(md5, 16, buf);
gcry_md_close(gmh);
POP_DM(pc, DEBUG_INFO, "APOP response: %s %s\n", PCU.userName, buf);
tlscomm_printf(scs, "APOP %s %s\r\n", PCU.userName, buf);
tlscomm_gets(buf, BUF_SIZE, scs);
if (!strncmp(buf, "+OK", 3))
return scs; /* AUTH successful */
else {
POP_DM(pc, DEBUG_ERROR,
"APOP AUTH failed for user '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
POP_DM(pc, DEBUG_INFO, "It said %s", buf);
return NULL;
}
}
#endif /* HAVE_GCRYPT_H */
/*@null@*/
static struct connection_state *authenticate_plaintext( /*@notnull@ */ Pop3 pc,
struct connection_state * scs, char *apop_str
__attribute__ ((unused)))
{
char buf[BUF_SIZE];
tlscomm_printf(scs, "USER %s\r\n", PCU.userName);
if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
POP_DM(pc, DEBUG_ERROR,
"Error reading from server authenticating '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
return NULL;
}
if (buf[0] != '+') {
POP_DM(pc, DEBUG_ERROR,
"Failed user name when authenticating '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
/* deb #128863 might be easier if we printed: */
POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
buf);
return NULL;
};
tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
POP_DM(pc, DEBUG_ERROR,
"Error reading from server (2) authenticating '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
return NULL;
}
if (strncmp(buf, "-ERR [AUTH] Password required", 20) == 0) {
if (PCU.interactive_password) {
PCU.password[0] = '\0';
ask_user_for_password(pc, 1); /* 1=overwrite the cache */
tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
POP_DM(pc, DEBUG_ERROR,
"Error reading from server (2) authenticating '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
return NULL;
}
}
}
if (buf[0] != '+') {
POP_DM(pc, DEBUG_ERROR,
"Failed password when authenticating '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
buf);
return NULL;
};
return scs;
}
void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc)
{
char buf[BUF_SIZE];
struct connection_state *scs;
int i;
if (pc->headerCache != NULL) {
/* decrement the reference count, and free our version */
imap_releaseHeaders(pc, pc->headerCache);
pc->headerCache = NULL;
}
POP_DM(pc, DEBUG_INFO, "working headers\n");
/* login the server */
scs = pop3Login(pc);
if (scs == NULL)
return;
/* pc->UnreadMsgs = pc->TotalMsgs - read; */
pc->headerCache = NULL;
for (i = pc->TotalMsgs - pc->UnreadMsgs + 1; i <= pc->TotalMsgs; ++i) {
struct msglst *m;
m = malloc(sizeof(struct msglst));
m->subj[0] = '\0';
m->from[0] = '\0';
POP_DM(pc, DEBUG_INFO, "search: %s", buf);
tlscomm_printf(scs, "TOP %i 0\r\n", i);
while (tlscomm_gets(buf, 256, scs) && buf[0] != '.') {
if (!strncasecmp(buf, "From: ", 6)) {
/* manage the from in heads */
strncpy(m->from, buf + 6, FROM_LEN - 1);
m->from[FROM_LEN - 1] = '\0';
} else if (!strncasecmp(buf, "Subject: ", 9)) {
/* manage subject */
strncpy(m->subj, buf + 9, SUBJ_LEN - 1);
m->subj[SUBJ_LEN - 1] = '\0';
}
if (!m->subj[0]) {
strncpy(m->subj, "[NO SUBJECT]", 14);
}
if (!m->from[0]) {
strncpy(m->from, "[ANONYMOUS]", 14);
}
}
m->next = pc->headerCache;
pc->headerCache = m;
pc->headerCache->in_use = 0;
}
tlscomm_printf(scs, "QUIT\r\n");
tlscomm_close(scs);
}
/* vim:set ts=4: */
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;
POP_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);
}
}
}
}