#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 <sys/time.h>
#include <signal.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_NET_SOCKET_H
#include <net/socket.h>
#endif
#ifdef HESIOD
#include <hesiod.h>
#endif
#if defined(HAVE_RES_SEARCH) || defined(HAVE_GETHOSTBYNAME)
#include <netdb.h>
#include "mx.h"
#endif
#include "kerberos.h"
#ifdef KERBEROS_V4
#include <netinet/in.h>
#endif
#include "i18n.h"
#include "socket.h"
#include "fetchmail.h"
#include "tunable.h"
#define THROW_TIMEOUT 1
#define THROW_SIGPIPE 2
#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;
volatile static int timeoutcount = 0;
volatile static int idletimeout = 0;
static jmp_buf restart;
int isidletimeout(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)
{
if(stage != STAGE_IDLE) {
timeoutcount++;
longjmp(restart, THROW_TIMEOUT);
} else
idletimeout = 1;
}
static RETSIGTYPE sigpipe_handler (int signal)
{
longjmp(restart, THROW_SIGPIPE);
}
#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;
{
char * host_primary;
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;
}
}
}
xalloca(ticket, KTEXT, 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"));
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);
#ifdef HAVE_KRB5_INIT_ETS
krb5_init_ets(context);
#endif
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) {
report(stderr, GT_("krb5_sendauth: %s [server says '%*s'] \n"),
error_message(retval),
err_ret->e_text);
#else
if (err_ret && err_ret->text.length) {
report(stderr, GT_("krb5_sendauth: %s [server says '%*s'] \n"),
error_message(retval),
err_ret->text.length,
err_ret->text.data);
#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, (struct msgblk *)NULL))
return;
stuff_warning(ctl,
GT_("Subject: Fetchmail oversized-messages warning.\n"
"\n"
"The following oversized messages remain on the mail server %s:"),
ctl->server.pollname);
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);
stuff_warning(ctl,
GT_("\t%d msg %d octets long skipped by fetchmail.\n"),
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;
}
close_warning_by_mail(ctl, (struct msgblk *)NULL);
}
static void mark_oversized(struct query *ctl, int num, int size)
{
struct idlist *current=NULL, *tmp=NULL;
char sizestr[32];
int cnt;
#ifdef HAVE_SNPRINTF
snprintf(sizestr, sizeof(sizestr),
#else
sprintf(sizestr,
#endif
"%d", size);
current = ctl->skipped;
cnt = current ? current->val.status.num : 0;
if (current && str_in_list(¤t, sizestr, FALSE))
{
for ( ; current; current = current->next)
{
if (strcmp(current->id, sizestr) == 0)
{
current->val.status.mark++;
break;
}
}
}
else
{
tmp = save_str(&ctl->skipped, sizestr, 1);
tmp->val.status.num = cnt;
}
}
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;
if (ctl->server.base_protocol->getpartialsizes && NUM_NONZERO(fetchsizelimit))
{
if (ctl->server.protocol == P_POP3)
fetchsizelimit = 1;
xalloca(msgsizes, int *, 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) && !check_only)
{
mark_oversized(ctl, num, msgsize);
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, GT_(" (%d %soctets)"),
len, wholesize ? "" : GT_("header "));
if (outlevel >= O_VERBOSE)
report_complete(stdout, "\n");
else
report_complete(stdout, " ");
}
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 == PS_TRUNCATED)
suppress_readbody = TRUE;
else if (err)
return(err);
if (separatefetchbody && ctl->server.base_protocol->trail)
{
if (outlevel >= O_VERBOSE && !isafile(1))
{
fputc('\n', stdout);
fflush(stdout);
}
if ((err = (ctl->server.base_protocol->trail)(mailserver_socket, ctl, num)))
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 (outlevel > O_SILENT && !wholesize)
report_complete(stdout,
GT_(" (%d body octets) "), len);
}
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)
{
if (outlevel >= O_VERBOSE && !isafile(1))
{
fputc('\n', stdout);
fflush(stdout);
}
err = (ctl->server.base_protocol->trail)(mailserver_socket, ctl, num);
if (err != 0)
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 (retained)
{
if (outlevel > O_SILENT)
report(stdout, GT_(" retained\n"));
}
else if (ctl->server.base_protocol->delete
&& !suppress_delete
&& ((msgcode >= 0 && !ctl->keep)
|| (msgcode == MSGLEN_OLD && ctl->flush)))
{
(*deletions)++;
if (outlevel > O_SILENT)
report_complete(stdout, GT_(" flushed\n"));
err = (ctl->server.base_protocol->delete)(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)
{
report(stdout, GT_("fetchlimit %d reached; %d messages left on server %s account %s\n"),
maxfetch, count - *fetches, ctl->server.truename, ctl->remotename);
return(PS_MAXFETCH);
}
}
return(PS_SUCCESS);
}
static int do_session(ctl, proto, maxfetch)
struct query *ctl;
const struct method *proto;
const int maxfetch;
{
int js;
#ifdef HAVE_VOLATILE
volatile int err, mailserver_socket = -1;
#else
int err, mailserver_socket = -1;
#endif
const char *msg;
SIGHANDLERTYPE pipesave;
SIGHANDLERTYPE alrmsave;
ctl->server.base_protocol = proto;
pass = 0;
err = 0;
init_transact(proto);
alrmsave = set_signal_handler(SIGALRM, timeout_handler);
mytimeout = ctl->server.timeout;
pipesave = set_signal_handler(SIGPIPE, sigpipe_handler);
if ((js = setjmp(restart)))
{
#ifdef HAVE_SIGPROCMASK
sigset_t allsigs;
sigfillset(&allsigs);
sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
#endif
if (js == THROW_SIGPIPE)
{
set_signal_handler(SIGPIPE, SIG_IGN);
report(stdout,
GT_("SIGPIPE thrown from an MDA or a stream socket error\n"));
wait(0);
}
else 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, (struct msgblk *)NULL))
{
stuff_warning(ctl,
GT_("Subject: fetchmail sees repeated timeouts\n"));
stuff_warning(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(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, new, bytes, deletions = 0;
int *msgsizes = (int *)NULL;
#if INET6_ENABLE
int fetches, dispatches, oldphase;
#else
int port, fetches, dispatches, oldphase;
#endif
struct idlist *idp;
if (ctl->preconnect && (err = system(ctl->preconnect)))
{
report(stderr,
GT_("pre-connection command failed with status %d\n"), err);
err = PS_SYNTAX;
goto closeUp;
}
oldphase = phase;
phase = OPEN_WAIT;
set_timeout(mytimeout);
#if !INET6_ENABLE
#ifdef SSL_ENABLE
port = ctl->server.port ? ctl->server.port : ( ctl->use_ssl ? ctl->server.base_protocol->sslport : ctl->server.base_protocol->port );
#else
port = ctl->server.port ? ctl->server.port : ctl->server.base_protocol->port;
#endif
#endif
#ifdef 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
#ifdef HAVE_GETHOSTBYNAME
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;
}
ctl->server.truename = xstrdup(leadname);
}
else
{
struct hostent *namerec;
errno = 0;
namerec = gethostbyname(ctl->server.queryname);
if (namerec == (struct hostent *)NULL)
{
report(stderr,
GT_("couldn't find canonical DNS name of %s (%s)\n"),
ctl->server.pollname, ctl->server.queryname);
err = PS_DNS;
set_timeout(0);
phase = oldphase;
goto closeUp;
}
else
{
ctl->server.truename=xstrdup((char *)namerec->h_name);
ctl->server.trueaddr=xmalloc(namerec->h_length);
memcpy(ctl->server.trueaddr,
namerec->h_addr_list[0],
namerec->h_length);
}
}
}
#endif
realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
if (ctl->server.plugin)
(void)sleep(1);
#if INET6_ENABLE
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.netsec, ctl->server.plugin)) == -1)
#else
if ((mailserver_socket = SockOpen(realhost, port, NULL, ctl->server.plugin)) == -1)
#endif
{
char errbuf[BUFSIZ];
#if !INET6_ENABLE
int err_no = errno;
#ifdef HAVE_RES_SEARCH
if (err_no != 0 && h_errno != 0)
report(stderr, GT_("internal inconsistency\n"));
#endif
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);
#ifdef HAVE_RES_SEARCH
if (h_errno != 0)
{
if (h_errno == HOST_NOT_FOUND)
strcpy(errbuf, GT_("host is unknown."));
#ifndef __BEOS__
else if (h_errno == NO_ADDRESS)
strcpy(errbuf, GT_("name is valid but has no IP address."));
#endif
else if (h_errno == NO_RECOVERY)
strcpy(errbuf, GT_("unrecoverable name server error."));
else if (h_errno == TRY_AGAIN)
strcpy(errbuf, GT_("temporary name server error."));
else
#ifdef HAVE_SNPRINTF
snprintf(errbuf, sizeof(errbuf),
#else
sprintf(errbuf,
#endif
GT_("unknown DNS error %d."), h_errno);
}
else
#endif
strcpy(errbuf, strerror(err_no));
report_complete(stderr, ": %s\n", errbuf);
#ifdef __UNUSED
if (open_warning_by_mail(ctl, (struct msgblk *)NULL) == 0)
{
stuff_warning(ctl,
GT_("Subject: Fetchmail unreachable-server warning.\n"
"\n"
"Fetchmail could not reach the mail server %s:")
ctl->server.pollname);
stuff_warning(ctl, errbuf, ctl->server.pollname);
close_warning_by_mail(ctl, (struct msgblk *)NULL);
}
#endif
}
#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->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1)
{
report(stderr, GT_("SSL connection failed.\n"));
err = PS_AUTHFAIL;
goto closeUp;
}
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)
{
err = (ctl->server.base_protocol->getauth)(mailserver_socket, ctl, buf);
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 (run.poll_interval
&& !ctl->wehavesentauthnote
&& ((ctl->wehaveauthed && ++ctl->authfailcount >= 10)
|| (!ctl->wehaveauthed && ++ctl->authfailcount >= 3))
&& !open_warning_by_mail(ctl, (struct msgblk *)NULL))
{
ctl->wehavesentauthnote = 1;
stuff_warning(ctl,
GT_("Subject: fetchmail authentication failed on %s@%s\n"),
ctl->remotename, ctl->server.truename);
stuff_warning(ctl,
GT_("Fetchmail could not get mail from %s@%s.\n"),
ctl->remotename,
ctl->server.truename);
if (ctl->wehaveauthed)
stuff_warning(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.\n\
\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(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, (struct msgblk *)NULL))
{
stuff_warning(ctl,
GT_("Subject: fetchmail authentication OK on %s@%s\n"),
ctl->remotename, ctl->server.truename);
stuff_warning(ctl,
GT_("Fetchmail was able to log into %s@%s.\n"),
ctl->remotename,
ctl->server.truename);
stuff_warning(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)
{
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, &new, &bytes);
if (err != 0)
goto cleanUp;
if (idp->id)
#ifdef HAVE_SNPRINTF
(void) snprintf(buf, sizeof(buf),
#else
(void) sprintf(buf,
#endif
GT_("%s at %s (folder %s)"),
ctl->remotename, ctl->server.pollname, idp->id);
else
#ifdef HAVE_SNPRINTF
(void) snprintf(buf, sizeof(buf),
#else
(void) sprintf(buf,
#endif
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 (new != -1 && (count - new) > 0)
report_build(stdout, GT_("%d %s (%d %s) for %s"),
count, count > 1 ? GT_("messages") :
GT_("message"),
count-new,
GT_("seen"),
buf);
else
report_build(stdout, GT_("%d %s for %s"),
count, count > 1 ? GT_("messages") :
GT_("message"), 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 (new == -1 || ctl->fetchall)
new = count;
fetches = new;
}
else if (count > 0)
{
int i;
if (count > INT_MAX/sizeof(int))
{
report(stderr, GT_("bogus message count!"));
return(PS_PROTOCOL);
}
if (proto->getsizes &&
!(proto->getpartialsizes && NUM_NONZERO(ctl->fetchsizelimit)))
{
xalloca(msgsizes, int *, 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)
goto cleanUp;
if (!check_only && ctl->skipped
&& run.poll_interval > 0 && !nodetach)
{
clean_skipped_list(&ctl->skipped);
send_size_warnings(ctl);
}
}
} while
(dispatches && ctl->server.base_protocol->retry && !ctl->keep && !ctl->errcount);
}
err = (ctl->server.base_protocol->logout_cmd)(mailserver_socket, ctl);
if (err == 0)
err = (fetches > 0) ? PS_SUCCESS : PS_NOMAIL;
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);
mailserver_socket = -1;
}
if (mailserver_socket_temp != -1) {
cleanupSockClose(mailserver_socket_temp);
mailserver_socket_temp = -1;
}
}
msg = (const char *)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:
report(stderr, GT_("undefined error\n"));
break;
}
if (err==PS_SOCKET || err==PS_SYNTAX
|| err==PS_IOERR || err==PS_ERROR || err==PS_PROTOCOL
|| err==PS_LOCKBUSY || err==PS_SMTP || err==PS_DNS)
{
char *stem;
if (phase == FORWARDING_WAIT || phase == LISTENER_WAIT)
stem = GT_("%s error while delivering to SMTP host %s\n");
else
stem = GT_("%s error while fetching from %s\n");
report(stderr, stem, msg, ctl->server.pollname);
}
closeUp:
if (ctl->postconnect && (err = system(ctl->postconnect)))
{
report(stderr, GT_("post-connection command failed with status %d\n"), err);
if (err == PS_SUCCESS)
err = PS_SYNTAX;
}
set_timeout(0);
set_signal_handler(SIGALRM, alrmsave);
set_signal_handler(SIGPIPE, pipesave);
return(err);
}
int do_protocol(ctl, proto)
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 && 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);
}
}