#include <errno.h>
#include <ctype.h>
#include <string.h>
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif /* HAVE_MEMORY_H */
#if defined(STDC_HEADERS)
#include <stdlib.h>
#endif
#endif
#if defined(HAVE_ALLOCA_H)
#include <alloca.h>
+#else
+#ifdef _AIX
+ #pragma alloca
+#endif
#endif
#if defined(HAVE_SYS_ITIMER_H)
#include <sys/itimer.h>
#include <sys/time.h>
#include <signal.h>
+#ifndef HAVE_STRFTIME /* For ctime prototype */
+#include <sys/types.h>
+#include <time.h>
+#endif
+
#ifdef HAVE_GETHOSTBYNAME
#include <netdb.h>
#include "mx.h"
#endif /* HAVE_GETHOSTBYNAME */
-#ifdef SUNOS
-#include <memory.h>
-#endif
-
#ifdef KERBEROS_V4
#if defined (__bsdi__)
#include <des.h> /* order of includes matters */
#include <krb.h>
#define krb_get_err_text(e) (krb_err_txt[e])
#else
+#if defined(__FreeBSD__) || defined(__linux__)
+#define krb_get_err_text(e) (krb_err_txt[e])
#include <krb.h>
#include <des.h>
+#else
+#include <krb.h>
+#include <des.h>
+#endif /* ! defined (__FreeBSD__) */
#endif /* ! defined (__bsdi__) */
#include <netinet/in.h>
#include <netdb.h>
#define SIGCHLD SIGCLD
#endif
+#if INET6
+#define SMTP_PORT "smtp" /* standard SMTP service port */
+#else /* INET6 */
#define SMTP_PORT 25 /* standard SMTP service port */
+#endif /* INET6 */
+#ifndef strstr /* glibc-2.1 declares this as a macro */
extern char *strstr(); /* needed on sysV68 R3V7.1. */
+#endif /* strstr */
int fetchlimit; /* how often to tear down the server connection */
int batchcount; /* count of messages sent in current batch */
flag peek_capable; /* can we peek for better error recovery? */
+int pass; /* how many times have we re-polled? */
static const struct method *protocol;
static jmp_buf restart;
char tag[TAGLEN];
static int tagnum;
-#define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag)
+#define GENSYM (sprintf(tag, "a%04d", ++tagnum % TAGMOD), tag)
static char *shroud; /* string to shroud in debug output, if non-NULL */
static int mytimeout; /* value of nonreponse timeout */
+static int msglen; /* actual message length */
+
+/* use these to track what was happening when the nonresponse timer fired */
+#define GENERAL_WAIT 0
+#define SERVER_WAIT 1
+#define FORWARDING_WAIT 2
+static phase;
static void set_timeout(int timeleft)
/* reset the nonresponse-timeout */
#define XMIT_ANTISPAM 3
static int accept_count, reject_count;
-#ifdef HAVE_RES_SEARCH
#define MX_RETRIES 3
static int is_host_alias(const char *name, struct query *ctl)
* name doesn't match any is it time to call the bind library.
* If this happens odds are good we're looking at an MX name.
*/
- if (strcmp(lead_server->truename, name) == 0)
+ if (strcasecmp(lead_server->truename, name) == 0)
return(TRUE);
else if (str_in_list(&lead_server->akalist, name))
return(TRUE);
else if (!ctl->server.dns)
return(FALSE);
+#ifndef HAVE_RES_SEARCH
+ return(FALSE);
+#else
/*
+ * The only code that calls the BIND library is here and in the
+ * start-of-query probe with gethostbyname(3).
+ *
* We know DNS service was up at the beginning of this poll cycle.
* If it's down, our nameserver has crashed. We don't want to try
* delivering the current message or anything else from this
*/
else if ((he = gethostbyname(name)) != (struct hostent *)NULL)
{
- if (strcmp(ctl->server.truename, he->h_name) == 0)
+ if (strcasecmp(ctl->server.truename, he->h_name) == 0)
goto match;
else
return(FALSE);
case NO_RECOVERY: /* non-recoverable name server error */
case TRY_AGAIN: /* temporary error on authoritative server */
default:
- error(0, 0,
+ error(0, -1,
"nameserver failure while looking for `%s' during poll of %s.",
name, ctl->server.pollname);
ctl->errcount++;
else
{
for (mxp = mxrecords; mxp->name; mxp++)
- if (strcmp(ctl->server.truename, mxp->name) == 0)
+ if (strcasecmp(ctl->server.truename, mxp->name) == 0)
goto match;
return(FALSE);
match:;
/* add this name to relevant server's `also known as' list */
save_str(&lead_server->akalist, -1, name);
return(TRUE);
+#endif /* HAVE_RES_SEARCH */
}
static void map_name(name, ctl, xmit_names)
struct idlist **xmit_names; /* list of recipient names parsed out */
{
const char *lname;
-
+ int sl;
+ int off = 0;
+
lname = idpair_find(&ctl->localnames, name);
if (!lname && ctl->wildcard)
lname = name;
if (lname != (char *)NULL)
{
+ /*
+ * If the name of the user begins with a
+ * qmail virtual domain prefix, remove
+ * the prefix
+ */
+ if (ctl->server.qvirtual)
+ {
+ sl=strlen(ctl->server.qvirtual);
+ if (!strncasecmp(lname,ctl->server.qvirtual,sl)) off=sl;
+ }
if (outlevel == O_VERBOSE)
- error(0, 0, "mapped %s to local %s", name, lname);
- save_str(xmit_names, XMIT_ACCEPT, lname);
+ error(0, 0, "mapped %s to local %s", name, lname+off);
+ save_str(xmit_names, XMIT_ACCEPT, lname+off);
accept_count++;
}
}
{
char *cp;
- if ((cp = nxtaddr(hdr)) != (char *)NULL)
- do {
- char *atsign;
+ for (cp = nxtaddr(hdr);
+ cp != NULL;
+ cp = nxtaddr(NULL))
+ {
+ char *atsign;
- if ((atsign = strchr(cp, '@')))
- {
- struct idlist *idp;
+ if ((atsign = strchr(cp, '@'))) {
+ struct idlist *idp;
- /*
- * Does a trailing segment of the hostname match something
- * on the localdomains list? If so, save the whole name
- * and keep going.
- */
- for (idp = ctl->server.localdomains; idp; idp = idp->next)
- {
- char *rhs;
-
- rhs = atsign + (strlen(atsign) - strlen(idp->id));
- if ((rhs[-1] == '.' || rhs[-1] == '@')
- && strcasecmp(rhs, idp->id) == 0)
- {
- if (outlevel == O_VERBOSE)
- error(0, 0, "passed through %s matching %s",
- cp, idp->id);
- save_str(xmit_names, XMIT_ACCEPT, cp);
- accept_count++;
- continue;
- }
- }
+ /*
+ * Does a trailing segment of the hostname match something
+ * on the localdomains list? If so, save the whole name
+ * and keep going.
+ */
+ for (idp = ctl->server.localdomains; idp; idp = idp->next) {
+ char *rhs;
- /*
- * Check to see if the right-hand part is an alias
- * or MX equivalent of the mailserver. If it's
- * not, skip this name. If it is, we'll keep
- * going and try to find a mapping to a client name.
- */
- if (!is_host_alias(atsign+1, ctl))
+ rhs = atsign + (strlen(atsign) - strlen(idp->id));
+ if (rhs > atsign &&
+ (rhs[-1] == '.' || rhs[-1] == '@') &&
+ strcasecmp(rhs, idp->id) == 0)
{
- save_str(xmit_names, XMIT_REJECT, cp);
- reject_count++;
- continue;
+ if (outlevel == O_VERBOSE)
+ error(0, 0, "passed through %s matching %s",
+ cp, idp->id);
+ save_str(xmit_names, XMIT_ACCEPT, cp);
+ accept_count++;
+ break;
}
- atsign[0] = '\0';
}
+ /* if we matched a local domain, idp != NULL */
+ if (idp) continue;
- map_name(cp, ctl, xmit_names);
- } while
- ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
+ /*
+ * Check to see if the right-hand part is an alias
+ * or MX equivalent of the mailserver. If it's
+ * not, skip this name. If it is, we'll keep
+ * going and try to find a mapping to a client name.
+ */
+ if (!is_host_alias(atsign+1, ctl))
+ {
+ save_str(xmit_names, XMIT_REJECT, cp);
+ reject_count++;
+ continue;
+ }
+ atsign[0] = '\0';
+ }
+
+ map_name(cp, ctl, xmit_names);
+ }
}
}
-char *parse_received(struct query *ctl, char *bufp)
-/* try to extract real addressee from the Received line */
+static char *parse_received(struct query *ctl, char *bufp)
+/* try to extract real address from the Received line */
+/* If a valid Received: line is found, we return the full address in
+ * a buffer wich can be parsed from nxtaddr(). This is to ansure that
+ * the local domain part of the address can be passed along in
+ * find_server_names() if it contains one.
+ * Note: We should return a dummy header containing the address
+ * which makes nxtaddr() behave correctly.
+ */
{
- char *ok;
+ char *ok = (char *)NULL;
static char rbuf[HOSTLEN + USERNAMELEN + 4];
/*
* address in the Received line. Sendmail itself only
* does this when the mail has a single recipient.
*/
- if ((ok = strstr(bufp, "by ")) == (char *)NULL)
- ok = (char *)NULL;
- else
+ if ((ok = strstr(bufp, "by ")) && isspace(ok[-1]))
{
char *sp, *tp;
* recipient name after a following "for". Otherwise
* punt.
*/
- if (is_host_alias(rbuf, ctl))
- ok = strstr(sp, "for ");
- else
+ if (is_host_alias(rbuf, ctl) &&
+ (ok = strstr(sp, "for ")) &&
+ isspace(ok[-1]))
+ {
+ tp = rbuf;
+ sp = ok + 4;
+ *tp++ = ':'; /* Here is the hack. This is to be friend */
+ *tp++ = ' '; /* with nxtaddr()... */
+ if (*sp == '<')
+ sp++;
+ while (*sp == '@') /* skip routes */
+ while (*sp && *sp++ != ':')
+ continue;
+ while (*sp && *sp != '>' && *sp != ';')
+ if (!isspace(*sp))
+ *tp++ = *sp++;
+ else
+ {
+ /* uh oh -- whitespace here can't be right! */
+ ok = (char *)NULL;
+ break;
+ }
+ *tp++ = '\n';
+ *tp = '\0';
+ if (strlen(rbuf) <= 3) /* apparently nothing has been found */
+ ok = NULL;
+ } else
ok = (char *)NULL;
}
- if (ok != 0)
- {
- char *sp, *tp;
-
- tp = rbuf;
- sp = ok + 4;
- if (*sp == '<')
- sp++;
- while (*sp && *sp != '>' && *sp != '@' && *sp != ';')
- if (!isspace(*sp))
- *tp++ = *sp++;
- else
- {
- /* uh oh -- whitespace here can't be right! */
- ok = (char *)NULL;
- break;
- }
- *tp = '\0';
- }
-
if (!ok)
return(NULL);
else
{
- if (outlevel == O_VERBOSE)
- error(0, 0, "found Received address `%s'", rbuf);
+ if (outlevel == O_VERBOSE) {
+ char *lf = rbuf + strlen(rbuf)-1;
+ *lf = '\0';
+ error(0, 0, "found Received address `%s'", rbuf+2);
+ *lf = '\n';
+ }
return(rbuf);
}
}
-#endif /* HAVE_RES_SEARCH */
-int smtp_open(struct query *ctl)
+static int smtp_open(struct query *ctl)
/* try to open a socket to the appropriate SMTP server for this query */
{
- struct idlist *idp;
-
/* maybe it's time to close the socket in order to force delivery */
- if (ctl->batchlimit > 0 && (ctl->smtp_socket != -1) && batchcount++ == ctl->batchlimit)
+ if (NUM_NONZERO(ctl->batchlimit) && (ctl->smtp_socket != -1) && batchcount++ == ctl->batchlimit)
{
close(ctl->smtp_socket);
ctl->smtp_socket = -1;
batchcount = 0;
}
- /* run down the SMTP hunt list looking for a server that's up */
- for (idp = ctl->smtphunt; idp; idp = idp->next)
+ /* if no socket to any SMTP host is already set up, try to open one */
+ if (ctl->smtp_socket == -1)
{
/*
* RFC 1123 requires that the domain name in HELO address is a
- * "valid principal domain name" for the client host. We
- * violate this with malice aforethought in order to make the
- * Received headers and logging look right.
+ * "valid principal domain name" for the client host. If we're
+ * running in invisible mode, violate this with malice
+ * aforethought in order to make the Received headers and
+ * logging look right.
*
* In fact this code relies on the RFC1123 requirement that the
* SMTP listener must accept messages even if verification of the
* that matches the local machine fetchmail is running on.
* What it will affect is the listener's logging.
*/
+ struct idlist *idp;
+ char *id_me = use_invisible ? ctl->server.truename : fetchmailhost;
+
+ errno = 0;
- /* if no socket to this host is already set up, try to open ESMTP */
- if (ctl->smtp_socket == -1)
+ /*
+ * Run down the SMTP hunt list looking for a server that's up.
+ * Use both explicit hunt entries (value TRUE) and implicit
+ * (default) ones (value FALSE).
+ */
+ for (idp = ctl->smtphunt; idp; idp = idp->next)
{
-#ifndef HAVE_RES_SEARCH
- char *fakename;
-#endif /* HAVE_RES_SEARCH */
+ ctl->smtphost = idp->id; /* remember last host tried. */
if ((ctl->smtp_socket = SockOpen(idp->id,SMTP_PORT)) == -1)
continue;
- if (SMTP_ok(ctl->smtp_socket) != SM_OK
- || SMTP_ehlo(ctl->smtp_socket,
- ctl->server.truename,
- &ctl->server.esmtp_options) != SM_OK)
- {
- /*
- * RFC 1869 warns that some listeners hang up on a failed EHLO,
- * so it's safest not to assume the socket will still be good.
- */
- close(ctl->smtp_socket);
- ctl->smtp_socket = -1;
- }
- else
- {
- ctl->smtphost = idp->id;
- break;
- }
- }
+ if (SMTP_ok(ctl->smtp_socket) == SM_OK &&
+ SMTP_ehlo(ctl->smtp_socket, id_me,
+ &ctl->server.esmtp_options) == SM_OK)
+ break; /* success */
- /* if opening for ESMTP failed, try SMTP */
- if (ctl->smtp_socket == -1)
- {
+ /*
+ * RFC 1869 warns that some listeners hang up on a failed EHLO,
+ * so it's safest not to assume the socket will still be good.
+ */
+ close(ctl->smtp_socket);
+ ctl->smtp_socket = -1;
+
+ /* if opening for ESMTP failed, try SMTP */
if ((ctl->smtp_socket = SockOpen(idp->id,SMTP_PORT)) == -1)
continue;
- else if (SMTP_ok(ctl->smtp_socket) != SM_OK
- || SMTP_helo(ctl->smtp_socket,
- ctl->server.truename) != SM_OK)
- {
- close(ctl->smtp_socket);
- ctl->smtp_socket = -1;
- }
- else
- {
- ctl->smtphost = idp->id;
- break;
- }
+
+ if (SMTP_ok(ctl->smtp_socket) == SM_OK &&
+ SMTP_helo(ctl->smtp_socket, id_me) == SM_OK)
+ break; /* success */
+
+ close(ctl->smtp_socket);
+ ctl->smtp_socket = -1;
}
}
+ if (outlevel >= O_VERBOSE && ctl->smtp_socket != -1)
+ error(0, 0, "forwarding to SMTP port on %s", ctl->smtphost);
+
return(ctl->smtp_socket);
}
static RETSIGTYPE (*sigchld)();
static int sizeticker;
-static int stuffline(struct query *ctl, char *buf, flag delimited)
-/* ship a line to the given control block's SMTP server */
+static int stuffline(struct query *ctl, char *buf)
+/* ship a line to the given control block's output sink (SMTP server or MDA) */
{
- int n;
+ int n, oldphase;
+ char *last;
+
+ /* The line may contain NUL characters. Find the last char to use
+ * -- the real line termination is the sequence "\n\0".
+ */
+ last = buf;
+ while ((last += strlen(last)) && (last[-1] != '\n'))
+ last++;
/* fix message lines that have only \n termination (for qmail) */
if (ctl->forcecr)
{
- char *cp = buf + strlen(buf) - 1;
-
- if (*cp == '\n' && (cp == buf || cp[-1] != '\r'))
+ if (last - 1 == buf || last[-2] != '\r')
{
- *cp++ = '\r';
- *cp++ = '\n';
- *cp++ = '\0';
+ last[-1] = '\r';
+ *last++ = '\n';
+ *last = '\0';
}
}
+ oldphase = phase;
+ phase = FORWARDING_WAIT;
+
/*
* SMTP byte-stuffing. We only do this if the protocol does *not*
* use .<CR><LF> as EOM. If it does, the server will already have
* decorated any . lines it sends back up.
*/
- if (!delimited && *buf == '.')
- if (sinkfp && ctl->mda)
- fputs(".", sinkfp);
- else if (ctl->smtp_socket != -1)
- SockWrite(ctl->smtp_socket, buf, 1);
+ if (*buf == '.')
+ if (protocol->delimited) /* server has already byte-stuffed */
+ {
+ if (ctl->mda)
+ ++buf;
+ else
+ /* writing to SMTP, leave the byte-stuffing in place */;
+ }
+ else /* if (!protocol->delimited) -- not byte-stuffed already */
+ {
+ if (!ctl->mda)
+ SockWrite(ctl->smtp_socket, buf, 1); /* byte-stuff it */
+ else
+ /* leave it alone */;
+ }
/* we may need to strip carriage returns */
if (ctl->stripcr)
{
char *sp, *tp;
- for (sp = tp = buf; *sp; sp++)
+ for (sp = tp = buf; sp < last; sp++)
if (*sp != '\r')
*tp++ = *sp;
*tp = '\0';
+ last = tp;
}
n = 0;
if (ctl->mda)
- n = fwrite(buf, 1, strlen(buf), sinkfp);
+ n = fwrite(buf, 1, last - buf, sinkfp);
else if (ctl->smtp_socket != -1)
- n = SockWrite(ctl->smtp_socket, buf, strlen(buf));
+ n = SockWrite(ctl->smtp_socket, buf, last - buf);
+
+ phase = oldphase;
return(n);
}
-static int readheaders(sock, len, ctl, realname, num)
+static int readheaders(sock, fetchlen, reallen, ctl, num)
/* read message headers and ship to SMTP or MDA */
int sock; /* to which the server is connected */
-long len; /* length of message */
+long fetchlen; /* length of message according to fetch response */
+long reallen; /* length of message according to getsizes */
struct query *ctl; /* query control record */
-char *realname; /* real name of host */
int num; /* index of message */
{
- char buf[MSGBUFSIZE+1], return_path[MSGBUFSIZE+1];
- int from_offs, ctt_offs, env_offs, addressoffs[128], next_address;
- char *headers, *received_for;
- int n, linelen, oldlen, ch, remaining;
- char *cp;
+ struct addrblk
+ {
+ int offset;
+ struct addrblk *next;
+ };
+ struct addrblk *to_addrchain = NULL;
+ struct addrblk **to_chainptr = &to_addrchain;
+ struct addrblk *resent_to_addrchain = NULL;
+ struct addrblk **resent_to_chainptr = &resent_to_addrchain;
+
+ char buf[MSGBUFSIZE+1];
+ char return_path[HOSTLEN + USERNAMELEN + 4];
+ int from_offs, reply_to_offs, resent_from_offs;
+ int app_from_offs, sender_offs, resent_sender_offs;
+ int ctt_offs, env_offs;
+ char *headers, *received_for, *destaddr, *rcv, *cp;
+ int n, linelen, oldlen, ch, remaining, skipcount;
struct idlist *idp, *xmit_names;
- flag good_addresses, bad_addresses, has_nuls;
-#ifdef HAVE_RES_SEARCH
+ flag good_addresses, bad_addresses, has_nuls;
flag no_local_matches = FALSE;
-#endif /* HAVE_RES_SEARCH */
int olderrs;
- next_address = sizeticker = 0;
+ sizeticker = 0;
has_nuls = FALSE;
return_path[0] = '\0';
olderrs = ctl->errcount;
/* read message headers */
headers = received_for = NULL;
- from_offs = ctt_offs = env_offs = -1;
+ from_offs = reply_to_offs = resent_from_offs = app_from_offs =
+ sender_offs = resent_sender_offs = ctt_offs = env_offs = -1;
oldlen = 0;
- for (remaining = len; remaining > 0; remaining -= linelen)
+ msglen = 0;
+ skipcount = 0;
+
+ for (remaining = fetchlen; remaining > 0 || protocol->delimited; remaining -= linelen)
{
char *line;
linelen = 0;
line[0] = '\0';
do {
- if ((n = SockRead(sock, buf, sizeof(buf)-1)) == -1)
+ if ((n = SockRead(sock, buf, sizeof(buf)-1)) == -1) {
+ free(line);
+ free(headers);
return(PS_SOCKET);
+ }
linelen += n;
+ msglen += n;
/* lines may not be properly CRLF terminated; fix this for qmail */
if (ctl->forcecr)
}
set_timeout(ctl->server.timeout);
- /* leave extra room for reply_hack to play with */
- line = (char *) realloc(line, strlen(line) + strlen(buf) + HOSTLEN + 1);
+
+ line = (char *) realloc(line, strlen(line) + strlen(buf) +1);
+
strcat(line, buf);
if (line[0] == '\r' && line[1] == '\n')
break;
break;
}
- /* maybe this is a special message that holds mailbox annotations? */
- if (protocol->retain_hdr && protocol->retain_hdr(num, line))
- return(PS_RETAINED);
+ /*
+ * The University of Washington IMAP server (the reference
+ * implementation of IMAP4 written by Mark Crispin) relies
+ * on being able to keep base-UID information in a special
+ * message at the head of the mailbox. This message should
+ * neither be deleted nor forwarded.
+ */
+#ifdef POP2_ENABLE
+ /*
+ * We disable this check under POP2 because there's no way to
+ * prevent deletion of the message. So at least we ought to
+ * forward it to the user so he or she will have some clue
+ * that things have gone awry.
+ */
+ if (protocol->port != 109)
+#endif /* POP2_ENABLE */
+ if (num == 1 && !strncasecmp(line, "X-IMAP:", 7)) {
+ free(line);
+ free(headers);
+ return(PS_RETAINED);
+ }
/*
* This code prevents fetchmail from becoming an accessory after
* unconditionally. Nonempty ones get chucked if the user
* turns on the dropstatus flag.
*/
- if (!strncasecmp(line, "Status:", 7))
+ if (!strncasecmp(line, "Status:", 7)
+ || !strncasecmp(line, "X-Mozilla-Status:", 7))
{
char *cp;
}
}
+ if (ctl->rewrite)
+ line = reply_hack(line, ctl->server.truename);
+
/*
* OK, this is messy. If we're forwarding by SMTP, it's the
* SMTP-receiver's job (according to RFC821, page 22, section
* envelope sender from the Return-Path, the new Return-Path should be
* exactly the same as the original one.
*/
- if (!ctl->mda && !strncasecmp("Return-Path:", line, 12))
+ if (!strncasecmp("Return-Path:", line, 12))
{
strcpy(return_path, nxtaddr(line));
- free(line);
- continue;
+ if (!ctl->mda) {
+ free(line);
+ continue;
+ }
}
- if (ctl->rewrite)
- reply_hack(line, realname);
-
if (!headers)
{
oldlen = strlen(line);
newlen = oldlen + strlen(line);
headers = (char *) realloc(headers, newlen + 1);
- if (headers == NULL)
+ if (headers == NULL) {
+ free(line);
return(PS_IOERR);
+ }
strcpy(headers + oldlen, line);
free(line);
line = headers + oldlen;
oldlen = newlen;
}
- if (from_offs == -1 && !strncasecmp("From:", line, 5))
- from_offs = (line - headers);
- else if (from_offs == -1 && !strncasecmp("Resent-From:", line, 12))
+ if (!strncasecmp("From:", line, 5))
from_offs = (line - headers);
- else if (from_offs == -1 && !strncasecmp("Apparently-From:", line, 16))
- from_offs = (line - headers);
- else if (!strncasecmp("To:", line, 3))
+ else if (!strncasecmp("Reply-To:", line, 9))
+ reply_to_offs = (line - headers);
+ else if (!strncasecmp("Resent-From:", line, 12))
+ resent_from_offs = (line - headers);
+ else if (!strncasecmp("Apparently-From:", line, 16))
+ app_from_offs = (line - headers);
+ else if (!strncasecmp("Sender:", line, 7))
+ sender_offs = (line - headers);
+ else if (!strncasecmp("Resent_Sender:", line, 14))
+ resent_sender_offs = (line - headers);
+
+ else if (!strncasecmp("Content-Transfer-Encoding:", line, 26))
+ ctt_offs = (line - headers);
+ else if (!strncasecmp("Message-Id:", buf, 11 ))
{
- if (next_address >= sizeof(addressoffs)/sizeof(addressoffs[0]))
- error(PS_UNDEFINED, errno, "too many destination headers");
- addressoffs[next_address++] = (line - headers);
- }
+ if( ctl->server.uidl )
+ {
+ char id[IDLEN+1];
+ /* prevent stack overflows */
+ buf[IDLEN+12] = 0;
+ sscanf( buf+12, "%s", id);
+ if( !str_find( &ctl->newsaved, num ) )
+ save_str(&ctl->newsaved, num, id );
+ }
+ }
+
+ else if (!MULTIDROP(ctl))
+ continue;
- else if (!strncasecmp("Cc:", line, 3))
+ else if (!strncasecmp("To:", line, 3)
+ || !strncasecmp("Cc:", line, 3)
+ || !strncasecmp("Bcc:", line, 4)
+ || !strncasecmp("Apparently-To:", line, 14))
{
- if (next_address >= sizeof(addressoffs)/sizeof(addressoffs[0]))
- error(PS_UNDEFINED, errno, "too many destination headers");
- addressoffs[next_address++] = (line - headers);
+ *to_chainptr = xmalloc(sizeof(struct addrblk));
+ (*to_chainptr)->offset = (line - headers);
+ to_chainptr = &(*to_chainptr)->next;
+ *to_chainptr = NULL;
}
- else if (!strncasecmp("Bcc:", line, 4))
+ else if (!strncasecmp("Resent-To:", line, 10)
+ || !strncasecmp("Resent-Cc:", line, 10)
+ || !strncasecmp("Resent-Bcc:", line, 11))
{
- if (next_address >= sizeof(addressoffs)/sizeof(addressoffs[0]))
- error(PS_UNDEFINED, errno, "too many destination headers");
- addressoffs[next_address++] = (line - headers);
+ *resent_to_chainptr = xmalloc(sizeof(struct addrblk));
+ (*resent_to_chainptr)->offset = (line - headers);
+ resent_to_chainptr = &(*resent_to_chainptr)->next;
+ *resent_to_chainptr = NULL;
}
- else if (!strncasecmp("Content-Transfer-Encoding:", line, 26))
- ctt_offs = (line - headers);
-
- else if (MULTIDROP(ctl) && ctl->server.envelope != STRING_DISABLED)
+ else if (ctl->server.envelope != STRING_DISABLED)
{
if (ctl->server.envelope
- && strcasecmp(ctl->server.envelope, "received"))
+ && strcasecmp(ctl->server.envelope, "Received"))
{
if (env_offs == -1 && !strncasecmp(ctl->server.envelope,
line,
strlen(ctl->server.envelope)))
+ {
+ if (skipcount++ != ctl->server.envskip)
+ continue;
env_offs = (line - headers);
+ }
}
-#ifdef HAVE_RES_SEARCH
else if (!received_for && !strncasecmp("Received:", line, 9))
+ {
+ if (skipcount++ != ctl->server.envskip)
+ continue;
received_for = parse_received(ctl, line);
-#endif /* HAVE_RES_SEARCH */
+ }
}
}
*/
if (headers == (char *)NULL)
{
+#ifdef HAVE_SNPRINTF
+ snprintf(buf, sizeof(buf),
+#else
sprintf(buf,
+#endif /* HAVE_SNPRINTF */
"From: <FETCHMAIL-DAEMON@%s>\r\nTo: %s@localhost\r\nSubject: Headerless mail from %s's mailbox on %s\r\n",
- fetchmailhost, user, ctl->remotename, realname);
+ fetchmailhost, user, ctl->remotename, ctl->server.truename);
headers = xstrdup(buf);
}
* In fact we have to, as this will tell us where to forward to.
*/
+ /*
+ * If there is a Return-Path address on the message, this was
+ * almost certainly the MAIL FROM address given the originating
+ * sendmail. This is the best thing to use for logging the
+ * message origin (it sets up the right behavior for bounces and
+ * mailing lists). Otherwise, fall down to the next available
+ * envelope address wich is the most probable real sender
+ * respectively. *** The order is important! ***
+ * This is especially useful when receiving mailing list
+ * messages in multidrop mode. if a local address doesn't
+ * exist, the bounce message won't be returned blindly to the
+ * author or to the list itself but rather to the list manager
+ * (ex: specified by "Sender:") wich is less anoying. This is
+ * true for most mailing list packages.
+ */
+ if( !return_path[0] ){
+ char *ap = NULL;
+ if (resent_sender_offs >= 0 && (ap = nxtaddr(headers + resent_sender_offs)));
+ else if (sender_offs >= 0 && (ap = nxtaddr(headers + sender_offs)));
+ else if (resent_from_offs >= 0 && (ap = nxtaddr(headers + resent_from_offs)));
+ else if (from_offs >= 0 && (ap = nxtaddr(headers + from_offs)));
+ else if (reply_to_offs >= 0 && (ap = nxtaddr(headers + reply_to_offs)));
+ else if (app_from_offs >= 0 && (ap = nxtaddr(headers + app_from_offs)));
+ if (ap) strcpy( return_path, ap );
+ }
+
/* cons up a list of local recipients */
xmit_names = (struct idlist *)NULL;
bad_addresses = good_addresses = accept_count = reject_count = 0;
-#ifdef HAVE_RES_SEARCH
/* is this a multidrop box? */
if (MULTIDROP(ctl))
{
* We have the Received for addressee.
* It has to be a mailserver address, or we
* wouldn't have got here.
+ * We use find_server_names() to let local
+ * hostnames go through.
*/
- map_name(received_for, ctl, &xmit_names);
- else
- {
- int i;
-
+ find_server_names(received_for, ctl, &xmit_names);
+ else {
/*
* We haven't extracted the envelope address.
- * So check all the header addresses.
+ * So check all the "Resent-To" header addresses if
+ * they exist. If and only if they don't, consider
+ * the "To" adresses.
*/
- for (i = 0; i < next_address; i++)
- find_server_names(headers + addressoffs[i], ctl, &xmit_names);
+ register struct addrblk *nextptr;
+ if (resent_to_addrchain) {
+ /* delete the "To" chain and substitute it
+ * with the "Resent-To" list
+ */
+ while (to_addrchain) {
+ nextptr = to_addrchain->next;
+ free(to_addrchain);
+ to_addrchain = nextptr;
+ }
+ to_addrchain = resent_to_addrchain;
+ resent_to_addrchain = NULL;
+ }
+ /* now look for remaining adresses */
+ while (to_addrchain) {
+ find_server_names(headers+to_addrchain->offset, ctl, &xmit_names);
+ nextptr = to_addrchain->next;
+ free(to_addrchain);
+ to_addrchain = nextptr;
+ }
}
if (!accept_count)
{
}
}
else /* it's a single-drop box, use first localname */
-#endif /* HAVE_RES_SEARCH */
save_str(&xmit_names, XMIT_ACCEPT, ctl->localnames->id);
if (outlevel == O_VERBOSE)
error(0,0, "forwarding and deletion suppressed due to DNS errors");
free(headers);
+ free_str_list(&xmit_names);
return(PS_TRANSIENT);
}
else if (ctl->mda) /* we have a declared MDA */
{
int length = 0;
- char *names, *cmd;
+ char *names, *before, *after;
- /*
- * We go through this in order to be able to handle very
- * long lists of users and (re)implement %s.
- */
- for (idp = xmit_names; idp; idp = idp->next)
- if (idp->val.num == XMIT_ACCEPT)
- length += (strlen(idp->id) + 1);
- names = (char *)alloca(length);
- names[0] = '\0';
for (idp = xmit_names; idp; idp = idp->next)
if (idp->val.num == XMIT_ACCEPT)
- {
- strcat(names, idp->id);
- strcat(names, " ");
- }
- cmd = (char *)alloca(strlen(ctl->mda) + length);
- sprintf(cmd, ctl->mda, names);
+ good_addresses++;
+
+ destaddr = "localhost";
+
+ length = strlen(ctl->mda) + 1;
+ before = xstrdup(ctl->mda);
+
+ /* sub user addresses for %T (or %s for backward compatibility) */
+ cp = (char *)NULL;
+ if (strstr(before, "%s") || (cp = strstr(before, "%T")))
+ {
+ char *sp;
+
+ if (cp && cp[1] == 'T')
+ cp[1] = 's';
+
+ /* \177 had better be out-of-band for MDA commands */
+ for (sp = before; *sp; sp++)
+ if (*sp == '%' && sp[1] != 's' && sp[1] != 'T')
+ *sp = '\177';
+
+ /*
+ * We go through this in order to be able to handle very
+ * long lists of users and (re)implement %s.
+ */
+ for (idp = xmit_names; idp; idp = idp->next)
+ if (idp->val.num == XMIT_ACCEPT)
+ length += (strlen(idp->id) + 1);
+
+ names = (char *)xmalloc(++length);
+ names[0] = '\0';
+ for (idp = xmit_names; idp; idp = idp->next)
+ if (idp->val.num == XMIT_ACCEPT)
+ {
+ strcat(names, idp->id);
+ strcat(names, " ");
+ }
+ after = (char *)xmalloc(length);
+#ifdef SNPRINTF
+ snprintf(after, length, before, names);
+#else
+ sprintf(after, before, names);
+#endif /* SNPRINTF */
+ free(names);
+ free(before);
+ before = after;
+
+ for (sp = before; *sp; sp++)
+ if (*sp == '\177')
+ *sp = '%';
+ }
+
+ /* substitute From address for %F */
+ if ((cp = strstr(before, "%F")))
+ {
+ char *from = return_path;
+ char *sp;
+
+ /* \177 had better be out-of-band for MDA commands */
+ for (sp = before; *sp; sp++)
+ if (*sp == '%' && sp[1] != 'F')
+ *sp = '\177';
+
+ length += strlen(from);
+ after = (char *)xmalloc(length);
+ cp[1] = 's';
+#ifdef SNPRINTF
+ snprintf(after, length, before, from);
+#else
+ sprintf(after, before, from);
+#endif /* SNPRINTF */
+ free(before);
+ before = after;
+
+ for (sp = before; *sp; sp++)
+ if (*sp == '\177')
+ *sp = '%';
+ }
+
if (outlevel == O_VERBOSE)
- error(0, 0, "about to deliver with: %s", cmd);
+ error(0, 0, "about to deliver with: %s", before);
#ifdef HAVE_SETEUID
/*
seteuid(ctl->uid);
#endif /* HAVE_SETEUID */
- sinkfp = popen(cmd, "w");
+ sinkfp = popen(before, "w");
+ free(before);
#ifdef HAVE_SETEUID
/* this will fail quietly if we didn't start as root */
if (!sinkfp)
{
- error(0, -1, "MDA open failed");
+ error(0, 0, "MDA open failed");
+ free(headers);
+ free_str_list(&xmit_names);
return(PS_IOERR);
}
/* build a connection to the SMTP listener */
if ((smtp_open(ctl) == -1))
{
- free_str_list(&xmit_names);
- error(0, -1, "SMTP connect to %s failed",
+ error(0, errno, "SMTP connect to %s failed",
ctl->smtphost ? ctl->smtphost : "localhost");
+ free(headers);
+ free_str_list(&xmit_names);
return(PS_SMTP);
}
options[0] = '\0';
if (ctl->server.esmtp_options & ESMTP_8BITMIME)
if (ctl->pass8bits)
- sprintf(options, " BODY=8BITMIME");
+ strcpy(options, " BODY=8BITMIME");
else if ((ctt_offs >= 0) && (ctt = nxtaddr(headers + ctt_offs)))
{
if (!strcasecmp(ctt,"7BIT"))
- sprintf(options, " BODY=7BIT");
+ strcpy(options, " BODY=7BIT");
else if (!strcasecmp(ctt,"8BIT"))
- sprintf(options, " BODY=8BITMIME");
+ strcpy(options, " BODY=8BITMIME");
}
- if ((ctl->server.esmtp_options & ESMTP_SIZE))
- sprintf(options + strlen(options), " SIZE=%ld", len);
+ if ((ctl->server.esmtp_options & ESMTP_SIZE) && reallen > 0)
+ sprintf(options + strlen(options), " SIZE=%ld", reallen);
/*
- * If there is a Return-Path address on the message, this was
- * almost certainly the MAIL FROM address given the originating
- * sendmail. This is the best thing to use for logging the
- * message origin (it sets up the right behavior for bounces and
- * mailing lists). Otherwise, take the From address.
- *
- * Try to get the SMTP listener to take the Return-Path or
- * From address as MAIL FROM . If it won't, fall back on the
+ * Try to get the SMTP listener to take the Return-Path
+ * address as MAIL FROM . If it won't, fall back on the
* calling-user ID. This won't affect replies, which use the
* header From address anyway.
*
* didn't pass canonicalized From/Return-Path lines, *and* the
* local SMTP listener insists on them.
*/
- ap = (char *)NULL;
- if (return_path[0])
- ap = return_path;
- else if (from_offs == -1 || !(ap = nxtaddr(headers + from_offs)))
- ap = user;
+ ap = (return_path[0]) ? return_path : user;
if (SMTP_from(ctl->smtp_socket, ap, options) != SM_OK)
{
int smtperr = atoi(smtp_response);
* don't prevent it from being deleted.
*/
free(headers);
+ free_str_list(&xmit_names);
return(PS_REFUSED);
case 452: /* insufficient system storage */
*/
SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */
free(headers);
+ free_str_list(&xmit_names);
return(PS_TRANSIENT);
case 552: /* message exceeds fixed maximum message size */
*/
SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */
free(headers);
+ free_str_list(&xmit_names);
return(PS_REFUSED);
default: /* retry with invoking user's address */
{
error(0, -1, "SMTP error: %s", smtp_response);
free(headers);
+ free_str_list(&xmit_names);
return(PS_SMTP); /* should never happen */
}
}
* or MX but not a CNAME. Some listeners (like exim)
* enforce this.
*/
+ destaddr = ctl->smtpaddress ? ctl->smtpaddress : ( ctl->smtphost ? ctl->smtphost : "localhost");
+
for (idp = xmit_names; idp; idp = idp->next)
if (idp->val.num == XMIT_ACCEPT)
{
+ if (strchr(idp->id, '@'))
+ strcpy(addr, idp->id);
+ else
#ifdef HAVE_SNPRINTF
- snprintf(addr, sizeof(addr)-1, "%s@%s", idp->id,fetchmailhost);
+ snprintf(addr, sizeof(addr)-1, "%s@%s", idp->id, destaddr);
#else
- sprintf(addr, "%s@%s", idp->id, );
+ sprintf(addr, "%s@%s", idp->id, destaddr);
#endif /* HAVE_SNPRINTF */
if (SMTP_rcpt(ctl->smtp_socket, addr) == SM_OK)
bad_addresses++;
idp->val.num = XMIT_ANTISPAM;
error(0, 0,
- "SMTP listener doesn't like recipient address `%s'", idp->id);
+ "SMTP listener doesn't like recipient address `%s@%s'", idp->id, destaddr);
}
}
if (!good_addresses)
{
#ifdef HAVE_SNPRINTF
- snprintf(addr, sizeof(addr)-1, "%s@%s", idp->id, fetchmailhost);
+ snprintf(addr, sizeof(addr)-1, "%s@%s", user, destaddr);
#else
- sprintf(addr, "%s@%s", idp->id, );
+ sprintf(addr, "%s@%s", user, destaddr);
#endif /* HAVE_SNPRINTF */
- if (SMTP_rcpt(ctl->smtp_socket, user) != SM_OK)
+ if (SMTP_rcpt(ctl->smtp_socket, addr) != SM_OK)
{
error(0, 0, "can't even send to calling user!");
free(headers);
+ free_str_list(&xmit_names);
return(PS_SMTP);
}
}
SMTP_data(ctl->smtp_socket);
}
- /* we may need to strip carriage returns */
- if (ctl->stripcr)
+ n = 0;
+ /*
+ * Some server/sendmail combinations cause problems when our
+ * synthetic Received line is before the From header. Cope
+ * with this...
+ */
+ if ((rcv = strstr(headers, "Received:")) == (char *)NULL)
+ rcv = headers;
+ if (rcv > headers)
{
- char *sp, *tp;
+ *rcv = '\0';
+ n = stuffline(ctl, headers);
+ *rcv = 'R';
+ }
+ if (!use_invisible && n != -1)
+ {
+ /* utter any per-message Received information we need here */
+ sprintf(buf, "Received: from %s\n", ctl->server.truename);
+ n = stuffline(ctl, buf);
+ if (n != -1)
+ {
+ /*
+ * We used to include ctl->remotename in this log line,
+ * but this can be secure information that would be bad
+ * to reveal.
+ */
+ sprintf(buf, "\tby %s (fetchmail-%s %s)\n",
+ fetchmailhost,
+ RELEASE_ID,
+ protocol->name);
+ n = stuffline(ctl, buf);
+ if (n != -1)
+ {
+ time_t now;
- for (sp = tp = headers; *sp; sp++)
- if (*sp != '\r')
- *tp++ = *sp;
- *tp = '\0';
+ buf[0] = '\t';
+ if (good_addresses == 0)
+ {
+ sprintf(buf+1,
+ "for <%s@%s> (by default); ",
+ user, destaddr);
+ }
+ else if (good_addresses == 1)
+ {
+ for (idp = xmit_names; idp; idp = idp->next)
+ if (idp->val.num == XMIT_ACCEPT)
+ break; /* only report first address */
+ sprintf(buf+1, "for <%s@%s> (%s); ",
+ idp->id, destaddr,
+ MULTIDROP(ctl) ? "multi-drop" : "single-drop");
+ }
+ else
+ buf[1] = '\0';
+ time(&now);
+#ifdef HAVE_STRFTIME
+ /*
+ * Conform to RFC822. This is typically going to emit
+ * a three-letter timezone for %Z, which is going to
+ * be marked "obsolete syntax" in 822bis. Note that we
+ * generate a 4-digit year here.
+ */
+ strftime(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ "%a, %d %b %Y %H:%M:%S %Z\n", localtime(&now));
+#else
+ /*
+ * This is really just a portability fallback, as the
+ * date format ctime(3) emits is not RFC822
+ * conformant.
+ */
+ strcat(buf, ctime(&now));
+#endif /* HAVE_STRFTIME */
+ n = stuffline(ctl, buf);
+ }
+ }
}
- /* write all the headers */
- n = 0;
- if (ctl->mda && sinkfp)
- n = fwrite(headers, 1, strlen(headers), sinkfp);
- else if (ctl->smtp_socket != -1)
- n = SockWrite(ctl->smtp_socket, headers, strlen(headers));
- free(headers);
+ if (n != -1)
+ n = stuffline(ctl, rcv); /* ship out rest of headers */
- if (n < 0)
+ if (n == -1)
{
error(0, errno, "writing RFC822 headers");
if (ctl->mda)
{
- pclose(sinkfp);
+ if (sinkfp)
+ pclose(sinkfp);
signal(SIGCHLD, sigchld);
}
+ free(headers);
+ free_str_list(&xmit_names);
return(PS_IOERR);
}
else if (outlevel == O_VERBOSE)
fputs("#", stderr);
/* write error notifications */
-#ifdef HAVE_RES_SEARCH
if (no_local_matches || has_nuls || bad_addresses)
-#else
- if (has_nuls || bad_addresses)
-#endif /* HAVE_RES_SEARCH */
{
int errlen = 0;
char errhd[USERNAMELEN + POPBUFSIZE], *errmsg;
errmsg = errhd;
(void) strcpy(errhd, "X-Fetchmail-Warning: ");
-#ifdef HAVE_RES_SEARCH
if (no_local_matches)
{
if (reject_count != 1)
sprintf(errhd+strlen(errhd), "recipient address %s didn't match any local name", idp->id);
}
}
-#endif /* HAVE_RES_SEARCH */
if (has_nuls)
{
}
+ strcat(errmsg, "\n");
+
/* ship out the error line */
if (sinkfp)
- stuffline(ctl, errmsg, protocol->delimited);
+ stuffline(ctl, errmsg);
}
- free_str_list(&xmit_names);
-
/* issue the delimiter line */
cp = buf;
*cp++ = '\r';
*cp++ = '\n';
*cp++ = '\0';
- stuffline(ctl, buf, protocol->delimited);
+ stuffline(ctl, buf);
+ free(headers);
+ free_str_list(&xmit_names);
return(PS_SUCCESS);
}
-static int readbody(sock, ctl, forward, len, delimited)
+static int readbody(sock, ctl, forward, len)
/* read and dispose of a message body presented on sock */
struct query *ctl; /* query control record */
int sock; /* to which the server is connected */
int len; /* length of message */
flag forward; /* TRUE to forward */
-flag delimited; /* does the protocol use a message delimiter? */
{
int linelen;
- char buf[MSGBUFSIZE+1], *cp;
+ char buf[MSGBUFSIZE+1];
/* pass through the text lines */
- while (delimited || len > 0)
+ while (protocol->delimited || len > 0)
{
if ((linelen = SockRead(sock, buf, sizeof(buf)-1)) == -1)
{
if (ctl->mda)
{
- pclose(sinkfp);
+ if (sinkfp)
+ pclose(sinkfp);
signal(SIGCHLD, sigchld);
}
return(PS_SOCKET);
len -= linelen;
/* check for end of message */
- if (delimited && *buf == '.')
+ if (protocol->delimited && *buf == '.')
if (buf[1] == '\r' && buf[2] == '\n' && buf[3] == '\0')
break;
else if (buf[1] == '\n' && buf[2] == '\0')
break;
+ else
+ msglen--; /* subtract the size of the dot escape */
+
+ msglen += linelen;
/* ship out the text line */
if (forward)
{
- int n = stuffline(ctl, buf, delimited);
+ int n = stuffline(ctl, buf);
if (n < 0)
{
error(0, errno, "writing message text");
if (ctl->mda)
{
- pclose(sinkfp);
+ if (sinkfp)
+ pclose(sinkfp);
signal(SIGCHLD, sigchld);
}
return(PS_IOERR);
kerberos_auth (socket, canonical)
/* authenticate to the server host using Kerberos V4 */
int socket; /* socket to server host */
+#ifdef __FreeBSD__
+char *canonical; /* server name */
+#else
const char *canonical; /* server name */
+#endif
{
char * host_primary;
KTEXT ticket;
if (rem != KSUCCESS)
{
error(0, -1, "kerberos error %s", (krb_get_err_text (rem)));
- return (PS_ERROR);
+ return (PS_AUTHFAIL);
}
return (0);
}
struct query *ctl; /* parsed options with merged-in defaults */
const struct method *proto; /* protocol method table */
{
- int ok, js, pst, sock = -1;
- char *msg, *cp, realname[HOSTLEN];
+ int ok, js, sock = -1;
+ char *msg;
void (*sigsave)();
#ifndef KERBEROS_V4
return(PS_SYNTAX);
}
}
- if (!proto->getsizes && ctl->limit)
+ if (!proto->getsizes && NUM_SPECIFIED(ctl->limit))
{
error(0, 0,
"Option --limit is not supported with %s",
}
protocol = proto;
+ pass = 0;
tagnum = 0;
tag[0] = '\0'; /* nuke any tag hanging out from previous query */
ok = 0;
- error_init(poll_interval == 0 && !logfile);
/* set up the server-nonresponse timeout */
sigsave = signal(SIGALRM, timeout_handler);
if ((js = setjmp(restart)) == 1)
{
- error(0, 0,
- "timeout after %d seconds waiting for %s.",
- ctl->server.timeout, ctl->server.pollname);
+ if (phase == SERVER_WAIT)
+ error(0, 0,
+ "timeout after %d seconds waiting for server %s.",
+ ctl->server.timeout, ctl->server.pollname);
+ else if (phase == FORWARDING_WAIT)
+ error(0, 0,
+ "timeout after %d seconds waiting for %s.",
+ ctl->server.timeout,
+ ctl->mda ? "MDA" : "SMTP");
+ else
+ error(0, 0, "timeout after %d seconds.", ctl->server.timeout);
+
if (ctl->smtp_socket != -1)
close(ctl->smtp_socket);
if (sock != -1)
close(sock);
+ if (sinkfp)
+ pclose(sinkfp);
ok = PS_ERROR;
}
else
{
- char buf [POPBUFSIZE+1], *sp, *realhost;
+ char buf [POPBUFSIZE+1], *realhost;
int *msgsizes, len, num, count, new, deletions = 0;
- int port, fetches;
+#if INET6
+ int fetches, dispatches;
+#else /* INET6 */
+ int port, fetches, dispatches;
+#endif /* INET6 */
struct idlist *idp;
/* execute pre-initialization command, if any */
}
/* open a socket to the mail server */
+#if !INET6
port = ctl->server.port ? ctl->server.port : protocol->port;
+#endif /* !INET6 */
realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
+#if INET6
+ if ((sock = SockOpen(realhost,
+ ctl->server.service ? ctl->server.service : protocol->service,
+ ctl->server.netsec)) == -1)
+#else /* INET6 */
if ((sock = SockOpen(realhost, port)) == -1)
+#endif /* INET6 */
{
+#if !INET6
#ifndef EHOSTUNREACH
#define EHOSTUNREACH (-1)
#endif
{
error_build("fetchmail: %s connection to %s failed: ",
protocol->name, ctl->server.pollname);
+#ifdef HAVE_RES_SEARCH
if (h_errno == HOST_NOT_FOUND)
error_complete(0, 0, "host is unknown");
else if (h_errno == NO_ADDRESS)
else if (h_errno)
error_complete(0, 0, "unknown DNS error %d", h_errno);
else
+#endif /* HAVE_RES_SEARCH */
error_complete(0, errno, "local error");
}
+#endif /* INET6 */
ok = PS_SOCKET;
goto closeUp;
}
goto cleanUp;
set_timeout(ctl->server.timeout);
- /*
- * Try to parse the host's actual name out of the greeting
- * message. We do this so that the progress messages will
- * make sense even if the connection is indirected through
- * ssh. *Do* use this for hacking reply headers, but *don't*
- * use it for error logging, as the names in the log should
- * correlate directly back to rc file entries.
- *
- * This assumes that the first space-delimited token found
- * that contains at least two dots (with the characters on
- * each side of the dot alphanumeric to exclude version
- * numbers) is the hostname. The hostname candidate may not
- * contain @ -- if it does it's probably a mailserver
- * maintainer's name. If no such token is found, fall back on
- * the .fetchmailrc id.
- */
- pst = 0;
- sp = (char *)NULL; /* sacrifice to -Wall */
- for (cp = buf; *cp; cp++)
- {
- switch (pst)
- {
- case 0: /* skip to end of current token */
- if (*cp == ' ')
- pst = 1;
- break;
-
- case 1: /* look for blank-delimited token */
- if (*cp != ' ')
- {
- sp = cp;
- pst = 2;
- }
- break;
-
- case 2: /* look for first dot */
- if (*cp == '@')
- pst = 0;
- else if (*cp == ' ')
- pst = 1;
- else if (*cp == '.' && isalpha(cp[1]) && isalpha(cp[-1]))
- pst = 3;
- break;
-
- case 3: /* look for second dot */
- if (*cp == '@')
- pst = 0;
- else if (*cp == ' ')
- pst = 1;
- else if (*cp == '.' && isalpha(cp[1]) && isalpha(cp[-1]))
- pst = 4;
- break;
-
- case 4: /* look for trailing space */
- if (*cp == '@')
- pst = 0;
- else if (*cp == ' ')
- {
- pst = 5;
- goto done;
- }
- break;
- }
- }
- done:
- if (pst == 5)
- {
- char *tp = realname;
-
- while (sp < cp)
- *tp++ = *sp++;
- *tp = '\0';
- }
- else
- strcpy(realname, ctl->server.pollname);
-
/* try to get authorized to fetch mail */
if (protocol->getauth)
{
if (ok == PS_LOCKBUSY)
error(0, -1, "Lock-busy error on %s@%s",
ctl->remotename,
- realname);
+ ctl->server.truename);
else
{
if (ok == PS_ERROR)
ok = PS_AUTHFAIL;
error(0, -1, "Authorization failure on %s@%s",
ctl->remotename,
- realname);
+ ctl->server.truename);
}
goto cleanUp;
}
/* now iterate over each folder selected */
for (idp = ctl->mailboxes; idp; idp = idp->next)
{
- if (outlevel >= O_VERBOSE)
- if (idp->next)
- error(0, 0, "selecting folder %s");
- else
- error(0, 0, "selecting default folder");
-
- /* compute number of messages and number of new messages waiting */
- ok = (protocol->getrange)(sock, ctl, idp->id, &count, &new);
- if (ok != 0)
- goto cleanUp;
- set_timeout(ctl->server.timeout);
+ pass = 0;
+ do {
+ dispatches = 0;
+ ++pass;
- /* show user how many messages we downloaded */
- if (idp->id)
- (void) sprintf(buf, "%s@%s:%s",
- ctl->remotename, realname, idp->id);
- else
- (void) sprintf(buf, "%s@%s", ctl->remotename, realname);
- if (outlevel > O_SILENT)
- if (count == -1) /* only used for ETRN */
- error(0, 0, "Polling %s", buf);
- else if (count == 0)
- {
- /* these are pointless in normal daemon mode */
- if (poll_interval == 0 || outlevel == O_VERBOSE )
- error(0, 0, "No mail at %s", buf);
- }
+ if (outlevel >= O_VERBOSE)
+ if (idp->id)
+ error(0, 0, "selecting or re-polling folder %s", idp->id);
+ else
+ error(0, 0, "selecting or re-polling default folder");
+
+ /* compute # of messages and number of new messages waiting */
+ ok = (protocol->getrange)(sock, ctl, idp->id, &count, &new);
+ if (ok != 0)
+ goto cleanUp;
+ set_timeout(ctl->server.timeout);
+
+ /* show user how many messages we downloaded */
+ if (idp->id)
+ (void) sprintf(buf, "%s at %s (folder %s)",
+ ctl->remotename, ctl->server.truename, idp->id);
else
- {
- if (new != -1 && (count - new) > 0)
- error(0, 0, "%d message%s (%d seen) at %s.",
- count, count > 1 ? "s" : "", count-new, buf);
+ (void) sprintf(buf, "%s at %s", ctl->remotename, ctl->server.truename);
+ if (outlevel > O_SILENT)
+ if (count == -1) /* only used for ETRN */
+ error(0, 0, "Polling %s", ctl->server.truename);
+ else if (count != 0)
+ {
+ if (new != -1 && (count - new) > 0)
+ error(0, 0, "%d message%s (%d seen) for %s.",
+ count, count > 1 ? "s" : "", count-new, buf);
+ else
+ error(0, 0, "%d message%s for %s.",
+ count, count > 1 ? "s" : "", buf);
+ }
else
- error(0, 0, "%d message%s at %s.",
- count, count > 1 ? "s" : "", buf);
- }
-
- if (check_only)
- {
- if (new == -1 || ctl->fetchall)
- new = count;
- ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
- goto cleanUp;
- }
- else if (count > 0)
- {
- flag force_retrieval;
+ {
+ /* these are pointless in normal daemon mode */
+ if (pass == 1 && (poll_interval == 0 || outlevel == O_VERBOSE))
+ error(0, 0, "No mail for %s", buf);
+ }
- /*
- * What forces this code is that in POP3 and IMAP2BIS you can't
- * fetch a message without having it marked `seen'. In IMAP4,
- * on the other hand, you can (peek_capable is set to convey
- * this).
- *
- * The result of being unable to peek is that if there's
- * any kind of transient error (DNS lookup failure, or
- * sendmail refusing delivery due to process-table limits)
- * the message will be marked "seen" on the server without
- * having been delivered. This is not a big problem if
- * fetchmail is running in foreground, because the user
- * will see a "skipped" message when it next runs and get
- * clued in.
- *
- * But in daemon mode this leads to the message being silently
- * ignored forever. This is not acceptable.
- *
- * We compensate for this by checking the error count from the
- * previous pass and forcing all messages to be considered new
- * if it's nonzero.
- */
- force_retrieval = !peek_capable && (ctl->errcount > 0);
+ /* very important, this is where we leave the do loop */
+ if (count == 0)
+ break;
- /*
- * We may need to get sizes in order to check message
- * limits. Or it may be forced because the fetch methods
- * don't return reliable sizes.
- */
- msgsizes = (int *)NULL;
- if (proto->getsizes && (proto->force_getsizes || ctl->limit > 0))
+ if (check_only)
{
- msgsizes = (int *)alloca(sizeof(int) * count);
-
- ok = (proto->getsizes)(sock, count, msgsizes);
- if (ok != 0)
- goto cleanUp;
- set_timeout(ctl->server.timeout);
+ if (new == -1 || ctl->fetchall)
+ new = count;
+ ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
+ goto cleanUp;
}
+ else if (count > 0)
+ {
+ flag force_retrieval;
- /* read, forward, and delete messages */
- for (num = 1; num <= count; num++)
- {
- flag toolarge = (ctl->limit > 0)
- && msgsizes && (msgsizes[num-1] > ctl->limit);
- flag fetch_it = !toolarge
- && (ctl->fetchall || force_retrieval || !(protocol->is_old && (protocol->is_old)(sock,ctl,num)));
- flag suppress_delete = FALSE;
- flag suppress_forward = FALSE;
- flag retained = FALSE;
-
- /* we may want to reject this message if it's old */
- if (!fetch_it)
- {
- if (outlevel > O_SILENT)
- {
- error_build("skipping message %d", num);
- if (toolarge)
- error_build(" (oversized, %d bytes)", msgsizes[num-1]);
- }
- }
- else
+ /*
+ * What forces this code is that in POP3 and
+ * IMAP2BIS you can't fetch a message without
+ * having it marked `seen'. In IMAP4, on the
+ * other hand, you can (peek_capable is set to
+ * convey this).
+ *
+ * The result of being unable to peek is that if there's
+ * any kind of transient error (DNS lookup failure, or
+ * sendmail refusing delivery due to process-table limits)
+ * the message will be marked "seen" on the server without
+ * having been delivered. This is not a big problem if
+ * fetchmail is running in foreground, because the user
+ * will see a "skipped" message when it next runs and get
+ * clued in.
+ *
+ * But in daemon mode this leads to the message
+ * being silently ignored forever. This is not
+ * acceptable.
+ *
+ * We compensate for this by checking the error
+ * count from the previous pass and forcing all
+ * messages to be considered new if it's nonzero.
+ */
+ force_retrieval = !peek_capable && (ctl->errcount > 0);
+
+ /*
+ * We need the size of each message before it's
+ * loaded in order to pass via the ESMTP SIZE
+ * option. If the protocol has a getsizes method,
+ * we presume this means it doesn't get reliable
+ * sizes from message fetch responses.
+ */
+ if (proto->getsizes)
{
- flag wholesize = !protocol->fetch_body;
+ int i;
+
+ msgsizes = (int *)alloca(sizeof(int) * count);
+ for (i = 0; i < count; i++)
+ msgsizes[i] = -1;
- /* request a message */
- ok = (protocol->fetch_headers)(sock, ctl, num, &len);
+ ok = (proto->getsizes)(sock, count, msgsizes);
if (ok != 0)
goto cleanUp;
set_timeout(ctl->server.timeout);
+ }
- /* -1 means we didn't see a size in the response */
- if (len == -1 && msgsizes)
+ /* read, forward, and delete messages */
+ for (num = 1; num <= count; num++)
+ {
+ flag toolarge = NUM_NONZERO(ctl->limit)
+ && msgsizes && (msgsizes[num-1] > ctl->limit);
+ flag fetch_it = !toolarge
+ && (ctl->fetchall || force_retrieval || !(protocol->is_old && (protocol->is_old)(sock,ctl,num)));
+ flag suppress_delete = FALSE;
+ flag suppress_forward = FALSE;
+ flag retained = FALSE;
+
+ /*
+ * This check copes with Post Office/NT's
+ * annoying habit of randomly prepending bogus
+ * LIST items of length -1. Patrick Audley
+ * <paudley@pobox.com> tells us: LIST shows a
+ * size of -1, RETR and TOP return "-ERR
+ * System error - couldn't open message", and
+ * DELE succeeds but doesn't actually delete
+ * the message.
+ */
+ if (msgsizes && msgsizes[num-1] == -1)
{
- len = msgsizes[num - 1];
- wholesize = TRUE;
+ if (outlevel >= O_VERBOSE)
+ error(0, 0,
+ "Skipping message %d, length -1",
+ num - 1);
+ continue;
}
- if (outlevel > O_SILENT)
+ /* we may want to reject this message if it's old */
+ if (!fetch_it)
{
- error_build("reading message %d of %d",num,count);
-
- if (len > 0)
- error_build(" (%d %sbytes)",
- len, wholesize ? "" : "header ");
- if (outlevel == O_VERBOSE)
- error_complete(0, 0, "");
- else
- error_build(" ");
+ if (outlevel > O_SILENT)
+ {
+ error_build("skipping message %d", num);
+ if (toolarge)
+ error_build(" (oversized, %d bytes)",
+ msgsizes[num-1]);
+ }
}
-
- /*
- * Read the message headers and ship them to the
- * output sink.
- */
- ok = readheaders(sock, len, ctl, realname, num);
- if (ok == PS_RETAINED)
- suppress_forward = retained = TRUE;
- else if (ok == PS_TRANSIENT)
- suppress_delete = suppress_forward = TRUE;
- else if (ok == PS_REFUSED)
- suppress_forward = TRUE;
- else if (ok)
- goto cleanUp;
- set_timeout(ctl->server.timeout);
-
- /*
- * If we're using IMAP4 or something else that
- * can fetch headers separately from bodies,
- * it's time to request the body now. This
- * fetch may be skipped if we got an anti-spam
- * or other PS_REFUSED error response during
- * read_headers.
- */
- if (protocol->fetch_body)
+ else
{
- if ((ok = (protocol->trail)(sock, ctl, num)))
+ flag wholesize = !protocol->fetch_body;
+
+ /* request a message */
+ ok = (protocol->fetch_headers)(sock,ctl,num, &len);
+ if (ok != 0)
goto cleanUp;
set_timeout(ctl->server.timeout);
- len = 0;
- if (!suppress_forward)
+
+ /* -1 means we didn't see a size in the response */
+ if (len == -1 && msgsizes)
{
- if ((ok=(protocol->fetch_body)(sock,ctl,num,&len)))
- goto cleanUp;
- if (outlevel > O_SILENT && !msgsizes)
- error_build(" (%d body bytes) ", len);
- set_timeout(ctl->server.timeout);
+ len = msgsizes[num - 1];
+ wholesize = TRUE;
}
- }
- /* process the body now */
- if (len > 0)
- {
- ok = readbody(sock,
- ctl,
- !suppress_forward,
- len,
- protocol->delimited);
- if (ok == PS_TRANSIENT)
+ if (outlevel > O_SILENT)
+ {
+ error_build("reading message %d of %d",
+ num,count);
+
+ if (len > 0)
+ error_build(" (%d %sbytes)",
+ len, wholesize ? "" : "header ");
+ if (outlevel == O_VERBOSE)
+ error_complete(0, 0, "");
+ else
+ error_build(" ");
+ }
+
+ /* later we'll test for this before closing */
+ sinkfp = (FILE *)NULL;
+
+ /*
+ * Read the message headers and ship them to the
+ * output sink.
+ */
+ ok = readheaders(sock, len, msgsizes[num-1],
+ ctl, num);
+ if (ok == PS_RETAINED)
+ suppress_forward = retained = TRUE;
+ else if (ok == PS_TRANSIENT)
suppress_delete = suppress_forward = TRUE;
+ else if (ok == PS_REFUSED)
+ suppress_forward = TRUE;
else if (ok)
goto cleanUp;
set_timeout(ctl->server.timeout);
- /* tell server we got it OK and resynchronize */
- if (protocol->trail)
+ /*
+ * If we're using IMAP4 or something else that
+ * can fetch headers separately from bodies,
+ * it's time to request the body now. This
+ * fetch may be skipped if we got an anti-spam
+ * or other PS_REFUSED error response during
+ * read_headers.
+ */
+ if (protocol->fetch_body)
{
- ok = (protocol->trail)(sock, ctl, num);
- if (ok != 0)
+ if (outlevel == O_VERBOSE)
+ fputc('\n', stderr);
+
+ if ((ok = (protocol->trail)(sock, ctl, num)))
goto cleanUp;
set_timeout(ctl->server.timeout);
+ len = 0;
+ if (!suppress_forward)
+ {
+ if ((ok=(protocol->fetch_body)(sock,ctl,num,&len)))
+ goto cleanUp;
+ if (outlevel > O_SILENT && !wholesize)
+ error_build(" (%d body bytes) ", len);
+ set_timeout(ctl->server.timeout);
+ }
}
- }
- /* end-of-message processing starts here */
- if (outlevel == O_VERBOSE)
- fputc('\n', stderr);
+ /* process the body now */
+ if (len > 0)
+ {
+ ok = readbody(sock,
+ ctl,
+ !suppress_forward,
+ len);
+ if (ok == PS_TRANSIENT)
+ suppress_delete = suppress_forward = TRUE;
+ else if (ok)
+ goto cleanUp;
+ set_timeout(ctl->server.timeout);
- if (ctl->mda)
- {
- int rc;
+ /* tell server we got it OK and resynchronize */
+ if (protocol->trail)
+ {
+ if (outlevel == O_VERBOSE)
+ fputc('\n', stderr);
+
+ ok = (protocol->trail)(sock, ctl, num);
+ if (ok != 0)
+ goto cleanUp;
+ set_timeout(ctl->server.timeout);
+ }
+ }
+
+ /* count # messages forwarded on this pass */
+ if (!suppress_forward)
+ dispatches++;
+
+ /*
+ * Check to see if the numbers matched?
+ *
+ * Yes, some servers foo this up horribly.
+ * All IMAP servers seem to get it right, and
+ * so does Eudora QPOP at least in 2.xx
+ * versions.
+ *
+ * Microsoft Exchange gets it completely
+ * wrong, reporting compressed rather than
+ * actual sizes (so the actual length of
+ * message is longer than the reported size).
+ * Another fine example of Microsoft brain death!
+ *
+ * Some older POP servers, like the old UCB
+ * POP server and the pre-QPOP QUALCOMM
+ * versions, report a longer size in the LIST
+ * response than actually gets shipped up.
+ * It's unclear what is going on here, as the
+ * QUALCOMM server (at least) seems to be
+ * reporting the on-disk size correctly.
+ */
+ if (msgsizes && msglen != msgsizes[num-1])
+ {
+ if (outlevel >= O_VERBOSE)
+ error(0, 0,
+ "message %d was not the expected length (%d != %d)",
+ num, msglen, msgsizes[num-1]);
+ }
- /* close the delivery pipe, we'll reopen before next message */
- rc = pclose(sinkfp);
- signal(SIGCHLD, sigchld);
- if (rc)
+ /* end-of-message processing starts here */
+
+ if (ctl->mda)
{
- error(0, -1, "MDA exited abnormally or returned nonzero status");
- goto cleanUp;
+ int rc;
+
+ /* close the delivery pipe, we'll reopen before next message */
+ if (sinkfp)
+ rc = pclose(sinkfp);
+ else
+ rc = 0;
+ signal(SIGCHLD, sigchld);
+ if (rc)
+ {
+ error(0, -1, "MDA exited abnormally or returned nonzero status");
+ goto cleanUp;
+ }
}
- }
- else if (!suppress_forward)
- {
- /* write message terminator */
- if (SMTP_eom(ctl->smtp_socket) != SM_OK)
+ else if (!suppress_forward)
{
- error(0, -1, "SMTP listener refused delivery");
- ctl->errcount++;
- suppress_delete = TRUE;
+ /* write message terminator */
+ if (SMTP_eom(ctl->smtp_socket) != SM_OK)
+ {
+ error(0, -1, "SMTP listener refused delivery");
+ ctl->errcount++;
+ suppress_delete = TRUE;
+ }
}
+
+ fetches++;
}
- fetches++;
- }
+ /*
+ * At this point in flow of control, either
+ * we've bombed on a protocol error or had
+ * delivery refused by the SMTP server
+ * (unlikely -- I've never seen it) or we've
+ * seen `accepted for delivery' and the
+ * message is shipped. It's safe to mark the
+ * message seen and delete it on the server
+ * now.
+ */
- /*
- * At this point in flow of control, either we've bombed
- * on a protocol error or had delivery refused by the SMTP
- * server (unlikely -- I've never seen it) or we've seen
- * `accepted for delivery' and the message is shipped.
- * It's safe to mark the message seen and delete it on the
- * server now.
- */
+ /* tell the UID code we've seen this */
+ if (ctl->newsaved)
+ for (idp = ctl->newsaved; idp; idp = idp->next)
+ if (idp->val.num == num)
+ MARK_SEEN(idp->val.num);
- /* maybe we delete this message now? */
- if (retained)
- {
- if (outlevel > O_SILENT)
- error_complete(0, 0, " retained");
- }
- else if (protocol->delete
- && !suppress_delete
- && (fetch_it ? !ctl->keep : ctl->flush))
- {
- deletions++;
- if (outlevel > O_SILENT)
- error_complete(0, 0, " flushed");
- ok = (protocol->delete)(sock, ctl, num);
- if (ok != 0)
- goto cleanUp;
- set_timeout(ctl->server.timeout);
- delete_str(&ctl->newsaved, num);
- }
- else if (outlevel > O_SILENT)
- error_complete(0, 0, " not flushed");
+ /* maybe we delete this message now? */
+ if (retained)
+ {
+ if (outlevel > O_SILENT)
+ error_complete(0, 0, " retained");
+ }
+ else if (protocol->delete
+ && !suppress_delete
+ && (fetch_it ? !ctl->keep : ctl->flush))
+ {
+ deletions++;
+ if (outlevel > O_SILENT)
+ error_complete(0, 0, " flushed");
+ ok = (protocol->delete)(sock, ctl, num);
+ if (ok != 0)
+ goto cleanUp;
+ set_timeout(ctl->server.timeout);
+#ifdef POP3_ENABLE
+ delete_str(&ctl->newsaved, num);
+#endif /* POP3_ENABLE */
+ }
+ else if (outlevel > O_SILENT)
+ error_complete(0, 0, " not flushed");
- /* perhaps this as many as we're ready to handle */
- if (ctl->fetchlimit > 0 && ctl->fetchlimit <= fetches)
- goto no_error;
+ /* perhaps this as many as we're ready to handle */
+ if (NUM_NONZERO(ctl->fetchlimit) && ctl->fetchlimit <= fetches)
+ goto no_error;
+ }
}
- }
+ } while
+ /*
+ * Only re-poll if we had some actual forwards, allowed
+ * deletions and had no errors.
+ * Otherwise it is far too easy to get into infinite loops.
+ */
+ (dispatches && protocol->retry && !ctl->keep && !ctl->errcount);
}
no_error:
set_timeout(ctl->server.timeout);
- ok = gen_transact(sock, protocol->exit_cmd);
+ ok = (protocol->logout_cmd)(sock, ctl);
+ /*
+ * Hmmmm...arguably this would be incorrect if we had fetches but
+ * no dispatches (due to oversized messages, etc.)
+ */
if (ok == 0)
ok = (fetches > 0) ? PS_SUCCESS : PS_NOMAIL;
set_timeout(0);
cleanUp:
set_timeout(ctl->server.timeout);
if (ok != 0 && ok != PS_SOCKET)
- gen_transact(sock, protocol->exit_cmd);
+ (protocol->logout_cmd)(sock, ctl);
set_timeout(0);
close(sock);
}
case PS_SMTP:
msg = "SMTP transaction";
break;
+ case PS_DNS:
+ msg = "DNS lookup";
+ break;
case PS_UNDEFINED:
error(0, 0, "undefined");
break;
}
#if defined(HAVE_STDARG_H)
-void gen_send(int sock, char *fmt, ... )
+void gen_send(int sock, const char *fmt, ... )
/* assemble command in printf(3) style and send to the server */
#else
void gen_send(sock, fmt, va_alist)
char *buf; /* buffer to receive input */
int size; /* length of buffer */
{
+ int oldphase = phase; /* we don't have to be re-entrant */
+
+ phase = SERVER_WAIT;
if (SockRead(sock, buf, size) == -1)
+ {
+ phase = oldphase;
return(PS_SOCKET);
+ }
else
{
if (buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\r';
if (outlevel == O_VERBOSE)
error(0, 0, "%s< %s", protocol->name, buf);
+ phase = oldphase;
return(PS_SUCCESS);
}
}
int ok;
char buf [POPBUFSIZE+1];
va_list ap;
+ int oldphase = phase; /* we don't have to be re-entrant */
+
+ phase = SERVER_WAIT;
if (protocol->tagged)
(void) sprintf(buf, "%s ", GENSYM);
ok = (protocol->parse_response)(sock, buf);
set_timeout(mytimeout);
+ phase = oldphase;
return(ok);
}