#include <config.h>
#include <stdio.h>
#include <setjmp.h>
+#include <errno.h>
#include <ctype.h>
#include <string.h>
#if defined(STDC_HEADERS)
#else
#include <varargs.h>
#endif
+#if defined(HAVE_ALLOCA_H)
+#include <alloca.h>
+#endif
#include <sys/time.h>
#include <signal.h>
#include "fetchmail.h"
#include "smtp.h"
+/* BSD portability hack...I know, this is an ugly place to put it */
+#if !defined(SIGCHLD) && defined(SIGCLD)
+#define SIGCHLD SIGCLD
+#endif
+
#define SMTP_PORT 25 /* standard SMTP service port */
+int batchlimit; /* how often to tear down the delivery connection */
+int batchcount; /* count of messages sent in current batch */
+int peek_capable; /* can we peek for better error recovery? */
+
static const struct method *protocol;
static jmp_buf restart;
return len;
}
-static void vtalarm(timeleft)
+static void vtalarm(int timeleft)
/* reset the nonresponse-timeout */
-int timeleft;
{
struct itimerval ntimeout;
- ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_sec = 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_VIRTUAL, &ntimeout, (struct itimerval *)NULL);
longjmp(restart, 1);
}
-static void reply_hack(buf, host)
-/* hack message headers so replies will work properly */
-char *buf; /* header to be hacked */
-const char *host; /* server hostname */
-{
- const char *from;
- int parendepth, state = 0, tokencount = 0;
- char mycopy[POPBUFSIZE+1];
-
- if (strncmp("From: ", buf, 6)
- && strncmp("To: ", buf, 4)
- && strncmp("Reply-", buf, 6)
- && strncmp("Cc: ", buf, 4)
- && strncmp("Bcc: ", buf, 5)) {
- return;
- }
-
- strcpy(mycopy, buf);
- for (from = mycopy; *from; from++)
- {
- switch (state)
- {
- case 0: /* before header colon */
- if (*from == ':')
- state = 1;
- break;
-
- case 1: /* we've seen the colon, we're looking for addresses */
- if (*from == '"')
- state = 3;
- else if (*from == '(')
- {
- parendepth = 1;
- state = 4;
- }
- else if (*from == '<' || isalnum(*from))
- state = 5;
- else if (isspace(*from))
- state = 2;
- break;
-
- case 2: /* found a token boundary -- reset without copying */
- if (*from != ' ' && *from != '\t')
- {
- tokencount++;
- state = 1;
- --from;
- continue;
- }
-
- case 3: /* we're in a quoted human name, copy and ignore */
- if (*from == '"')
- state = 1;
- break;
-
- case 4: /* we're in a parenthesized human name, copy and ignore */
- if (*from == '(')
- ++parendepth;
- else if (*from == ')')
- --parendepth;
- if (parendepth == 0)
- state = 1;
- break;
-
- case 5: /* the real work gets done here */
- /*
- * We're in something that might be an address part,
- * either a bare unquoted/unparenthesized text or text
- * enclosed in <> as per RFC822.
- */
- /* if the address part contains an @, don't mess with it */
- if (*from == '@')
- state = 6;
-
- /* If the address token is not properly terminated, ignore it. */
- else if (*from == ' ' || *from == '\t')
- {
- const char *cp;
-
- /*
- * The only lookahead case. If we're looking at space or tab,
- * we might be looking at a local name immediately followed
- * by a human name.
- */
- for (cp = from; isspace(*cp); cp++)
- continue;
- if (*cp == '(')
- {
- strcpy(buf, "@");
- strcat(buf, host);
- buf += strlen(buf);
- state = 1;
- }
- }
-
- /*
- * On proper termination with no @, insert hostname.
- * Case '>' catches <>-enclosed mail IDs. Case ',' catches
- * comma-separated bare IDs.
- */
- else if (strchr(">,", *from))
- {
- strcpy(buf, "@");
- strcat(buf, host);
- buf += strlen(buf);
- tokencount = 0;
- state = 1;
- }
-
- /* a single local name alone on the line */
- else if (*from == '\n' && tokencount == 1)
- {
- strcpy(buf, "@");
- strcat(buf, host);
- buf += strlen(buf);
- state = 2;
- }
-
- /* everything else, including alphanumerics, just passes through */
- break;
-
- case 6: /* we're in a remote mail ID, no need to append hostname */
- if (*from == '>' || *from == ',' || isspace(*from))
- state = 1;
- break;
- }
-
- /* all characters from the old buffer get copied to the new one */
- *buf++ = *from;
- }
- *buf++ = '\0';
-}
-
-static char *nxtaddr(hdr)
-/* parse addresses in succession out of a specified RFC822 header */
-char *hdr; /* header line to be parsed, NUL to continue in previous hdr */
-{
- static char *hp, *tp, address[POPBUFSIZE+1];
- static int state;
- int parendepth;
-
- /*
- * Note 1: RFC822 escaping with \ is *not* handled. Note 2: it is
- * important that this routine not stop on \r, since we use \r as
- * a marker for RFC822 continuations below.
- */
-
- if (hdr)
- {
- hp = hdr;
- state = 0;
- }
-
- for (; *hp; hp++)
- {
- switch (state)
- {
- case 0: /* before header colon */
- if (*hp == '\n')
- return(NULL);
- else if (*hp == ':')
- {
- state = 1;
- tp = address;
- }
- break;
-
- case 1: /* we've seen the colon, now grab the address */
- if (*hp == '\n') /* end of address list */
- {
- *tp++ = '\0';
- state = 6;
- return(address);
- }
- else if (*hp == ',') /* end of address */
- {
- *tp++ = '\0';
- return(address);
- }
- else if (*hp == '"') /* quoted string */
- {
- state = 2;
- *tp++ = *hp;
- }
- else if (*hp == '(') /* address comment -- ignore */
- {
- parendepth = 1;
- state = 3;
- }
- else if (*hp == '<') /* begin <address> */
- {
- state = 4;
- tp = address;
- }
- else if (isspace(*hp)) /* ignore space */
- state = 1;
- else /* just take it */
- {
- state = 1;
- *tp++ = *hp;
- }
- break;
-
- case 2: /* we're in a quoted string, copy verbatim */
- if (*hp == '\n')
- return(NULL);
- if (*hp != '"')
- *tp++ = *hp;
- else if (*hp == '"')
- {
- *tp++ = *hp;
- state = 1;
- }
- break;
-
- case 3: /* we're in a parenthesized comment, ignore */
- if (*hp == '\n')
- return(NULL);
- else if (*hp == '(')
- ++parendepth;
- else if (*hp == ')')
- --parendepth;
- if (parendepth == 0)
- state = 1;
- break;
-
- case 4: /* possible <>-enclosed address */
- if (*hp == '>') /* end of address */
- {
- *tp++ = '\0';
- state = 1;
- return(address);
- }
- else if (*hp == '<') /* nested <> */
- tp = address;
- else if (*hp == '"') /* quoted address */
- {
- *tp++ = *hp;
- state = 5;
- }
- else /* just copy address */
- *tp++ = *hp;
- break;
-
- case 5: /* we're in a quoted address, copy verbatim */
- if (*hp == '\n') /* mismatched quotes */
- return(NULL);
- if (*hp != '"') /* just copy it if it isn't a quote */
- *tp++ = *hp;
- else if (*hp == '"') /* end of quoted string */
- {
- *tp++ = *hp;
- state = 4;
- }
- break;
-
- case 6:
- return(NULL);
- break;
- }
- }
-
- return(NULL);
-}
-
-#ifdef HAVE_GETHOSTBYNAME
+#ifdef HAVE_RES_SEARCH
#define MX_RETRIES 3
-static int is_host_alias(name, ctl)
+static int is_host_alias(const char *name, struct query *ctl)
/* determine whether name is a DNS alias of the hostname */
-const char *name;
-struct query *ctl;
{
struct hostent *he;
- int i;
+ struct mxentry *mxp, *mxrecords;
/*
* The first two checks are optimizations that will catch a good
- * many cases. First, check against the hostname the user specified.
- * Odds are good this will either be the mailserver's FQDN or a
- * suffix of it with the mailserver's domain's default host name
- * omitted. Next, check against the mailserver's FQDN, in case
+ * many cases. (1) check against the hostname the user
+ * specified. Odds are good this will either be the mailserver's
+ * FQDN or a suffix of it with the mailserver's domain's default
+ * host name omitted. Then check the rest of the `also known as'
+ * cache accumulated by previous DNS checks. This cache is primed
+ * by the aka list option.
+ *
+ * (2) check against the mailserver's FQDN, in case
* it's not the same as the declared hostname.
*
* Either of these on a mail address is definitive. Only if the
* name doesn't match either is it time to call the bind library.
* If this happens odds are good we're looking at an MX name.
*/
- if (strcmp(name, ctl->servername) == 0)
+ if (str_in_list(&ctl->lead_server->servernames, name))
return(TRUE);
else if (strcmp(name, ctl->canonical_name) == 0)
return(TRUE);
/*
- * We treat DNS lookup failure as a negative on the theory that
- * the mailserver's DNS server is `nearby' and should be able
- * to respond quickly and reliably. Ergo if we get failure,
- * the name isn't a mailserver alias.
+ * 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
+ * mailbox until it's back up.
*/
- else if ((he = gethostbyname(name)) && strcmp(ctl->canonical_name, he->h_name) == 0)
- return(TRUE);
+ else if ((he = gethostbyname(name)) != (struct hostent *)NULL)
+ {
+ if (strcmp(ctl->canonical_name, he->h_name) == 0)
+ goto match;
+ else
+ return(FALSE);
+ }
+ else
+ switch (h_errno)
+ {
+ case HOST_NOT_FOUND: /* specified host is unknown */
+ case NO_ADDRESS: /* valid, but does not have an IP address */
+ break;
+
+ case NO_RECOVERY: /* non-recoverable name server error */
+ case TRY_AGAIN: /* temporary error on authoritative server */
+ default:
+ if (outlevel != O_SILENT)
+ putchar('\n'); /* terminate the progress message */
+ error(0, 0,
+ "nameserver failure while looking for `%s' during poll of %s.",
+ name, ctl->servernames->id);
+ ctl->errcount++;
+ longjmp(restart, 2); /* try again next poll cycle */
+ break;
+ }
/*
- * Search for a name match on MX records pointing to the server
- * site. These may live far away, so allow a couple of retries.
+ * We're only here if DNS was OK but the gethostbyname() failed
+ * with a HOST_NOT_FOUND or NO_ADDRESS error.
+ * Search for a name match on MX records pointing to the server.
*/
- for (i = 0; i < MX_RETRIES; i++)
+ h_errno = 0;
+ if ((mxrecords = getmxrecords(name)) == (struct mxentry *)NULL)
{
- struct mxentry *mxrecords, *mxp;
+ switch (h_errno)
+ {
+ case HOST_NOT_FOUND: /* specified host is unknown */
+ return(FALSE);
+
+ case NO_ADDRESS: /* valid, but does not have an IP address */
+ for (mxp = mxrecords; mxp->name; mxp++)
+ if (strcmp(name, mxp->name) == 0)
+ goto match;
+ return(FALSE);
+ break;
- mxrecords = getmxrecords(name);
+ case NO_RECOVERY: /* non-recoverable name server error */
+ case TRY_AGAIN: /* temporary error on authoritative server */
+ default:
+ error(0, 0,
+ "nameserver failure while looking for `%s' during poll of %s.",
+ name, ctl->servernames->id);
+ ctl->errcount++;
+ longjmp(restart, 2); /* try again next poll cycle */
+ break;
+ }
+ }
- if (mxrecords == (struct mxentry *)NULL)
- if (h_errno == TRY_AGAIN)
- {
- sleep(1);
- continue;
- }
- else
- break;
+match:
+ /* add this name to relevant server's `also known as' list */
+ save_str(&ctl->lead_server->servernames, -1, name);
+ return(TRUE);
+}
- for (mxp = mxrecords; mxp->name; mxp++)
- if (strcmp(name, mxp->name) == 0)
- return(TRUE);
- }
+static void map_name(name, ctl, xmit_names)
+/* add given name to xmit_names if it matches declared localnames */
+const char *name; /* name to map */
+struct query *ctl; /* list of permissible aliases */
+struct idlist **xmit_names; /* list of recipient names parsed out */
+{
+ const char *lname;
- return(FALSE);
+ lname = idpair_find(&ctl->localnames, name);
+ if (!lname && ctl->wildcard)
+ lname = name;
+
+ if (lname != (char *)NULL)
+ {
+ if (outlevel == O_VERBOSE)
+ error(0, 0, "mapped %s to local %s", name, lname);
+ save_str(xmit_names, -1, lname);
+ }
}
void find_server_names(hdr, ctl, xmit_names)
/* parse names out of a RFC822 header into an ID list */
const char *hdr; /* RFC822 header in question */
-struct query *ctl; /* list of permissible aliases */
+struct query *ctl; /* list of permissible aliases */
struct idlist **xmit_names; /* list of recipient names parsed out */
{
if (hdr == (char *)NULL)
atsign[0] = '\0';
}
- lname = idpair_find(&ctl->localnames, cp);
- if (lname != (char *)NULL)
- {
- if (outlevel == O_VERBOSE)
- fprintf(stderr,
- "fetchmail: mapped %s to local %s\n",
- cp, lname);
- save_uid(xmit_names, -1, lname);
- }
+ map_name(cp, ctl, xmit_names);
} while
((cp = nxtaddr((char *)NULL)) != (char *)NULL);
}
}
-#endif /* HAVE_GETHOSTBYNAME */
+#endif /* HAVE_RES_SEARCH */
-static FILE *smtp_open(ctl)
+static FILE *smtp_open(struct query *ctl)
/* try to open a socket to the appropriate SMTP server for this query */
-struct query *ctl;
{
- ctl = ctl->leader; /* go to the SMTP leader for this query */
+ ctl = ctl->lead_smtp; /* go to the SMTP leader for this query */
+
+ /* maybe it's time to close the socket in order to force delivery */
+ if (batchlimit && ctl->smtp_sockfp && batchcount++ == batchlimit)
+ {
+ fclose(ctl->smtp_sockfp);
+ ctl->smtp_sockfp = (FILE *)NULL;
+ batchcount = 0;
+ }
/* if no socket to this host is already set up, try to open one */
if (ctl->smtp_sockfp == (FILE *)NULL)
{
- if ((ctl->smtp_sockfp = Socket(ctl->smtphost, SMTP_PORT)) == (FILE *)NULL)
+ if ((ctl->smtp_sockfp = sockopen(ctl->smtphost, SMTP_PORT)) == (FILE *)NULL)
return((FILE *)NULL);
- else if (SMTP_ok(ctl->smtp_sockfp, NULL) != SM_OK
- || SMTP_helo(ctl->smtp_sockfp, ctl->servername) != SM_OK)
+ else if (SMTP_ok(ctl->smtp_sockfp) != SM_OK
+ || SMTP_helo(ctl->smtp_sockfp, ctl->servernames->id) != SM_OK)
{
fclose(ctl->smtp_sockfp);
ctl->smtp_sockfp = (FILE *)NULL;
return(ctl->smtp_sockfp);
}
+static int SockGets(char *buf, int len, FILE *sockfp)
+/* get a LF-terminated line, removing \r characters */
+{
+ int rdlen = 0;
+
+ while (--len)
+ {
+ if ((*buf = fgetc(sockfp)) == EOF)
+ return -1;
+ else
+ rdlen++;
+ if (*buf == '\n')
+ break;
+ if (*buf != '\r') /* remove all CRs */
+ buf++;
+ }
+ *buf = 0;
+ return rdlen;
+}
+
static int gen_readmsg (sockfp, len, delimited, ctl)
/* read message content and ship to SMTP or MDA */
-FILE *sockfp; /* to which the server is connected */
-long len; /* length of message */
-int delimited; /* does the protocol use a message delimiter? */
+FILE *sockfp; /* to which the server is connected */
+long len; /* length of message */
+int delimited; /* does the protocol use a message delimiter? */
struct query *ctl; /* query control record */
{
char buf [MSGBUFSIZE+1];
- char *bufp, *headers, *fromhdr, *tohdr, *cchdr, *bcchdr;
- int n, oldlen, mboxfd;
+ char *bufp, *headers, *fromhdr,*tohdr,*cchdr,*bcchdr,*received_for,*envto;
+ int n, oldlen;
int inheaders,lines,sizeticker;
FILE *sinkfp;
+ RETSIGTYPE (*sigchld)();
+#ifdef HAVE_GETHOSTBYNAME
+ char rbuf[HOSTLEN + USERNAMELEN + 4];
+#endif /* HAVE_GETHOSTBYNAME */
/* read the message content from the server */
inheaders = 1;
- headers = fromhdr = tohdr = cchdr = bcchdr = NULL;
+ headers = fromhdr = tohdr = cchdr = bcchdr = received_for = envto = NULL;
lines = 0;
sizeticker = 0;
oldlen = 0;
if (inheaders)
{
if (!ctl->norewrite)
- reply_hack(bufp, ctl->servername);
+ reply_hack(bufp, ctl->servernames->id);
if (!lines)
{
oldlen = strlen(bufp);
- headers = malloc(oldlen + 1);
- if (headers == NULL)
- return(PS_IOERR);
+ headers = xmalloc(oldlen + 1);
(void) strcpy(headers, bufp);
bufp = headers;
}
if (!strncasecmp("From:", bufp, 5))
fromhdr = bufp;
+ else if (!fromhdr && !strncasecmp("Resent-From:", bufp, 12))
+ fromhdr = bufp;
+ else if (!fromhdr && !strncasecmp("Apparently-From:", bufp, 16))
+ fromhdr = bufp;
else if (!strncasecmp("To:", bufp, 3))
tohdr = bufp;
+ else if (!strncasecmp("Apparently-To:", bufp, 14))
+ envto = bufp;
+ else if (!strncasecmp(ctl->envelope, bufp, 14))
+ envto = bufp;
else if (!strncasecmp("Cc:", bufp, 3))
cchdr = bufp;
else if (!strncasecmp("Bcc:", bufp, 4))
bcchdr = bufp;
+#ifdef HAVE_GETHOSTBYNAME
+ else if (MULTIDROP(ctl) && !strncasecmp("Received:", bufp, 9))
+ {
+ char *ok;
+
+ /*
+ * Try to extract the real envelope addressee. We look here
+ * specifically for the mailserver's Received line.
+ * Note: this will only work for sendmail, or an MTA that
+ * shares sendmail's convention of embedding the envelope
+ * address in the Received line.
+ */
+ strcpy(rbuf, "by ");
+ strcat(rbuf, ctl->canonical_name);
+ if ((ok = strstr(bufp, rbuf)))
+ ok = strstr(ok, "for <");
+ 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 != 0)
+ {
+ received_for = alloca(strlen(rbuf)+1);
+ strcpy(received_for, rbuf);
+ if (outlevel == O_VERBOSE)
+ error(0, 0,
+ "found Received address `%s'",
+ received_for);
+ }
+ }
+#endif /* HAVE_GETHOSTBYNAME */
goto skipwrite;
}
{
char *cp;
struct idlist *idp, *xmit_names;
+ int good_addresses, bad_addresses;
+#ifdef HAVE_RES_SEARCH
+ int no_local_matches = FALSE;
+#endif /* HAVE_RES_SEARCH */
/* cons up a list of local recipients */
xmit_names = (struct idlist *)NULL;
-#ifdef HAVE_GETHOSTBYNAME
+ bad_addresses = good_addresses = 0;
+#ifdef HAVE_RES_SEARCH
/* is this a multidrop box? */
if (MULTIDROP(ctl))
{
- /* compute the local address list */
- find_server_names(tohdr, ctl, &xmit_names);
- find_server_names(cchdr, ctl, &xmit_names);
- find_server_names(bcchdr, ctl, &xmit_names);
+ if (envto) /* We have the actual envelope addressee */
+ find_server_names(envto, ctl, &xmit_names);
+ else if (received_for)
+ /*
+ * We have the Received for addressee.
+ * It has to be a mailserver address, or we
+ * wouldn't have got here.
+ */
+ map_name(received_for, ctl, &xmit_names);
+ else
+ {
+ /*
+ * We haven't extracted the envelope address.
+ * So check all the header addresses.
+ */
+ find_server_names(tohdr, ctl, &xmit_names);
+ find_server_names(cchdr, ctl, &xmit_names);
+ find_server_names(bcchdr, ctl, &xmit_names);
+ }
+ if (!xmit_names)
+ {
+ no_local_matches = TRUE;
+ save_str(&xmit_names, -1, user);
+ if (outlevel == O_VERBOSE)
+ error(0, 0,
+ "no local matches, forwarding to %s",
+ user);
+ }
}
else /* it's a single-drop box, use first localname */
-#endif /* HAVE_GETHOSTBYNAME */
- {
- if (ctl->localnames)
- save_uid(&xmit_names, -1, ctl->localnames->id);
- }
-
- /* if nothing supplied localnames, default appropriately */
- if (!xmit_names)
- if (getuid() == 0)
- save_uid(&xmit_names, -1, ctl->remotename);
- else
- save_uid(&xmit_names, -1, user);
+#endif /* HAVE_RES_SEARCH */
+ save_str(&xmit_names, -1, ctl->localnames->id);
/* time to address the message */
if (ctl->mda[0]) /* we have a declared MDA */
{
- int i, nlocals = 0;
- char **sargv, **sp;
+ int length = 0;
+ char *names, *cmd;
/*
* We go through this in order to be able to handle very
- * long lists of users and (re
-)implement %s.
+ * long lists of users and (re)implement %s.
*/
for (idp = xmit_names; idp; idp = idp->next)
- nlocals++;
- sp = sargv = (char **)alloca(sizeof(char **) * ctl->mda_argcount+nlocals+2);
- for (i = 0; i < ctl->mda_argcount; i++)
- if (strcmp("%s", ctl->mda_argv[i]))
- *sp++ = ctl->mda_argv[i];
- else
- for (idp = xmit_names; idp; idp = idp->next)
- *sp++ = idp->id;
- *sp = (char *)NULL;
+ length += (strlen(idp->id) + 1);
+ names = (char *)alloca(length);
+ names[0] = '\0';
+ for (idp = xmit_names; idp; idp = idp->next)
+ {
+ strcat(names, idp->id);
+ strcat(names, " ");
+ }
+ cmd = (char *)alloca(strlen(ctl->mda) + length);
+ sprintf(cmd, ctl->mda, names);
+ if (outlevel == O_VERBOSE)
+ error(0, 0, "about to deliver with: %s", cmd);
#ifdef HAVE_SETEUID
/*
seteuid(ctl->uid);
#endif /* HAVE_SETEUID */
- mboxfd = openmailpipe(sargv);
+ sinkfp = popen(cmd, "w");
#ifdef HAVE_SETEUID
/* this will fail quietly if we didn't start as root */
seteuid(0);
#endif /* HAVE_SETEUID */
- if (mboxfd < 0)
+ if (!sinkfp)
{
- fprintf(stderr, "fetchmail: MDA open failed\n");
+ error(0, 0, "MDA open failed");
return(PS_IOERR);
}
+
+ sigchld = signal(SIGCHLD, SIG_DFL);
}
else
{
- if (ctl->mda[0] == '\0' && ((sinkfp = smtp_open(ctl)) < 0))
+ char *ap;
+
+ /* build a connection to the SMTP listener */
+ if (ctl->mda[0] == '\0' && ((sinkfp = smtp_open(ctl)) == NULL))
{
- free_uid_list(&xmit_names);
- fprintf(stderr, "fetchmail: SMTP connect failed\n");
+ free_str_list(&xmit_names);
+ error(0, 0, "SMTP connect failed");
return(PS_SMTP);
}
- if (SMTP_from(sinkfp, nxtaddr(fromhdr)) != SM_OK)
+ /*
+ * Try to get the SMTP listener to take the header
+ * From address as MAIL FROM (this makes the logging
+ * nicer). If it won't, fall back on the calling-user
+ * ID. This won't affect replies, which use the header
+ * From address anyway.
+ */
+ if (!fromhdr || !(ap = nxtaddr(fromhdr)))
{
- fprintf(stderr, "fetchmail: SMTP listener is confused\n");
- return(PS_SMTP);
+ if (SMTP_from(sinkfp, user) != SM_OK)
+ return(PS_SMTP); /* should never happen */
}
+ else if (SMTP_from(sinkfp, ap) != SM_OK)
+ if (smtp_response == 571)
+ {
+ /*
+ * SMTP listener explicitly refuses to deliver
+ * mail coming from this address, probably due
+ * to an anti-spam domain exclusion. Respect
+ * this.
+ */
+ sinkfp = (FILE *)NULL;
+ goto skiptext;
+ }
+ else if (SMTP_from(sinkfp, user) != SM_OK)
+ return(PS_SMTP); /* should never happen */
+ /* now list the recipient addressees */
for (idp = xmit_names; idp; idp = idp->next)
- if (SMTP_rcpt(sinkfp, idp->id) != SM_OK)
+ if (SMTP_rcpt(sinkfp, idp->id) == SM_OK)
+ good_addresses++;
+ else
{
- fprintf(stderr, "fetchmail: SMTP listener is upset\n");
- return(PS_SMTP);
+ bad_addresses++;
+ idp->val.num = 0;
+ error(0, 0,
+ "SMTP listener doesn't like recipient address `%s'", idp->id);
}
+ if (!good_addresses && SMTP_rcpt(sinkfp, user) != SM_OK)
+ {
+ error(0, 0,
+ "can't even send to calling user!");
+ return(PS_SMTP);
+ }
+ /* tell it we're ready to send data */
SMTP_data(sinkfp);
- if (outlevel == O_VERBOSE)
- fputs("SMTP> ", stderr);
+
+ skiptext:;
}
- free_uid_list(&xmit_names);
/* change continuation markers back to regular newlines */
for (cp = headers; cp < headers + oldlen; cp++)
if (*cp == '\r')
*cp = '\n';
- headers[oldlen++] = '\0';
/* replace all LFs with CR-LF before sending to the SMTP server */
if (!ctl->mda[0])
{
- char *newheaders = malloc(1 + oldlen * 2);
+ char *newheaders = xmalloc(1 + oldlen * 2);
- if (newheaders == NULL)
- return(PS_IOERR);
oldlen = strcrlf(newheaders, headers, oldlen);
free(headers);
headers = newheaders;
}
/* write all the headers */
- if (ctl->mda[0])
- n = write(mboxfd,headers,oldlen);
- else
- n = SockWrite(headers, oldlen, sinkfp);
+ n = 0;
+ if (sinkfp)
+ n = fwrite(headers, 1, oldlen, sinkfp);
if (n < 0)
{
free(headers);
headers = NULL;
- perror("fetchmail: writing RFC822 headers");
+ error(0, errno, "writing RFC822 headers");
+ if (ctl->mda[0])
+ {
+ pclose(sinkfp);
+ signal(SIGCHLD, sigchld);
+ }
return(PS_IOERR);
}
else if (outlevel == O_VERBOSE)
fputs("#", stderr);
free(headers);
headers = NULL;
+
+ /* write error notifications */
+#ifdef HAVE_RES_SEARCH
+ if (no_local_matches || bad_addresses)
+#else
+ if (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)
+ {
+ strcat(errhd, "no recipient addresses matched declared local names");
+ if (bad_addresses)
+ strcat(errhd, "; ");
+ }
+#endif /* HAVE_RES_SEARCH */
+
+ if (bad_addresses)
+ {
+ strcat(errhd, "SMTP listener rejected local recipient addresses: ");
+ errlen = strlen(errhd);
+ for (idp = xmit_names; idp; idp = idp->next)
+ if (!idp->val.num)
+ errlen += strlen(idp->id) + 2;
+
+ errmsg = alloca(errlen+3);
+ (void) strcpy(errmsg, errhd);
+ for (idp = xmit_names; idp; idp = idp->next)
+ if (!idp->val.num)
+ {
+ strcat(errmsg, idp->id);
+ if (idp->next)
+ strcat(errmsg, ", ");
+ }
+ }
+
+ strcat(errmsg, "\n");
+
+ if (sinkfp)
+ fputs(errmsg, sinkfp);
+ }
+
+ free_str_list(&xmit_names);
}
/* SMTP byte-stuffing */
if (*bufp == '.' && ctl->mda[0] == 0)
- SockWrite(".", 1, sinkfp);
+ if (sinkfp)
+ fputs(".", sinkfp);
/* replace all LFs with CR-LF in the line */
if (!ctl->mda[0])
{
- char *newbufp = malloc(1 + strlen(bufp) * 2);
+ char *newbufp = xmalloc(1 + strlen(bufp) * 2);
- if (newbufp == NULL)
- return(PS_IOERR);
strcrlf(newbufp, bufp, strlen(bufp));
bufp = newbufp;
}
/* ship out the text line */
- if (ctl->mda[0])
- n = write(mboxfd,bufp,strlen(bufp));
- else
- n = SockWrite(bufp, strlen(bufp), sinkfp);
+ n = 0;
+ if (sinkfp)
+ n = fwrite(bufp, 1, strlen(bufp), sinkfp);
if (!ctl->mda[0])
free(bufp);
if (n < 0)
{
- perror("fetchmail: writing message text");
+ error(0, errno, "writing message text");
+ if (ctl->mda[0])
+ {
+ pclose(sinkfp);
+ signal(SIGCHLD, sigchld);
+ }
return(PS_IOERR);
}
else if (outlevel == O_VERBOSE)
if (ctl->mda[0])
{
+ int rc;
+
/* close the delivery pipe, we'll reopen before next message */
- if (closemailpipe(mboxfd))
+ rc = pclose(sinkfp);
+ signal(SIGCHLD, sigchld);
+ if (rc)
+ {
+ error(0, 0, "MDA exited abnormally or returned nonzero status");
return(PS_IOERR);
- }
- else
+ }
+ }
+ else if (sinkfp)
{
/* write message terminator */
if (SMTP_eom(sinkfp) != SM_OK)
{
- fputs("fetchmail: SMTP listener refused delivery\n", stderr);
+ error(0, 0, "SMTP listener refused delivery");
return(PS_SMTP);
}
}
kerberos_auth (socket, canonical)
/* authenticate to the server host using Kerberos V4 */
int socket; /* socket to server host */
-char *canonical; /* server name */
+const char *canonical; /* server name */
{
char * host_primary;
KTEXT ticket;
free (ticket);
if (rem != KSUCCESS)
{
- fprintf (stderr, "fetchmail: kerberos error %s\n", (krb_get_err_text (rem)));
+ error(0, 0, "kerberos error %s", (krb_get_err_text (rem)));
return (PS_ERROR);
}
return (0);
struct query *ctl; /* parsed options with merged-in defaults */
const struct method *proto; /* protocol method table */
{
- int ok;
+ int ok, js;
+ char *msg;
void (*sigsave)();
#ifndef KERBEROS_V4
if (ctl->authenticate == A_KERBEROS)
{
- fputs("fetchmail: Kerberos support not linked.\n", stderr);
+ error(0, 0, "Kerberos support not linked.");
return(PS_ERROR);
}
#endif /* KERBEROS_V4 */
{
/* check for unsupported options */
if (ctl->flush) {
- fprintf(stderr,
- "Option --flush is not supported with %s\n",
+ error(0, 0,
+ "Option --flush is not supported with %s",
proto->name);
return(PS_SYNTAX);
}
else if (ctl->fetchall) {
- fprintf(stderr,
- "Option --all is not supported with %s\n",
+ error(0, 0,
+ "Option --all is not supported with %s",
proto->name);
return(PS_SYNTAX);
}
}
if (!proto->getsizes && ctl->limit)
{
- fprintf(stderr,
- "Option --limit is not supported with %s\n",
+ error(0, 0,
+ "Option --limit is not supported with %s",
proto->name);
return(PS_SYNTAX);
}
sigsave = signal(SIGVTALRM, vtalarm_handler);
vtalarm(mytimeout = ctl->timeout);
- if (setjmp(restart) == 1)
- fprintf(stderr,
- "fetchmail: timeout after %d seconds waiting for %s.\n",
- ctl->timeout, ctl->servername);
+ if ((js = setjmp(restart)) == 1)
+ {
+ error(0, 0,
+ "timeout after %d seconds waiting for %s.",
+ ctl->timeout, ctl->servernames->id);
+ ok = PS_ERROR;
+ }
+ else if (js == 2)
+ {
+ /* error message printed at point of longjmp */
+ ok = PS_ERROR;
+ }
else
{
char buf [POPBUFSIZE+1];
FILE *sockfp;
/* open a socket to the mail server */
- if ((sockfp = Socket(ctl->servername,
- ctl->port ? ctl->port : protocol->port)) == (FILE *)NULL)
+ if ((sockfp = sockopen(ctl->servernames->id,
+ ctl->port ? ctl->port : protocol->port)) == NULL)
{
- perror("fetchmail, connecting to host");
+ error(0, errno, "connecting to host");
ok = PS_SOCKET;
goto closeUp;
}
#ifdef KERBEROS_V4
if (ctl->authenticate == A_KERBEROS)
{
- ok = (kerberos_auth (fileno(sockfp), ctl->canonical_name));
- vtalarm(ctl->timeout);
+ ok = kerberos_auth(fileno(sockfp), ctl->canonical_name);
if (ok != 0)
goto cleanUp;
+ vtalarm(ctl->timeout);
}
#endif /* KERBEROS_V4 */
/* accept greeting message from mail server */
ok = (protocol->parse_response)(sockfp, buf);
- vtalarm(ctl->timeout);
if (ok != 0)
goto cleanUp;
+ vtalarm(ctl->timeout);
/* try to get authorized to fetch mail */
shroud = ctl->password;
ok = (protocol->getauth)(sockfp, ctl, buf);
- vtalarm(ctl->timeout);
shroud = (char *)NULL;
if (ok == PS_ERROR)
ok = PS_AUTHFAIL;
if (ok != 0)
goto cleanUp;
+ vtalarm(ctl->timeout);
/* compute number of messages and number of new messages waiting */
- if ((protocol->getrange)(sockfp, ctl, &count, &new) != 0)
+ ok = (protocol->getrange)(sockfp, ctl, &count, &new);
+ if (ok != 0)
goto cleanUp;
vtalarm(ctl->timeout);
/* show user how many messages we downloaded */
if (outlevel > O_SILENT)
if (count == 0)
- fprintf(stderr, "No mail from %s@%s\n",
+ error(0, 0, "No mail from %s@%s",
ctl->remotename,
- ctl->servername);
+ ctl->servernames->id);
else
{
- fprintf(stderr, "%d message%s", count, count > 1 ? "s" : "");
if (new != -1 && (count - new) > 0)
- fprintf(stderr, " (%d seen)", count-new);
- fprintf(stderr,
- " from %s@%s.\n",
- ctl->remotename,
- ctl->servername);
+ error(0, 0, "%d message%s (%d seen) from %s@%s.",
+ count, count > 1 ? "s" : "", count-new,
+ ctl->remotename,
+ ctl->servernames->id);
+ else
+ error(0, 0, "%d message%s from %s@%s.", count, count > 1 ? "s" : "",
+ ctl->remotename,
+ ctl->servernames->id);
}
/* we may need to get sizes in order to check message limits */
{
msgsizes = (int *)alloca(sizeof(int) * count);
- if ((ok = (proto->getsizes)(sockfp, count, msgsizes)) != 0)
- return(PS_ERROR);
+ ok = (proto->getsizes)(sockfp, count, msgsizes);
+ if (ok != 0)
+ goto cleanUp;
+ vtalarm(ctl->timeout);
}
+
if (check_only)
{
if (new == -1 || ctl->fetchall)
new = count;
ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
- goto closeUp;
+ goto cleanUp;
}
else if (count > 0)
{
+ /*
+ * 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.
+ */
+ int force_retrieval = !peek_capable && (ctl->errcount > 0);
+
+ ctl->errcount = 0;
+
/* read, forward, and delete messages */
for (num = 1; num <= count; num++)
{
- int toolarge = msgsizes && msgsizes[num-1]>ctl->limit;
+ int toolarge = msgsizes && (msgsizes[num-1] > ctl->limit);
int fetch_it = ctl->fetchall ||
- (!(protocol->is_old && (protocol->is_old)(sockfp,ctl,num)) && !toolarge);
+ (!toolarge && (force_retrieval || !(protocol->is_old && (protocol->is_old)(sockfp,ctl,num))));
/* we may want to reject this message if it's old */
if (!fetch_it)
else
{
/* request a message */
- (protocol->fetch)(sockfp, num, &len);
+ ok = (protocol->fetch)(sockfp, num, &len);
+ if (ok != 0)
+ goto cleanUp;
vtalarm(ctl->timeout);
if (outlevel > O_SILENT)
len,
protocol->delimited,
ctl);
- vtalarm(ctl->timeout);
if (ok != 0)
goto cleanUp;
+ vtalarm(ctl->timeout);
/* tell the server we got it OK and resynchronize */
if (protocol->trail)
- (protocol->trail)(sockfp, ctl, num);
+ {
+ ok = (protocol->trail)(sockfp, ctl, num);
+ if (ok != 0)
+ goto cleanUp;
+ vtalarm(ctl->timeout);
+ }
}
/*
if (outlevel > O_SILENT)
fprintf(stderr, " flushed\n");
ok = (protocol->delete)(sockfp, ctl, num);
- vtalarm(ctl->timeout);
if (ok != 0)
goto cleanUp;
- delete_uid(&ctl->newsaved, num);
+ vtalarm(ctl->timeout);
+ delete_str(&ctl->newsaved, num);
}
else if (outlevel > O_SILENT)
fprintf(stderr, " not flushed\n");
ok = gen_transact(sockfp, protocol->expunge_cmd);
if (ok != 0)
goto cleanUp;
+ vtalarm(ctl->timeout);
}
ok = gen_transact(sockfp, protocol->exit_cmd);
if (ok == 0)
ok = PS_SUCCESS;
+ vtalarm(0);
fclose(sockfp);
goto closeUp;
}
ok = gen_transact(sockfp, protocol->exit_cmd);
if (ok == 0)
ok = PS_NOMAIL;
+ vtalarm(0);
fclose(sockfp);
goto closeUp;
}
cleanUp:
+ vtalarm(ctl->timeout);
if (ok != 0 && ok != PS_SOCKET)
- {
gen_transact(sockfp, protocol->exit_cmd);
- fclose(sockfp);
- }
+ vtalarm(0);
+ fclose(sockfp);
}
- signal(SIGVTALRM, sigsave);
+ switch (ok)
+ {
+ case PS_SOCKET:
+ msg = "socket";
+ break;
+ case PS_AUTHFAIL:
+ msg = "authorization";
+ break;
+ case PS_SYNTAX:
+ msg = "missing or bad RFC822 header";
+ break;
+ case PS_IOERR:
+ msg = "MDA";
+ break;
+ case PS_ERROR:
+ msg = "client/server synchronization";
+ break;
+ case PS_PROTOCOL:
+ msg = "client/server protocol";
+ break;
+ case PS_SMTP:
+ msg = "SMTP transaction";
+ break;
+ case PS_UNDEFINED:
+ error(0, 0, "undefined");
+ break;
+ }
+ if (ok==PS_SOCKET || ok==PS_AUTHFAIL || ok==PS_SYNTAX || ok==PS_IOERR
+ || ok==PS_ERROR || ok==PS_PROTOCOL || ok==PS_SMTP)
+ error(0, 0, "%s error while talking to %s", msg, ctl->servernames->id);
closeUp:
+ signal(SIGVTALRM, sigsave);
return(ok);
}
va_end(ap);
strcat(buf, "\r\n");
- SockWrite(buf, strlen(buf), sockfp);
+ fputs(buf, sockfp);
if (outlevel == O_VERBOSE)
{
if (shroud && (cp = strstr(buf, shroud)))
memset(cp, '*', strlen(shroud));
- fprintf(stderr,"> %s", buf);
+ buf[strlen(buf)-1] = '\0';
+ error(0, 0, "%s> %s", protocol->name, buf);
}
}
va_end(ap);
strcat(buf, "\r\n");
- SockWrite(buf, strlen(buf), sockfp);
+ fputs(buf, sockfp);
if (outlevel == O_VERBOSE)
{
char *cp;
if (shroud && (cp = strstr(buf, shroud)))
memset(cp, '*', strlen(shroud));
- fprintf(stderr,"> %s", buf);
+ buf[strlen(buf)-1] = '\0';
+ error(0, 0, "%s> %s", protocol->name, buf);
}
/* we presume this does its own response echoing */