#include "config.h"
#include <stdio.h>
#include <setjmp.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#if defined(STDC_HEADERS)
#include <stdlib.h>
#include <limits.h>
#endif
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif
#if defined(HAVE_SYS_ITIMER_H)
#include <sys/itimer.h>
#endif
#include <signal.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NET_SOCKET_H
#include <net/socket.h>
#endif
#include <netdb.h>
#ifdef HAVE_PKG_hesiod
#include <hesiod.h>
#endif
#include <langinfo.h>
#include "kerberos.h"
#ifdef KERBEROS_V4
#include <netinet/in.h>
#endif
#include "i18n.h"
#include "socket.h"
#include "fetchmail.h"
#include "getaddrinfo.h"
#include "tunable.h"
#include "sdump.h"
#define THROW_TIMEOUT 1
#define MSGLEN_UNKNOWN 0
#define MSGLEN_INVALID -1
#define MSGLEN_TOOLARGE -2
#define MSGLEN_OLD -3
int pass;
int stage;
int phase;
int batchcount;
flag peek_capable;
int mailserver_socket_temp = -1;
struct addrinfo *ai0, *ai1;
static volatile int timeoutcount = 0;
static volatile int idletimeout = 0;
static sigjmp_buf restart;
int is_idletimeout(void)
{
return idletimeout;
}
void resetidletimeout(void)
{
idletimeout = 0;
}
void set_timeout(int timeleft)
{
#if !defined(__EMX__) && !defined(__BEOS__)
struct itimerval ntimeout;
if (timeleft == 0)
timeoutcount = 0;
ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_usec = 0;
ntimeout.it_value.tv_sec = timeleft;
ntimeout.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &ntimeout, (struct itimerval *)NULL);
#endif
}
static RETSIGTYPE timeout_handler (int signal)
{
(void)signal;
if(stage != STAGE_IDLE) {
timeoutcount++;
siglongjmp(restart, THROW_TIMEOUT);
} else
idletimeout = 1;
}
#define CLEANUP_TIMEOUT 60
static int cleanupSockClose (int fd)
{
int scerror;
SIGHANDLERTYPE alrmsave;
alrmsave = set_signal_handler(SIGALRM, null_signal_handler);
set_timeout(CLEANUP_TIMEOUT);
scerror = SockClose(fd);
set_timeout(0);
set_signal_handler(SIGALRM, alrmsave);
return (scerror);
}
#ifdef KERBEROS_V4
static int kerberos_auth(socket, canonical, principal)
int socket;
char *canonical;
char *principal;
{
KTEXT ticket;
MSG_DAT msg_data;
CREDENTIALS cred;
Key_schedule schedule;
int rem;
char * prin_copy = (char *) NULL;
char * prin = (char *) NULL;
char * inst = (char *) NULL;
char * realm = (char *) NULL;
if (principal != (char *)NULL && *principal)
{
char *cp;
prin = prin_copy = xstrdup(principal);
for (cp = prin_copy; *cp && *cp != '.'; ++cp)
;
if (*cp)
{
*cp++ = '\0';
inst = cp;
while (*cp && *cp != '@')
++cp;
if (*cp)
{
*cp++ = '\0';
realm = cp;
}
}
}
ticket = xmalloc(sizeof (KTEXT_ST));
rem = (krb_sendauth (0L, socket, ticket,
prin ? prin : "pop",
inst ? inst : canonical,
realm ? realm : ((char *) (krb_realmofhost (canonical))),
((unsigned long) 0),
(&msg_data),
(&cred),
(schedule),
((struct sockaddr_in *) 0),
((struct sockaddr_in *) 0),
"KPOPV0.1"));
free(ticket);
if (prin_copy)
{
free(prin_copy);
}
if (rem != KSUCCESS)
{
report(stderr, GT_("kerberos error %s\n"), (krb_get_err_text (rem)));
return (PS_AUTHFAIL);
}
return (0);
}
#endif
#ifdef KERBEROS_V5
static int kerberos5_auth(socket, canonical)
int socket;
const char *canonical;
{
krb5_error_code retval;
krb5_context context;
krb5_ccache ccdef;
krb5_principal client = NULL, server = NULL;
krb5_error *err_ret = NULL;
krb5_auth_context auth_context = NULL;
krb5_init_context(&context);
krb5_auth_con_init(context, &auth_context);
if ((retval = krb5_cc_default(context, &ccdef))) {
report(stderr, "krb5_cc_default: %s\n", error_message(retval));
return(PS_ERROR);
}
if ((retval = krb5_cc_get_principal(context, ccdef, &client))) {
report(stderr, "krb5_cc_get_principal: %s\n", error_message(retval));
return(PS_ERROR);
}
if ((retval = krb5_sname_to_principal(context, canonical, "pop",
KRB5_NT_UNKNOWN,
&server))) {
report(stderr, "krb5_sname_to_principal: %s\n", error_message(retval));
return(PS_ERROR);
}
retval = krb5_sendauth(context, &auth_context, (krb5_pointer) &socket,
"KPOPV1.0", client, server,
AP_OPTS_MUTUAL_REQUIRED,
NULL,
0,
ccdef,
&err_ret, 0,
NULL);
krb5_free_principal(context, server);
krb5_free_principal(context, client);
krb5_auth_con_free(context, auth_context);
if (retval) {
#ifdef HEIMDAL
if (err_ret && err_ret->e_text) {
char *t = err_ret->e_text;
char *tt = sdump(t, strlen(t));
report(stderr, GT_("krb5_sendauth: %s [server says '%s']\n"),
error_message(retval), tt);
free(tt);
#else
if (err_ret && err_ret->text.length) {
char *tt = sdump(err_ret->text.data, err_ret->text.length);
report(stderr, GT_("krb5_sendauth: %s [server says '%s']\n"),
error_message(retval), tt);
free(tt);
#endif
krb5_free_error(context, err_ret);
} else
report(stderr, "krb5_sendauth: %s\n", error_message(retval));
return(PS_ERROR);
}
return 0;
}
#endif
static void clean_skipped_list(struct idlist **skipped_list)
{
struct idlist *current=NULL, *prev=NULL, *tmp=NULL, *head=NULL;
prev = current = head = *skipped_list;
if (!head)
return;
do
{
if (current && current->val.status.mark == 0)
{
if (current == head)
{
head = current->next;
if (current->id) free(current->id);
free(current);
prev = current = head;
}
else
{
tmp = current->next;
prev->next = tmp;
if (current->id) free(current->id);
free(current);
current = tmp;
}
}
else
{
prev = current;
current = current->next;
}
} while(current);
*skipped_list = head;
}
static void send_size_warnings(struct query *ctl)
{
int size, nbr;
int msg_to_send = FALSE;
struct idlist *head=NULL, *current=NULL;
int max_warning_poll_count;
head = ctl->skipped;
if (!head)
return;
for (current = head; current; current = current->next)
if (current->val.status.num == 0 && current->val.status.mark)
msg_to_send = TRUE;
if (!msg_to_send)
return;
if (open_warning_by_mail(ctl))
return;
stuff_warning(iana_charset, ctl,
GT_("Subject: Fetchmail oversized-messages warning"));
stuff_warning(NULL, ctl, "%s", "");
if (ctl->limitflush)
stuff_warning(NULL, ctl,
GT_("The following oversized messages were deleted on server %s account %s:"),
ctl->server.pollname, ctl->remotename);
else
stuff_warning(NULL, ctl,
GT_("The following oversized messages remain on server %s account %s:"),
ctl->server.pollname, ctl->remotename);
stuff_warning(NULL, ctl, "%s", "");
if (run.poll_interval == 0)
max_warning_poll_count = 0;
else
max_warning_poll_count = ctl->warnings/run.poll_interval;
for (current = head; current; current = current->next)
{
if (current->val.status.num == 0 && current->val.status.mark)
{
nbr = current->val.status.mark;
size = atoi(current->id);
if (ctl->limitflush)
stuff_warning(NULL, ctl,
ngettext(" %d message %d octets long deleted by fetchmail.",
" %d messages %d octets long deleted by fetchmail.", nbr),
nbr, size);
else
stuff_warning(NULL, ctl,
ngettext(" %d message %d octets long skipped by fetchmail.",
" %d messages %d octets long skipped by fetchmail.", nbr),
nbr, size);
}
current->val.status.num++;
current->val.status.mark = 0;
if (current->val.status.num >= max_warning_poll_count)
current->val.status.num = 0;
}
stuff_warning(NULL, ctl, "%s", "");
close_warning_by_mail(ctl, (struct msgblk *)NULL);
}
static void mark_oversized(struct query *ctl, int size)
{
struct idlist *current=NULL, *tmp=NULL;
char sizestr[32];
int cnt;
snprintf(sizestr, sizeof(sizestr), "%d", size);
current = ctl->skipped;
cnt = current ? current->val.status.num : 0;
if (current && (tmp = str_in_list(¤t, sizestr, FALSE)))
{
tmp->val.status.mark++;
}
else
{
tmp = save_str(&ctl->skipped, sizestr, 1);
tmp->val.status.num = cnt;
}
}
static int eat_trailer(int sock, struct query *ctl)
{
if (outlevel >= O_VERBOSE && want_progress()) fputc('\n', stdout);
return (ctl->server.base_protocol->trail)(sock, ctl, tag);
}
static int fetch_messages(int mailserver_socket, struct query *ctl,
int count, int **msgsizes, int maxfetch,
int *fetches, int *dispatches, int *deletions)
{
flag force_retrieval;
int num, firstnum = 1, lastnum = 0, err, len;
int fetchsizelimit = ctl->fetchsizelimit;
int msgsize;
int initialfetches = *fetches;
if (ctl->server.base_protocol->getpartialsizes && NUM_NONZERO(fetchsizelimit))
{
switch (ctl->server.protocol)
{
case P_POP3: case P_APOP: case P_RPOP:
fetchsizelimit = 1;
}
xfree(*msgsizes);
*msgsizes = (int *)xmalloc(sizeof(int) * fetchsizelimit);
}
force_retrieval = !peek_capable && (ctl->errcount > 0);
for (num = 1; num <= count; num++)
{
flag suppress_delete = FALSE;
flag suppress_forward = FALSE;
flag suppress_readbody = FALSE;
flag retained = FALSE;
int msgcode = MSGLEN_UNKNOWN;
if (ctl->fetchall || force_retrieval) {
} else {
if (ctl->server.base_protocol->is_old && (ctl->server.base_protocol->is_old)(mailserver_socket,ctl,num)) {
msgcode = MSGLEN_OLD;
}
}
if (msgcode == MSGLEN_OLD)
{
if ( (outlevel >= O_VERBOSE) ||
(outlevel > O_SILENT && !run.use_syslog)
)
{
report_build(stdout,
GT_("skipping message %s@%s:%d"),
ctl->remotename, ctl->server.truename, num);
}
goto flagthemail;
}
if (ctl->server.base_protocol->getpartialsizes && NUM_NONZERO(fetchsizelimit) &&
lastnum < num)
{
int i;
int oldstage = stage;
firstnum = num;
lastnum = num + fetchsizelimit - 1;
if (lastnum > count)
lastnum = count;
for (i = 0; i < fetchsizelimit; i++)
(*msgsizes)[i] = 0;
stage = STAGE_GETSIZES;
err = (ctl->server.base_protocol->getpartialsizes)(mailserver_socket, num, lastnum, *msgsizes);
if (err != 0) {
return err;
}
stage = oldstage;
}
msgsize = *msgsizes ? (*msgsizes)[num-firstnum] : 0;
if (NUM_NONZERO(ctl->limit) && (msgsize > ctl->limit))
msgcode = MSGLEN_TOOLARGE;
if (msgcode < 0)
{
if (msgcode == MSGLEN_TOOLARGE)
{
mark_oversized(ctl, msgsize);
if (!ctl->limitflush)
suppress_delete = TRUE;
}
if (outlevel > O_SILENT)
{
report_build(stdout,
GT_("skipping message %s@%s:%d (%d octets)"),
ctl->remotename, ctl->server.truename, num,
msgsize);
switch (msgcode)
{
case MSGLEN_INVALID:
report_build(stdout, GT_(" (length -1)"));
break;
case MSGLEN_TOOLARGE:
report_build(stdout, GT_(" (oversized)"));
break;
}
}
}
else
{
flag wholesize = !ctl->server.base_protocol->fetch_body;
flag separatefetchbody = (ctl->server.base_protocol->fetch_body) ? TRUE : FALSE;
err = (ctl->server.base_protocol->fetch_headers)(mailserver_socket,ctl,num, &len);
if (err == PS_TRANSIENT)
{
report(stdout,
GT_("couldn't fetch headers, message %s@%s:%d (%d octets)\n"),
ctl->remotename, ctl->server.truename, num,
msgsize);
continue;
}
else if (err != 0)
return(err);
if (len == -1)
{
len = msgsize;
wholesize = TRUE;
}
if (outlevel > O_SILENT)
{
report_build(stdout, GT_("reading message %s@%s:%d of %d"),
ctl->remotename, ctl->server.truename,
num, count);
if (len > 0)
report_build(stdout, wholesize ? GT_(" (%d octets)")
: GT_(" (%d header octets)"), len);
if (want_progress()) {
report_flush(stdout);
putchar(' ');
}
}
err = readheaders(mailserver_socket, len, msgsize,
ctl, num,
separatefetchbody ? 0 : &suppress_readbody);
if (err == PS_RETAINED)
suppress_forward = suppress_delete = retained = TRUE;
else if (err == PS_TRANSIENT)
suppress_delete = suppress_forward = TRUE;
else if (err == PS_REFUSED)
suppress_forward = TRUE;
else if (err)
return(err);
if (separatefetchbody && ctl->server.base_protocol->trail)
{
err = eat_trailer(mailserver_socket, ctl);
if (err) return(err);
}
if (separatefetchbody && suppress_forward)
suppress_readbody = TRUE;
if (!suppress_readbody)
{
if (separatefetchbody)
{
len = -1;
if ((err=(ctl->server.base_protocol->fetch_body)(mailserver_socket,ctl,num,&len)))
return(err);
if (len == -1)
len = msgsize - msgblk.msglen;
if (!wholesize) {
if (outlevel > O_SILENT)
report_build(stdout,
GT_(" (%d body octets)"), len);
if (want_progress()) {
report_flush(stdout);
putchar(' ');
}
}
}
err = readbody(mailserver_socket,
ctl,
!suppress_forward,
len);
if (err == PS_TRANSIENT)
suppress_delete = suppress_forward = TRUE;
else if (err)
return(err);
if (ctl->server.base_protocol->trail) {
err = eat_trailer(mailserver_socket, ctl);
if (err) return(err);
}
}
if (!suppress_forward)
(*dispatches)++;
if (msgblk.msglen != msgsize)
{
if (outlevel >= O_DEBUG)
report(stdout,
GT_("message %s@%s:%d was not the expected length (%d actual != %d expected)\n"),
ctl->remotename, ctl->server.truename, num,
msgblk.msglen, msgsize);
}
if (!close_sink(ctl, &msgblk, !suppress_forward))
{
ctl->errcount++;
suppress_delete = TRUE;
}
if (!retained)
(*fetches)++;
}
flagthemail:
if (suppress_forward)
suppress_delete = suppress_delete || run.softbounce;
if (retained)
{
if (outlevel > O_SILENT)
report_complete(stdout, GT_(" retained\n"));
}
else if (ctl->server.base_protocol->delete_msg
&& !suppress_delete
&& ((msgcode >= 0 && !ctl->keep)
|| (msgcode == MSGLEN_OLD && ctl->flush)
|| (msgcode == MSGLEN_TOOLARGE && ctl->limitflush)))
{
(*deletions)++;
if (outlevel > O_SILENT)
report_complete(stdout, GT_(" flushed\n"));
err = (ctl->server.base_protocol->delete_msg)(mailserver_socket, ctl, num);
if (err != 0)
return(err);
}
else
{
if ( (outlevel >= O_VERBOSE) ||
(outlevel > O_SILENT && (!run.use_syslog || msgcode != MSGLEN_OLD))
)
report_complete(stdout, GT_(" not flushed\n"));
if (ctl->server.base_protocol->mark_seen
&& !suppress_delete
&& (msgcode >= 0 && ctl->keep))
{
err = (ctl->server.base_protocol->mark_seen)(mailserver_socket, ctl, num);
if (err != 0)
return(err);
}
}
if (maxfetch && maxfetch <= *fetches && num < count)
{
int remcount = count - (*fetches - initialfetches);
report(stdout,
ngettext("fetchlimit %d reached; %d message left on server %s account %s\n",
"fetchlimit %d reached; %d messages left on server %s account %s\n", remcount),
maxfetch, remcount, ctl->server.truename, ctl->remotename);
return(PS_MAXFETCH);
}
}
return(PS_SUCCESS);
}
static int do_session(
struct query *ctl,
const struct method *proto,
const int maxfetch)
{
static int *msgsizes;
volatile int err, mailserver_socket = -1;
int tmperr;
int deletions = 0, js;
const char *msg;
SIGHANDLERTYPE alrmsave;
ctl->server.base_protocol = proto;
msgsizes = NULL;
pass = 0;
err = 0;
init_transact(proto);
alrmsave = set_signal_handler(SIGALRM, timeout_handler);
mytimeout = ctl->server.timeout;
if ((js = sigsetjmp(restart,1)))
{
sigset_t allsigs;
sigfillset(&allsigs);
sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
if (ai0) {
fm_freeaddrinfo(ai0); ai0 = NULL;
}
if (ai1) {
fm_freeaddrinfo(ai1); ai1 = NULL;
}
if (js == THROW_TIMEOUT)
{
if (phase == OPEN_WAIT)
report(stdout,
GT_("timeout after %d seconds waiting to connect to server %s.\n"),
ctl->server.timeout, ctl->server.pollname);
else if (phase == SERVER_WAIT)
report(stdout,
GT_("timeout after %d seconds waiting for server %s.\n"),
ctl->server.timeout, ctl->server.pollname);
else if (phase == FORWARDING_WAIT)
report(stdout,
GT_("timeout after %d seconds waiting for %s.\n"),
ctl->server.timeout,
ctl->mda ? "MDA" : "SMTP");
else if (phase == LISTENER_WAIT)
report(stdout,
GT_("timeout after %d seconds waiting for listener to respond.\n"), ctl->server.timeout);
else
report(stdout,
GT_("timeout after %d seconds.\n"), ctl->server.timeout);
if (timeoutcount > MAX_TIMEOUTS
&& !open_warning_by_mail(ctl))
{
stuff_warning(iana_charset, ctl,
GT_("Subject: fetchmail sees repeated timeouts"));
stuff_warning(NULL, ctl, "%s", "");
stuff_warning(NULL, ctl,
GT_("Fetchmail saw more than %d timeouts while attempting to get mail from %s@%s.\n"),
MAX_TIMEOUTS,
ctl->remotename, ctl->server.truename);
stuff_warning(NULL, ctl,
GT_("This could mean that your mailserver is stuck, or that your SMTP\n" \
"server is wedged, or that your mailbox file on the server has been\n" \
"corrupted by a server error. You can run `fetchmail -v -v' to\n" \
"diagnose the problem.\n\n" \
"Fetchmail won't poll this mailbox again until you restart it.\n"));
close_warning_by_mail(ctl, (struct msgblk *)NULL);
ctl->wedged = TRUE;
}
}
err = PS_SOCKET;
goto cleanUp;
}
else
{
char buf[MSGBUFSIZE+1], *realhost;
int count, newm, bytes;
int fetches, dispatches, oldphase;
struct idlist *idp;
if (ctl->preconnect && (err = system(ctl->preconnect)))
{
if (WIFSIGNALED(err))
report(stderr,
GT_("pre-connection command terminated with signal %d\n"), WTERMSIG(err));
else
report(stderr,
GT_("pre-connection command failed with status %d\n"), WEXITSTATUS(err));
err = PS_SYNTAX;
goto closeUp;
}
oldphase = phase;
phase = OPEN_WAIT;
set_timeout(mytimeout);
#ifdef HAVE_PKG_hesiod
if (!strcasecmp(ctl->server.queryname, "hesiod")) {
struct hes_postoffice *hes_p;
hes_p = hes_getmailhost(ctl->remotename);
if (hes_p != NULL && strcmp(hes_p->po_type, "POP") == 0) {
free(ctl->server.queryname);
ctl->server.queryname = xstrdup(hes_p->po_host);
if (ctl->server.via)
free(ctl->server.via);
ctl->server.via = xstrdup(hes_p->po_host);
} else {
report(stderr,
GT_("couldn't find HESIOD pobox for %s\n"),
ctl->remotename);
}
}
#endif
if (ctl->server.dns && !ctl->server.trueaddr)
{
if (ctl->server.lead_server)
{
char *leadname = ctl->server.lead_server->truename;
if (!leadname)
{
report(stderr, GT_("Lead server has no name.\n"));
err = PS_DNS;
set_timeout(0);
phase = oldphase;
goto closeUp;
}
xfree(ctl->server.truename);
ctl->server.truename = xstrdup(leadname);
}
else
{
struct addrinfo hints, *res;
int error;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_CANONNAME;
#ifdef AI_ADDRCONFIG
hints.ai_flags |= AI_ADDRCONFIG;
#endif
error = fm_getaddrinfo(ctl->server.queryname, NULL, &hints, &res);
if (error)
{
report(stderr,
GT_("couldn't find canonical DNS name of %s (%s): %s\n"),
ctl->server.pollname, ctl->server.queryname,
gai_strerror(error));
err = PS_DNS;
set_timeout(0);
phase = oldphase;
goto closeUp;
}
else
{
xfree(ctl->server.truename);
if (res->ai_canonname != NULL) {
ctl->server.truename = xstrdup(res->ai_canonname);
} else {
ctl->server.truename = xstrdup(ctl->server.queryname);
}
ctl->server.trueaddr = (struct sockaddr *)xmalloc(res->ai_addrlen);
ctl->server.trueaddr_len = res->ai_addrlen;
memcpy(ctl->server.trueaddr, res->ai_addr, res->ai_addrlen);
fm_freeaddrinfo(res);
}
}
}
realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
if (ctl->server.plugin)
(void)sleep(1);
if ((mailserver_socket = SockOpen(realhost,
ctl->server.service ? ctl->server.service : ( ctl->use_ssl ? ctl->server.base_protocol->sslservice : ctl->server.base_protocol->service ),
ctl->server.plugin, &ai0)) == -1)
{
char errbuf[BUFSIZ];
int err_no = errno;
if (!((err_no == EHOSTUNREACH || err_no == ENETUNREACH)
&& run.poll_interval))
{
report_build(stderr, GT_("%s connection to %s failed"),
ctl->server.base_protocol->name, ctl->server.pollname);
strlcpy(errbuf, strerror(err_no), sizeof(errbuf));
report_complete(stderr, ": %s\n", errbuf);
#ifdef __UNUSED__
if (open_warning_by_mail(ctl) == 0)
{
stuff_warning(iana_charset, ctl,
GT_("Subject: Fetchmail unreachable-server warning."));
stuff_warning(NULL, ctl, "");
stuff_warning(NULL, ctl, GT_("Fetchmail could not reach the mail server %s:"),
ctl->server.pollname);
stuff_warning(NULL, ctl, errbuf, ctl->server.pollname);
close_warning_by_mail(ctl, (struct msgblk *)NULL);
}
#endif
}
err = PS_SOCKET;
set_timeout(0);
phase = oldphase;
goto closeUp;
}
#ifdef SSL_ENABLE
mailserver_socket_temp = mailserver_socket;
set_timeout(mytimeout);
if (ctl->use_ssl &&
SSLOpen(mailserver_socket, ctl->sslcert, ctl->sslkey,
ctl->sslproto, ctl->sslcertck,
ctl->sslcertfile, ctl->sslcertpath,
ctl->sslfingerprint, ctl->sslcommonname ?
ctl->sslcommonname : realhost, ctl->server.pollname,
&ctl->remotename) == -1)
{
report(stderr, GT_("SSL connection failed.\n"));
err = PS_SOCKET;
goto cleanUp;
}
mailserver_socket_temp = -1;
#endif
set_timeout(0);
phase = oldphase;
#ifdef KERBEROS_V4
if (ctl->server.authenticate == A_KERBEROS_V4 && (strcasecmp(proto->name,"IMAP") != 0))
{
set_timeout(mytimeout);
err = kerberos_auth(mailserver_socket, ctl->server.truename,
ctl->server.principal);
set_timeout(0);
if (err != 0)
goto cleanUp;
}
#endif
#ifdef KERBEROS_V5
if (ctl->server.authenticate == A_KERBEROS_V5)
{
set_timeout(mytimeout);
err = kerberos5_auth(mailserver_socket, ctl->server.truename);
set_timeout(0);
if (err != 0)
goto cleanUp;
}
#endif
err = (ctl->server.base_protocol->parse_response)(mailserver_socket, buf);
if (err != 0)
goto cleanUp;
stage = STAGE_GETAUTH;
if (ctl->server.base_protocol->getauth)
{
set_timeout(mytimeout);
err = (ctl->server.base_protocol->getauth)(mailserver_socket, ctl, buf);
set_timeout(0);
if (err != 0)
{
if (err == PS_LOCKBUSY)
report(stderr, GT_("Lock-busy error on %s@%s\n"),
ctl->remotename,
ctl->server.truename);
else if (err == PS_SERVBUSY)
report(stderr, GT_("Server busy error on %s@%s\n"),
ctl->remotename,
ctl->server.truename);
else if (err == PS_AUTHFAIL)
{
report(stderr, GT_("Authorization failure on %s@%s%s\n"),
ctl->remotename,
ctl->server.truename,
(ctl->wehaveauthed ? GT_(" (previously authorized)") : "")
);
if (ctl->server.authenticate == A_ANY && !ctl->wehaveauthed) {
report(stderr, GT_("For help, see http://www.fetchmail.info/fetchmail-FAQ.html#R15\n"));
}
if (run.poll_interval
&& !ctl->wehavesentauthnote
&& ((ctl->wehaveauthed && ++ctl->authfailcount >= 10)
|| (!ctl->wehaveauthed && ++ctl->authfailcount >= 3))
&& !open_warning_by_mail(ctl))
{
ctl->wehavesentauthnote = 1;
stuff_warning(iana_charset, ctl,
GT_("Subject: fetchmail authentication failed on %s@%s"),
ctl->remotename, ctl->server.truename);
stuff_warning(NULL, ctl, "%s", "");
stuff_warning(NULL, ctl,
GT_("Fetchmail could not get mail from %s@%s.\n"),
ctl->remotename,
ctl->server.truename);
if (ctl->wehaveauthed) {
stuff_warning(NULL, ctl, GT_("\
The attempt to get authorization failed.\n\
Since we have already succeeded in getting authorization for this\n\
connection, this is probably another failure mode (such as busy server)\n\
that fetchmail cannot distinguish because the server didn't send a useful\n\
error message."));
stuff_warning(NULL, ctl, GT_("\
\n\
However, if you HAVE changed your account details since starting the\n\
fetchmail daemon, you need to stop the daemon, change your configuration\n\
of fetchmail, and then restart the daemon.\n\
\n\
The fetchmail daemon will continue running and attempt to connect\n\
at each cycle. No future notifications will be sent until service\n\
is restored."));
} else {
stuff_warning(NULL, ctl, GT_("\
The attempt to get authorization failed.\n\
This probably means your password is invalid, but some servers have\n\
other failure modes that fetchmail cannot distinguish from this\n\
because they don't send useful error messages on login failure.\n\
\n\
The fetchmail daemon will continue running and attempt to connect\n\
at each cycle. No future notifications will be sent until service\n\
is restored."));
}
close_warning_by_mail(ctl, (struct msgblk *)NULL);
}
}
else if (err == PS_REPOLL)
{
if (outlevel >= O_VERBOSE)
report(stderr, GT_("Repoll immediately on %s@%s\n"),
ctl->remotename,
ctl->server.truename);
}
else
report(stderr, GT_("Unknown login or authentication error on %s@%s\n"),
ctl->remotename,
ctl->server.truename);
goto cleanUp;
}
else
{
ctl->wehaveauthed = 1;
if (ctl->wehavesentauthnote)
{
ctl->wehavesentauthnote = 0;
report(stderr,
GT_("Authorization OK on %s@%s\n"),
ctl->remotename,
ctl->server.truename);
if (!open_warning_by_mail(ctl))
{
stuff_warning(iana_charset, ctl,
GT_("Subject: fetchmail authentication OK on %s@%s"),
ctl->remotename, ctl->server.truename);
stuff_warning(NULL, ctl, "%s", "");
stuff_warning(NULL, ctl,
GT_("Fetchmail was able to log into %s@%s.\n"),
ctl->remotename,
ctl->server.truename);
stuff_warning(NULL, ctl,
GT_("Service has been restored.\n"));
close_warning_by_mail(ctl, (struct msgblk *)NULL);
}
}
ctl->authfailcount = 0;
}
}
ctl->errcount = fetches = 0;
for (idp = ctl->mailboxes; idp; idp = idp->next)
{
ctl->folder = idp->id;
pass = 0;
do {
dispatches = 0;
++pass;
mytimeout = ctl->server.timeout;
if (outlevel >= O_DEBUG)
{
if (idp->id)
report(stdout, GT_("selecting or re-polling folder %s\n"), idp->id);
else
report(stdout, GT_("selecting or re-polling default folder\n"));
}
stage = STAGE_GETRANGE;
err = (ctl->server.base_protocol->getrange)(mailserver_socket, ctl, idp->id, &count, &newm, &bytes);
if (err != 0)
goto cleanUp;
if (idp->id)
(void) snprintf(buf, sizeof(buf),
GT_("%s at %s (folder %s)"),
ctl->remotename, ctl->server.pollname, idp->id);
else
(void) snprintf(buf, sizeof(buf), GT_("%s at %s"),
ctl->remotename, ctl->server.pollname);
if (outlevel > O_SILENT)
{
if (count == -1)
report(stdout, GT_("Polling %s\n"), ctl->server.truename);
else if (count != 0)
{
if (newm != -1 && (count - newm) > 0)
report_build(stdout, ngettext("%d message (%d %s) for %s", "%d messages (%d %s) for %s", (unsigned long)count),
count,
count - newm,
ngettext("seen", "seen", (unsigned long)count-newm),
buf);
else
report_build(stdout, ngettext("%d message for %s",
"%d messages for %s",
count),
count, buf);
if (bytes == -1)
report_complete(stdout, ".\n");
else
report_complete(stdout, GT_(" (%d octets).\n"), bytes);
}
else
{
if (pass == 1 && (run.poll_interval == 0 || outlevel >= O_VERBOSE))
report(stdout, GT_("No mail for %s\n"), buf);
}
}
if (count == 0)
break;
if (check_only)
{
if (newm == -1 || ctl->fetchall)
newm = count;
fetches = newm;
}
else if (count > 0)
{
int i;
if ((unsigned)count > INT_MAX/sizeof(int))
{
report(stderr, GT_("bogus message count!"));
err = PS_PROTOCOL;
goto cleanUp;
}
if (proto->getsizes &&
!(proto->getpartialsizes && NUM_NONZERO(ctl->fetchsizelimit)))
{
xfree(msgsizes);
msgsizes = (int *)xmalloc(sizeof(int) * count);
for (i = 0; i < count; i++)
msgsizes[i] = 0;
stage = STAGE_GETSIZES;
err = (proto->getsizes)(mailserver_socket, count, msgsizes);
if (err != 0)
goto cleanUp;
if (bytes == -1)
{
bytes = 0;
for (i = 0; i < count; i++)
bytes += msgsizes[i];
}
}
stage = STAGE_FETCH;
err = fetch_messages(mailserver_socket, ctl,
count, &msgsizes,
maxfetch,
&fetches, &dispatches, &deletions);
if (err != PS_SUCCESS && err != PS_MAXFETCH)
goto cleanUp;
if (!check_only && ctl->skipped
&& run.poll_interval > 0 && !nodetach)
{
clean_skipped_list(&ctl->skipped);
send_size_warnings(ctl);
}
}
if (ctl->server.base_protocol->end_mailbox_poll)
{
tmperr = (ctl->server.base_protocol->end_mailbox_poll)(mailserver_socket, ctl);
if (tmperr) {
err = tmperr;
goto cleanUp;
}
}
if (maxfetch && maxfetch <= fetches)
goto no_error;
} while
(ctl->server.base_protocol->retry && (dispatches || ctl->idle) && !ctl->errcount);
}
no_error:
stage = STAGE_LOGOUT;
tmperr = (ctl->server.base_protocol->logout_cmd)(mailserver_socket, ctl);
if (tmperr != PS_SUCCESS)
err = tmperr;
else if (err == PS_SUCCESS && fetches == 0)
err = PS_NOMAIL;
smtp_close(ctl, ctl->server.protocol != P_ODMR);
cleanupSockClose(mailserver_socket);
goto closeUp;
cleanUp:
if (err != 0 && err != PS_SOCKET && err != PS_REPOLL)
{
stage = STAGE_LOGOUT;
(ctl->server.base_protocol->logout_cmd)(mailserver_socket, ctl);
}
release_sink(ctl);
smtp_close(ctl, 0);
if (mailserver_socket != -1) {
cleanupSockClose(mailserver_socket);
}
if (mailserver_socket_temp != -1) {
cleanupSockClose(mailserver_socket_temp);
mailserver_socket_temp = -1;
}
}
msg = NULL;
switch (err)
{
case PS_SOCKET:
msg = GT_("socket");
break;
case PS_SYNTAX:
msg = GT_("missing or bad RFC822 header");
break;
case PS_IOERR:
msg = GT_("MDA");
break;
case PS_ERROR:
msg = GT_("client/server synchronization");
break;
case PS_PROTOCOL:
msg = GT_("client/server protocol");
break;
case PS_LOCKBUSY:
msg = GT_("lock busy on server");
break;
case PS_SMTP:
msg = GT_("SMTP transaction");
break;
case PS_DNS:
msg = GT_("DNS lookup");
break;
case PS_UNDEFINED:
msg = GT_("undefined");
break;
}
if (msg) {
if (phase == FORWARDING_WAIT || phase == LISTENER_WAIT
|| err == PS_SMTP)
report(stderr, GT_("%s error while fetching from %s@%s and delivering to SMTP host %s\n"),
msg, ctl->remotename, ctl->server.pollname,
ctl->smtphost ? ctl->smtphost : GT_("unknown"));
else
report(stderr, GT_("%s error while fetching from %s@%s\n"),
msg, ctl->remotename, ctl->server.pollname);
}
closeUp:
xfree(msgsizes);
ctl->folder = NULL;
if (ctl->postconnect && (tmperr = system(ctl->postconnect)))
{
if (WIFSIGNALED(tmperr))
report(stderr, GT_("post-connection command terminated with signal %d\n"), WTERMSIG(tmperr));
else
report(stderr, GT_("post-connection command failed with status %d\n"), WEXITSTATUS(tmperr));
if (err == PS_SUCCESS)
err = PS_SYNTAX;
}
set_timeout(0);
set_signal_handler(SIGALRM, alrmsave);
return(err);
}
int do_protocol(struct query *ctl ,
const struct method *proto )
{
int err;
#ifndef KERBEROS_V4
if (ctl->server.authenticate == A_KERBEROS_V4)
{
report(stderr, GT_("Kerberos V4 support not linked.\n"));
return(PS_ERROR);
}
#endif
#ifndef KERBEROS_V5
if (ctl->server.authenticate == A_KERBEROS_V5)
{
report(stderr, GT_("Kerberos V5 support not linked.\n"));
return(PS_ERROR);
}
#endif
if (!proto->is_old)
{
if (ctl->flush) {
report(stderr,
GT_("Option --flush is not supported with %s\n"),
proto->name);
return(PS_SYNTAX);
}
else if (ctl->fetchall) {
report(stderr,
GT_("Option --all is not supported with %s\n"),
proto->name);
return(PS_SYNTAX);
}
}
if (!(proto->getsizes || proto->getpartialsizes)
&& NUM_SPECIFIED(ctl->limit))
{
report(stderr,
GT_("Option --limit is not supported with %s\n"),
proto->name);
return(PS_SYNTAX);
}
if ((ctl->keep && !ctl->flush) ||
proto->retry || !NUM_SPECIFIED(ctl->expunge))
return(do_session(ctl, proto, NUM_VALUE_OUT(ctl->fetchlimit)));
else
{
int totalcount = 0;
int lockouts = 0;
int expunge = NUM_VALUE_OUT(ctl->expunge);
int fetchlimit = NUM_VALUE_OUT(ctl->fetchlimit);
do {
if (fetchlimit > 0 && (expunge == 0 || expunge > fetchlimit - totalcount))
expunge = fetchlimit - totalcount;
err = do_session(ctl, proto, expunge);
totalcount += expunge;
if (NUM_SPECIFIED(ctl->fetchlimit) && totalcount >= fetchlimit)
break;
if (err != PS_LOCKBUSY)
lockouts = 0;
else if (lockouts >= MAX_LOCKOUTS)
break;
else
{
lockouts++;
sleep(3);
}
} while
(err == PS_MAXFETCH || err == PS_LOCKBUSY);
return(err);
}
}