#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 */
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);
}
-#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 */
+ fprintf(stderr,
+ "fetchmail: nameserver failure while looking for `%s' during poll of %s.\n",
+ 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++)
- {
- struct mxentry *mxrecords, *mxp;
+ h_errno = 0;
+ if ((mxrecords = getmxrecords(name)) == (struct mxentry *)NULL)
+ 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;
+ break;
+
+ case NO_RECOVERY: /* non-recoverable name server error */
+ case TRY_AGAIN: /* temporary error on authoritative server */
+ default:
+ fprintf(stderr,
+ "fetchmail: nameserver failure while looking for `%s' during poll of %s.\n",
+ name, ctl->servernames->id);
+ ctl->errcount++;
+ longjmp(restart, 2); /* try again next poll cycle */
+ break;
+ }
- mxrecords = getmxrecords(name);
+ return(FALSE);
- 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)
+ fprintf(stderr,
+ "fetchmail: mapped %s to local %s\n",
+ 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;
{
- static int batchcount;
-
- 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)
{
if ((ctl->smtp_sockfp = Socket(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;
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;
+ char *bufp, *headers, *fromhdr,*tohdr,*cchdr,*bcchdr,*received_for,*envto;
int n, oldlen, mboxfd;
int inheaders,lines,sizeticker;
FILE *sinkfp;
+#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)
{
fromhdr = bufp;
else if (!strncasecmp("To:", bufp, 3))
tohdr = bufp;
- else if (!strncasecmp("To:", bufp, 3))
- tohdr = bufp;
+ else if (!strncasecmp("Apparently-To:", bufp, 14))
+ envto = bufp;
+ else if (!strncasecmp("X-Envelope-To:", 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)
+ {
+ 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)
+ {
+ received_for = alloca(strlen(rbuf)+1);
+ strcpy(received_for, rbuf);
+ if (outlevel == O_VERBOSE)
+ fprintf(stderr,
+ "fetchmail: found Received address `%s'\n",
+ 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)
+ fprintf(stderr,
+ "fetchmail: no local matches, forwarding to %s\n",
+ 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 */
/*
* 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++;
{
char *ap;
- if (ctl->mda[0] == '\0' && ((sinkfp = smtp_open(ctl)) < 0))
+ /* build a connection to the SMTP listener */
+ if (ctl->mda[0] == '\0' && ((sinkfp = smtp_open(ctl)) == NULL))
{
- free_uid_list(&xmit_names);
+ free_str_list(&xmit_names);
fprintf(stderr, "fetchmail: SMTP connect failed\n");
return(PS_SMTP);
}
- if (!fromhdr)
- {
- fprintf(stderr, "fetchmail: I see no From header\n");
- return(PS_SMTP);
- }
-
- if (SMTP_from(sinkfp, ap = nxtaddr(fromhdr)) != SM_OK)
- {
- fprintf(stderr, "fetchmail: SMTP listener doesn't like the From address `%s'\n", ap);
- return(PS_SMTP);
- }
+ /*
+ * 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))
+ || SMTP_from(sinkfp, ap) != SM_OK)
+ 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
{
+ bad_addresses++;
+ idp->val.num = 0;
fprintf(stderr,
- "fetchmail: SMTP listener doesn't like the To address `%s'\n", idp->id);
- return(PS_SMTP);
+ "fetchmail: SMTP listener doesn't like recipient address `%s'\n", idp->id);
}
+ if (!good_addresses && SMTP_rcpt(sinkfp, user) != SM_OK)
+ {
+ fprintf(stderr,
+ "fetchmail: can't even send to calling user!\n");
+ return(PS_SMTP);
+ }
+ /* tell it we're ready to send data */
SMTP_data(sinkfp);
if (outlevel == O_VERBOSE)
fputs("SMTP> ", stderr);
}
- free_uid_list(&xmit_names);
/* change continuation markers back to regular newlines */
for (cp = headers; cp < headers + oldlen; cp++)
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 (ctl->mda[0])
+ write(mboxfd, errmsg, strlen(errmsg));
+ else
+ SockWrite(errmsg, strlen(errmsg), sinkfp);
+ }
+
+ free_str_list(&xmit_names);
}
/* SMTP byte-stuffing */
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;
struct query *ctl; /* parsed options with merged-in defaults */
const struct method *proto; /* protocol method table */
{
- int ok;
+ int ok, js;
void (*sigsave)();
#ifndef KERBEROS_V4
sigsave = signal(SIGVTALRM, vtalarm_handler);
vtalarm(mytimeout = ctl->timeout);
- if (setjmp(restart) == 1)
+ if ((js = setjmp(restart)) == 1)
{
fprintf(stderr,
"fetchmail: timeout after %d seconds waiting for %s.\n",
- ctl->timeout, ctl->servername);
+ ctl->timeout, ctl->servernames->id);
+ ok = PS_ERROR;
+ }
+ else if (js == 2)
+ {
+ /* error message printed at point of longjmp */
ok = PS_ERROR;
}
else
FILE *sockfp;
/* open a socket to the mail server */
- if ((sockfp = Socket(ctl->servername,
+ if ((sockfp = Socket(ctl->servernames->id,
ctl->port ? ctl->port : protocol->port)) == NULL)
{
perror("fetchmail, connecting to host");
if (count == 0)
fprintf(stderr, "No mail from %s@%s\n",
ctl->remotename,
- ctl->servername);
+ ctl->servernames->id);
else
{
fprintf(stderr, "%d message%s", count, count > 1 ? "s" : "");
fprintf(stderr,
" from %s@%s.\n",
ctl->remotename,
- ctl->servername);
+ ctl->servernames->id);
}
/* we may need to get sizes in order to check message limits */
return(PS_ERROR);
}
+
if (check_only)
{
if (new == -1 || ctl->fetchall)
}
else if (count > 0)
{
+ /*
+ * What forces this code is that in POP3 you can't fetch a
+ * message without having it marked `seen'.
+ *
+ * The result 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 = (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)
vtalarm(ctl->timeout);
if (ok != 0)
goto cleanUp;
+ delete_str(&ctl->newsaved, num);
}
else if (outlevel > O_SILENT)
- {
- /* nuke it from the unseen-messages list */
- delete_uid(&ctl->newsaved, num);
fprintf(stderr, " not flushed\n");
- }
}
/* remove all messages flagged for deletion */
}
if (ok==PS_SOCKET || ok==PS_AUTHFAIL || ok==PS_SYNTAX || ok==PS_IOERR
|| ok==PS_ERROR || ok==PS_PROTOCOL || ok==PS_SMTP)
- fprintf(stderr, " error while talking to %s\n", ctl->servername);
+ fprintf(stderr, " error while talking to %s\n", ctl->servernames->id);
closeUp:
signal(SIGVTALRM, sigsave);