-/* Copyright 1996 by Eric S. Raymond
+/*
+ * driver.c -- generic driver for mail fetch method protocols
+ *
+ * Copyright 1996 by Eric S. Raymond
* All rights reserved.
* For license terms, see the file COPYING in this directory.
*/
-/***********************************************************************
- module: driver.c
- project: fetchmail
- programmer: Eric S. Raymond
- description: Generic driver for mail fetch method protocols
-
- ***********************************************************************/
-
#include <config.h>
#include <stdio.h>
+#include <setjmp.h>
+#include <ctype.h>
+#if defined(STDC_HEADERS)
#include <stdlib.h>
+#include <string.h>
+#endif
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#if defined(HAVE_STDARG_H)
+#include <stdarg.h>
+#else
#include <varargs.h>
+#endif
#include <sys/time.h>
#include <signal.h>
-#include <string.h>
+
+#ifdef HAVE_GETHOSTBYNAME
+#include <netdb.h>
+#include "mx.h"
+#endif /* HAVE_GETHOSTBYNAME */
#ifdef KERBEROS_V4
#include <krb.h>
#define SMTP_PORT 25 /* standard SMTP service port */
-static struct method *protocol;
-
-static int alarmed; /* a flag to indicate that SIGALRM happened */
-static int mytimeout; /* server-nonresponse timeout for current query */
+static const struct method *protocol;
+static jmp_buf restart;
char tag[TAGLEN];
static int tagnum;
#define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag)
-static char *shroud;
+static char *shroud; /* string to shroud in debug output, if non-NULL */
+static int mytimeout; /* value of nonreponse timeout */
+
+static int strcrlf(dst, src, count)
+/* replace LFs with CR-LF; return length of string with replacements */
+char *dst; /* new string with CR-LFs */
+char *src; /* original string with LFs */
+int count; /* length of src */
+{
+ int len = count;
+
+ while (count--)
+ {
+ if (*src == '\n')
+ {
+ *dst++ = '\r';
+ len++;
+ }
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+ return len;
+}
-/*********************************************************************
- function:
- description: hack message headers so replies will work properly
+static void vtalarm(timeleft)
+/* reset the nonresponse-timeout */
+int timeleft;
+{
+ struct itimerval ntimeout;
- arguments:
- after where to put the hacked header
- before header to hack
- host name of the pop header
+ ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_sec = 0;
+ ntimeout.it_value.tv_sec = timeleft;
+ ntimeout.it_value.tv_usec = 0;
+ setitimer(ITIMER_VIRTUAL, &ntimeout, (struct itimerval *)NULL);
+}
- return value: none.
- calls: none.
- *********************************************************************/
+static void vtalarm_handler (int signal)
+/* handle server-timeout SIGVTALARM signal */
+{
+ longjmp(restart, 1);
+}
static void reply_hack(buf, host)
-/* hack local mail IDs -- code by Eric S. Raymond 20 Jun 1996 */
-char *buf;
-const char *host;
+/* hack message headers so replies will work properly */
+char *buf; /* header to be hacked */
+const char *host; /* server hostname */
{
const char *from;
- int state = 0;
+ int parendepth, state = 0, tokencount = 0;
char mycopy[POPBUFSIZE+1];
if (strncmp("From: ", buf, 6)
case 1: /* we've seen the colon, we're looking for addresses */
if (*from == '"')
- state = 2;
+ state = 3;
else if (*from == '(')
- state = 3;
+ {
+ parendepth = 1;
+ state = 4;
+ }
else if (*from == '<' || isalnum(*from))
- state = 4;
+ state = 5;
+ else if (isspace(*from))
+ state = 2;
break;
- case 2: /* we're in a quoted human name, copy and ignore */
+ 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 3: /* we're in a parenthesized human name, copy and ignore */
- if (*from == ')')
+ 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 4: /* the real work gets done here */
+ 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
*/
/* if the address part contains an @, don't mess with it */
if (*from == '@')
- state = 5;
+ state = 6;
/* If the address token is not properly terminated, ignore it. */
else if (*from == ' ' || *from == '\t')
- state = 1;
+ {
+ 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. Cases \r and \n catch the case
- * of a single ID alone on the line.
+ * comma-separated bare IDs.
*/
- else if (strchr(">,\r\n", *from))
+ 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 5: /* we're in a remote mail ID, no need to append hostname */
+ case 6: /* we're in a remote mail ID, no need to append hostname */
if (*from == '>' || *from == ',' || isspace(*from))
state = 1;
break;
*buf++ = '\0';
}
-/*********************************************************************
- function: nxtaddr
- description: Parse addresses in succession out of a specified RFC822
- header. 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.
- arguments:
- hdr header line to be parsed, NUL to continue in previous hdr
-
- return value: next address, or NUL if there is no next address
- calls: none
- *********************************************************************/
-
static char *nxtaddr(hdr)
-char *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 state;
+ 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)
{
break;
case 1: /* we've seen the colon, now grab the address */
- if ((*hp == '\n') || (*hp == ',')) /* end of address list */
+ if (*hp == '\n') /* end of address list */
+ {
+ *tp++ = '\0';
+ state = 6;
+ return(address);
+ }
+ else if (*hp == ',') /* end of address */
{
*tp++ = '\0';
return(address);
*tp++ = *hp;
}
else if (*hp == '(') /* address comment -- ignore */
+ {
+ parendepth = 1;
state = 3;
+ }
else if (*hp == '<') /* begin <address> */
{
state = 4;
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;
state = 4;
}
break;
+
+ case 6:
+ return(NULL);
+ break;
}
}
return(NULL);
}
-/*********************************************************************
- function: gen_readmsg
- description: Read the message content
-
- as described in RFC 1225.
- arguments:
- socket ... to which the server is connected.
- mboxfd open file descriptor to which the retrieved message will
- be written.
- len length of text
- delimited does the protocol use a message delimiter?
- queryctl host control block
-
- return value: zero if success else PS_* return code.
- calls: SockGets.
- globals: reads outlevel.
- *********************************************************************/
-
-static int gen_readmsg (socket, mboxfd, len, delimited, queryctl)
-int socket;
-int mboxfd;
-long len;
-int delimited;
-struct hostrec *queryctl;
-{
+#ifdef HAVE_GETHOSTBYNAME
+#define MX_RETRIES 3
+
+static int is_host_alias(name, ctl)
+/* determine whether name is a DNS alias of the hostname */
+const char *name;
+struct query *ctl;
+{
+ struct hostent *he;
+ int i;
+
+ /*
+ * 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
+ * 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)
+ 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.
+ */
+ else if ((he = gethostbyname(name)) && strcmp(ctl->canonical_name, he->h_name) == 0)
+ return(TRUE);
+
+ /*
+ * Search for a name match on MX records pointing to the server
+ * site. These may live far away, so allow a couple of retries.
+ */
+ for (i = 0; i < MX_RETRIES; i++)
+ {
+ struct mxentry *mxrecords, *mxp;
+
+ mxrecords = getmxrecords(name);
+
+ if (mxrecords == (struct mxentry *)NULL)
+ if (h_errno == TRY_AGAIN)
+ {
+ sleep(1);
+ continue;
+ }
+ else
+ break;
+
+ for (mxp = mxrecords; mxp->name; mxp++)
+ if (strcmp(name, mxp->name) == 0)
+ return(TRUE);
+ }
+
+ return(FALSE);
+}
+
+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 idlist **xmit_names; /* list of recipient names parsed out */
+{
+ if (hdr == (char *)NULL)
+ return;
+ else
+ {
+ char *cp, *lname;
+
+ if ((cp = nxtaddr(hdr)) != (char *)NULL)
+ do {
+ char *atsign;
+
+ if ((atsign = strchr(cp, '@')))
+ {
+ /*
+ * Address has an @. 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))
+ continue;
+ 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);
+ }
+ } while
+ ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
+ }
+}
+#endif /* HAVE_GETHOSTBYNAME */
+
+static FILE *smtp_open(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 */
+
+ /* 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)
+ return((FILE *)NULL);
+ else if (SMTP_ok(ctl->smtp_sockfp, NULL) != SM_OK
+ || SMTP_helo(ctl->smtp_sockfp, ctl->servername) != SM_OK)
+ {
+ fclose(ctl->smtp_sockfp);
+ ctl->smtp_sockfp = (FILE *)NULL;
+ }
+ }
+
+ return(ctl->smtp_sockfp);
+}
+
+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? */
+struct query *ctl; /* query control record */
+{
char buf [MSGBUFSIZE+1];
- char fromBuf[MSGBUFSIZE+1];
- char *bufp, *headers, *unixfrom, *fromhdr, *tohdr, *cchdr, *bcchdr;
- int n, oldlen;
- int inheaders;
- int lines,sizeticker;
- /* This keeps the retrieved message count for display purposes */
- static int msgnum = 0;
+ char *bufp, *headers, *fromhdr, *tohdr, *cchdr, *bcchdr;
+ int n, oldlen, mboxfd;
+ int inheaders,lines,sizeticker;
+ FILE *sinkfp;
/* read the message content from the server */
inheaders = 1;
- headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
+ headers = fromhdr = tohdr = cchdr = bcchdr = NULL;
lines = 0;
sizeticker = 0;
- while (delimited || len > 0) {
- if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
+ oldlen = 0;
+ while (delimited || len > 0)
+ {
+ if ((n = SockGets(buf,sizeof(buf),sockfp)) < 0)
return(PS_SOCKET);
+ vtalarm(ctl->timeout);
+
+ /* write the message size dots */
+ if (n > 0)
+ {
+ sizeticker += n;
+ while (sizeticker >= SIZETICKER)
+ {
+ if (outlevel > O_SILENT)
+ fputc('.',stderr);
+ sizeticker -= SIZETICKER;
+ }
+ }
len -= n;
bufp = buf;
if (buf[0] == '\0' || buf[0] == '\r' || buf[0] == '\n')
if (inheaders)
{
- if (!queryctl->norewrite)
- reply_hack(bufp, queryctl->servername);
+ if (!ctl->norewrite)
+ reply_hack(bufp, ctl->servername);
if (!lines)
{
* We deal with RFC822 continuation lines here.
* Replace previous '\n' with '\r' so nxtaddr
* and reply_hack will be able to see past it.
- * (We know this safe because SocketGets stripped
+ * (We know this is safe because SocketGets stripped
* out all carriage returns in the read loop above
* and we haven't reintroduced any since then.)
* We'll undo this before writing the header.
oldlen = newlen;
}
- if (!strncmp(bufp,"From ",5))
- unixfrom = bufp;
- else if (!strncasecmp("From: ", bufp, 6))
+ if (!strncasecmp("From:", bufp, 5))
fromhdr = bufp;
- else if (!strncasecmp("To: ", bufp, 4))
+ else if (!strncasecmp("To:", bufp, 3))
tohdr = bufp;
- else if (!strncasecmp("Cc: ", bufp, 4))
+ else if (!strncasecmp("Cc:", bufp, 3))
cchdr = bufp;
- else if (!strncasecmp("Bcc: ", bufp, 5))
+ else if (!strncasecmp("Bcc:", bufp, 4))
bcchdr = bufp;
goto skipwrite;
}
- else if (headers)
+ else if (headers) /* OK, we're at end of headers now */
{
- char *cp;
+ char *cp;
+ struct idlist *idp, *xmit_names;
+
+ /* cons up a list of local recipients */
+ xmit_names = (struct idlist *)NULL;
+#ifdef HAVE_GETHOSTBYNAME
+ /* 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);
+ }
+ 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 (!queryctl->mda[0])
+ /* 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);
+
+ /* time to address the message */
+ if (ctl->mda[0]) /* we have a declared MDA */
{
- if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
- return(PS_SMTP);
-#ifdef SMTP_RESEND
+ int i, nlocals = 0;
+ char **sargv, **sp;
+
/*
- * This is what we'd do if fetchmail were a real MDA
- * a la sendmail -- crack all the destination headers
- * and send to every address we can reach via SMTP.
+ * We go through this in order to be able to handle very
+ * long lists of users.
*/
- if (tohdr && (cp = nxtaddr(tohdr)) != (char *)NULL)
- do {
- if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
- return(PS_SMTP);
- } while
- (cp = nxtaddr(NULL));
- if (cchdr && (cp = nxtaddr(cchdr)) != (char *)NULL)
- do {
- if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
- return(PS_SMTP);
- } while
- (cp = nxtaddr(NULL));
- if (bcchdr && (cp = nxtaddr(bcchdr)) != (char *)NULL)
- do {
- if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
- return(PS_SMTP);
- } while
- (cp = nxtaddr(NULL));
-#else
+ 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++)
+ *sp++ = ctl->mda_argv[i];
+ for (idp = xmit_names; idp; idp = idp->next)
+ *sp++ = idp->id;
+ *sp = (char *)NULL;
+
+#ifdef HAVE_SETEUID
/*
- * Since we're really only fetching mail for one user
- * per host query, we can be simpler
+ * Arrange to run with user's permissions if we're root.
+ * This will initialize the ownership of any files the
+ * MDA creates properly. (The seteuid call is available
+ * under all BSDs and Linux)
*/
- if (SMTP_rcpt(mboxfd, queryctl->localname) == SM_UNRECOVERABLE)
+ seteuid(ctl->uid);
+#endif /* HAVE_SETEUID */
+
+ mboxfd = openmailpipe(sargv);
+
+#ifdef HAVE_SETEUID
+ /* this will fail quietly if we didn't start as root */
+ seteuid(0);
+#endif /* HAVE_SETEUID */
+
+ if (mboxfd < 0)
+ {
+ fprintf(stderr, "fetchmail: MDA open failed\n");
+ return(PS_IOERR);
+ }
+ }
+ else
+ {
+ if (ctl->mda[0] == '\0' && ((sinkfp = smtp_open(ctl)) < 0))
+ {
+ free_uid_list(&xmit_names);
+ fprintf(stderr, "fetchmail: SMTP connect failed\n");
return(PS_SMTP);
-#endif /* SMTP_RESEND */
- SMTP_data(mboxfd);
+ }
+
+ if (SMTP_from(sinkfp, nxtaddr(fromhdr)) != SM_OK)
+ {
+ fprintf(stderr, "fetchmail: SMTP listener is confused\n");
+ return(PS_SMTP);
+ }
+
+ for (idp = xmit_names; idp; idp = idp->next)
+ if (SMTP_rcpt(sinkfp, idp->id) != SM_OK)
+ {
+ fprintf(stderr, "fetchmail: SMTP listener is upset\n");
+ return(PS_SMTP);
+ }
+
+ 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++)
+ 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 (!queryctl->mda[0])
+ if (!ctl->mda[0])
{
char *newheaders = malloc(1 + oldlen * 2);
free(headers);
headers = newheaders;
}
- if (write(mboxfd,headers,oldlen) < 0)
+
+ /* write all the headers */
+ if (ctl->mda[0])
+ n = write(mboxfd,headers,oldlen);
+ else
+ n = SockWrite(headers, oldlen, sinkfp);
+
+ if (n < 0)
{
free(headers);
headers = NULL;
- perror("gen_readmsg: writing RFC822 headers");
+ perror("fetchmail: writing RFC822 headers");
return(PS_IOERR);
}
else if (outlevel == O_VERBOSE)
}
/* SMTP byte-stuffing */
- if (*bufp == '.' && queryctl->mda[0] == 0)
- write(mboxfd, ".", 1);
+ if (*bufp == '.' && ctl->mda[0] == 0)
+ SockWrite(".", 1, sinkfp);
- /* write this line to the file after replacing all LFs with CR-LF */
- if (!queryctl->mda[0])
+ /* replace all LFs with CR-LF in the line */
+ if (!ctl->mda[0])
{
char *newbufp = malloc(1 + strlen(bufp) * 2);
strcrlf(newbufp, bufp, strlen(bufp));
bufp = newbufp;
}
- n = write(mboxfd,bufp,strlen(bufp));
- if (!queryctl->mda[0])
+
+ /* ship out the text line */
+ if (ctl->mda[0])
+ n = write(mboxfd,bufp,strlen(bufp));
+ else
+ n = SockWrite(bufp, strlen(bufp), sinkfp);
+
+ if (!ctl->mda[0])
free(bufp);
if (n < 0)
{
- perror("gen_readmsg: writing message text");
+ perror("fetchmail: writing message text");
return(PS_IOERR);
}
else if (outlevel == O_VERBOSE)
fputc('*', stderr);
skipwrite:;
+ lines++;
+ }
- /* write the message size dots */
- sizeticker += strlen(bufp);
- while (sizeticker >= SIZETICKER)
+ if (ctl->mda[0])
+ {
+ /* close the delivery pipe, we'll reopen before next message */
+ if (closemailpipe(mboxfd))
+ return(PS_IOERR);
+ }
+ else
+ {
+ /* write message terminator */
+ if (SMTP_eom(sinkfp) != SM_OK)
{
- if (outlevel > O_SILENT && outlevel < O_VERBOSE)
- fputc('.',stderr);
- sizeticker -= SIZETICKER;
-
- /* reset timeout so we don't choke on very long messages */
- alarm(queryctl->timeout);
+ fputs("fetchmail: SMTP listener refused delivery\n", stderr);
+ return(PS_SMTP);
}
- lines++;
}
- if (alarmed)
- return (0);
- /* write message terminator */
- if (!queryctl->mda[0])
- if (SMTP_eom(mboxfd) != SM_OK)
- return(PS_SMTP);
return(0);
}
#ifdef KERBEROS_V4
-/*********************************************************************
- function: kerberos_auth
- description: Authenticate to the server host using Kerberos V4
-
- arguments:
- socket socket to server
- servername server name
-
- return value: exit code from the set of PS_.* constants defined in
- fetchmail.h
- calls:
- globals: reads outlevel.
- *********************************************************************/
-
int
-kerberos_auth (socket, servername)
- int socket;
- char *servername;
+kerberos_auth (socket, canonical)
+/* authenticate to the server host using Kerberos V4 */
+int socket; /* socket to server host */
+char *canonical; /* server name */
{
char * host_primary;
KTEXT ticket;
Key_schedule schedule;
int rem;
- /* Get the primary name of the host. */
- {
- struct hostent * hp = (gethostbyname (servername));
- if (hp == 0)
- {
- fprintf (stderr, "fetchmail: server %s unknown: n", servername);
- return (PS_ERROR);
- }
- host_primary = ((char *) (malloc ((strlen (hp -> h_name)) + 1)));
- strcpy (host_primary, (hp -> h_name));
- }
-
ticket = ((KTEXT) (malloc (sizeof (KTEXT_ST))));
- rem
- = (krb_sendauth (0L, socket, ticket, "pop",
- host_primary,
- ((char *) (krb_realmofhost (host_primary))),
+ rem = (krb_sendauth (0L, socket, ticket, "pop",
+ canonical,
+ ((char *) (krb_realmofhost (canonical))),
((unsigned long) 0),
(&msg_data),
(&cred),
((struct sockaddr_in *) 0),
"KPOPV0.1"));
free (ticket);
- free (host_primary);
if (rem != KSUCCESS)
{
fprintf (stderr, "fetchmail: kerberos error %s\n", (krb_get_err_text (rem)));
}
#endif /* KERBEROS_V4 */
-/*********************************************************************
- function: do_protocol
- description: retrieve messages from the specified mail server
- using a given set of methods
-
- arguments:
- queryctl fully-specified options (i.e. parsed, defaults invoked,
- etc).
- proto protocol method pointer
-
- return value: exit code from the set of PS_.* constants defined in
- fetchmail.h
- calls:
- globals: reads outlevel.
- *********************************************************************/
-
-int do_protocol(queryctl, proto)
-struct hostrec *queryctl;
-struct method *proto;
+int do_protocol(ctl, proto)
+/* retrieve messages from server using given protocol method table */
+struct query *ctl; /* parsed options with merged-in defaults */
+const struct method *proto; /* protocol method table */
{
- int ok, len;
- int mboxfd = -1;
- char buf [POPBUFSIZE+1], host[HOSTLEN+1];
- int socket;
+ int ok;
void (*sigsave)();
- int num, count, deletions = 0;
-
- alarmed = 0;
- sigsave = signal(SIGALRM, alarm_handler);
- alarm (mytimeout = queryctl->timeout);
#ifndef KERBEROS_V4
- if (queryctl->authenticate == A_KERBEROS)
+ if (ctl->authenticate == A_KERBEROS)
{
fputs("fetchmail: Kerberos support not linked.\n", stderr);
return(PS_ERROR);
if (!proto->is_old)
{
/* check for unsupported options */
- if (queryctl->flush) {
+ if (ctl->flush) {
fprintf(stderr,
"Option --flush is not supported with %s\n",
proto->name);
- alarm(0);
- signal(SIGALRM, sigsave);
return(PS_SYNTAX);
}
- else if (queryctl->fetchall) {
+ else if (ctl->fetchall) {
fprintf(stderr,
"Option --all is not supported with %s\n",
proto->name);
- alarm(0);
- signal(SIGALRM, sigsave);
return(PS_SYNTAX);
}
}
+ if (!proto->getsizes && ctl->limit)
+ {
+ fprintf(stderr,
+ "Option --limit is not supported with %s\n",
+ proto->name);
+ return(PS_SYNTAX);
+ }
+ protocol = proto;
tagnum = 0;
tag[0] = '\0'; /* nuke any tag hanging out from previous query */
- protocol = proto;
+ ok = 0;
- /* open a socket to the mail server */
- if ((socket = Socket(queryctl->servername,
- queryctl->port ? queryctl->port : protocol->port))<0
- || alarmed)
+ /* set up the server-nonresponse timeout */
+ 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);
+ else
{
- perror("fetchmail, connecting to host");
- ok = PS_SOCKET;
- goto closeUp;
- }
+ char buf [POPBUFSIZE+1];
+ int *msgsizes, len, num, count, new, deletions = 0;
+ FILE *sockfp;
+
+ /* open a socket to the mail server */
+ if ((sockfp = Socket(ctl->servername,
+ ctl->port ? ctl->port : protocol->port))<0)
+ {
+ perror("fetchmail, connecting to host");
+ ok = PS_SOCKET;
+ goto closeUp;
+ }
#ifdef KERBEROS_V4
- if (queryctl->authenticate == A_KERBEROS)
- {
- ok = (kerberos_auth (socket, queryctl->servername));
+ if (ctl->authenticate == A_KERBEROS)
+ {
+ ok = (kerberos_auth (fileno(sockfp), ctl->canonical_name));
+ vtalarm(ctl->timeout);
+ if (ok != 0)
+ goto cleanUp;
+ }
+#endif /* KERBEROS_V4 */
+
+ /* accept greeting message from mail server */
+ ok = (protocol->parse_response)(sockfp, buf);
+ vtalarm(ctl->timeout);
if (ok != 0)
goto cleanUp;
- }
-#endif /* KERBEROS_V4 */
- /* accept greeting message from mail server */
- ok = (protocol->parse_response)(socket, buf);
- if (alarmed || ok != 0)
- goto cleanUp;
-
- /* try to get authorized to fetch mail */
- shroud = queryctl->password;
- ok = (protocol->getauth)(socket, queryctl, buf);
- shroud = (char *)NULL;
- if (alarmed || ok == PS_ERROR)
- ok = PS_AUTHFAIL;
- if (alarmed || ok != 0)
- goto cleanUp;
-
- /* compute count, and get UID list if possible */
- if ((protocol->getrange)(socket, queryctl, &count) != 0 || alarmed)
- goto cleanUp;
-
- /* show user how many messages we downloaded */
- if (outlevel > O_SILENT && outlevel < O_VERBOSE)
- if (count == 0)
- fprintf(stderr, "No mail for %s from %s@%s\n",
- queryctl->remotename,
- queryctl->localname,
- queryctl->servername);
- else
- fprintf(stderr,
- "%d message%s from %s for %s@%s.\n",
- count, count > 1 ? "s" : "",
- queryctl->remotename,
- queryctl->localname,
- queryctl->servername);
+ /* 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;
- if ((count > 0) && (!check_only))
- {
- if (queryctl->mda[0] == '\0')
- if ((mboxfd = Socket(queryctl->smtphost, SMTP_PORT)) < 0
- || SMTP_ok(mboxfd, NULL) != SM_OK
- || SMTP_helo(mboxfd, queryctl->servername) != SM_OK
- || alarmed)
+ /* compute number of messages and number of new messages waiting */
+ if ((protocol->getrange)(sockfp, ctl, &count, &new) != 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",
+ ctl->remotename,
+ ctl->servername);
+ else
{
- ok = PS_SMTP;
- close(mboxfd);
- mboxfd = -1;
- goto cleanUp;
+ 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);
}
-
- /* read, forward, and delete messages */
- for (num = 1; num <= count; num++)
+
+ /* we may need to get sizes in order to check message limits */
+ msgsizes = (int *)NULL;
+ if (!ctl->fetchall && proto->getsizes && ctl->limit)
{
- int treat_as_new =
- !protocol->is_old
- || !(protocol->is_old)(socket, queryctl, num);
+ msgsizes = (int *)alloca(sizeof(int) * count);
- /* we may want to reject this message if it's old */
- if (treat_as_new || queryctl->fetchall)
- {
- int saveduid = getuid();
+ if ((ok = (proto->getsizes)(sockfp, count, msgsizes)) != 0)
+ return(PS_ERROR);
+ }
- /* request a message */
- (protocol->fetch)(socket, num, &len);
+ if (check_only)
+ {
+ if (new == -1 || ctl->fetchall)
+ new = count;
+ ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
+ goto closeUp;
+ }
+ else if (count > 0)
+ {
+ /* read, forward, and delete messages */
+ for (num = 1; num <= count; num++)
+ {
+ int toolarge = msgsizes && msgsizes[num-1]>ctl->limit;
+ int fetch_it = ctl->fetchall ||
+ (!(protocol->is_old && (protocol->is_old)(sockfp,ctl,num)) && !toolarge);
- if (outlevel > O_SILENT)
+ /* we may want to reject this message if it's old */
+ if (!fetch_it)
{
- fprintf(stderr, "reading message %d", num);
- if (len > 0)
- fprintf(stderr, " (%d bytes)", len);
- if (outlevel == O_VERBOSE)
- fputc('\n', stderr);
- else
- fputc(' ', stderr);
+ if (outlevel > O_SILENT)
+ {
+ fprintf(stderr, "skipping message %d", num);
+ if (toolarge)
+ fprintf(stderr, " (oversized, %d bytes)", msgsizes[num-1]);
+ }
}
-
- /* open the delivery pipe now if we're using an MDA */
- if (queryctl->mda[0])
+ else
{
- /*
- * In case we're running as root, change our UID to
- * that of the local user being delivered to, so the MDA
- * will be able to modify that user's files. This
- * code will fail quietly when run by non-root.
- */
- (void) setuid(queryctl->uid);
+ /* request a message */
+ (protocol->fetch)(sockfp, num, &len);
+ vtalarm(ctl->timeout);
- if ((mboxfd = openmailpipe(queryctl)) < 0)
+ if (outlevel > O_SILENT)
{
- (void) setuid(saveduid); /* see below */
- goto cleanUp;
+ fprintf(stderr, "reading message %d", num);
+ if (len > 0)
+ fprintf(stderr, " (%d bytes)", len);
+ if (outlevel == O_VERBOSE)
+ fputc('\n', stderr);
+ else
+ fputc(' ', stderr);
}
+
+ /* read the message and ship it to the output sink */
+ ok = gen_readmsg(sockfp,
+ len,
+ protocol->delimited,
+ ctl);
+ vtalarm(ctl->timeout);
+ if (ok != 0)
+ goto cleanUp;
+
+ /* tell the server we got it OK and resynchronize */
+ if (protocol->trail)
+ (protocol->trail)(sockfp, ctl, num);
}
- /* read the message and ship it to the output sink */
- ok = gen_readmsg(socket, mboxfd,
- len,
- protocol->delimited,
- queryctl);
+ /*
+ * 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.
+ */
- /* close the delivery pipe, we'll reopen before next message */
- if (queryctl->mda[0])
+ /* maybe we delete this message now? */
+ if (protocol->delete
+ && (fetch_it ? !ctl->keep : ctl->flush))
{
- if ((ok = closemailpipe(mboxfd)) != 0 || alarmed)
+ deletions++;
+ if (outlevel > O_SILENT)
+ fprintf(stderr, " flushed\n");
+ ok = (protocol->delete)(sockfp, ctl, num);
+ vtalarm(ctl->timeout);
+ if (ok != 0)
goto cleanUp;
-
- /*
- * Now try to reset our UID to root. This code will fail
- * quietly when run by non-root.
- */
- (void) setuid(saveduid);
}
-
- /* tell the server we got it OK and resynchronize */
- if (protocol->trail)
- (protocol->trail)(socket, queryctl, num);
- if (alarmed || ok != 0)
- goto cleanUp;
+ else if (outlevel > O_SILENT)
+ {
+ /* nuke it from the unseen-messages list */
+ delete_uid(&ctl->newsaved, num);
+ fprintf(stderr, " not flushed\n");
+ }
}
- /*
- * 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.
- */
-
- /* maybe we delete this message now? */
- if (protocol->delete
- && !queryctl->keep
- && (treat_as_new || queryctl->flush))
+ /* remove all messages flagged for deletion */
+ if (protocol->expunge_cmd && deletions > 0)
{
- deletions++;
- if (outlevel > O_SILENT && outlevel < O_VERBOSE)
- fprintf(stderr, " flushed\n", num);
- ok = (protocol->delete)(socket, queryctl, num);
- if (alarmed || ok != 0)
+ ok = gen_transact(sockfp, protocol->expunge_cmd);
+ if (ok != 0)
goto cleanUp;
}
- else if (outlevel > O_SILENT && outlevel < O_VERBOSE)
- {
- /* nuke it from the unseen-messages list */
- delete_uid(&queryctl->newsaved, num);
- fprintf(stderr, " not flushed\n", num);
- }
+
+ ok = gen_transact(sockfp, protocol->exit_cmd);
+ if (ok == 0)
+ ok = PS_SUCCESS;
+ fclose(sockfp);
+ goto closeUp;
+ }
+ else {
+ ok = gen_transact(sockfp, protocol->exit_cmd);
+ if (ok == 0)
+ ok = PS_NOMAIL;
+ fclose(sockfp);
+ goto closeUp;
}
- /* remove all messages flagged for deletion */
- if (protocol->expunge_cmd && deletions > 0)
+ cleanUp:
+ if (ok != 0 && ok != PS_SOCKET)
{
- ok = gen_transact(socket, protocol->expunge_cmd);
- if (alarmed || ok != 0)
- goto cleanUp;
- }
-
- ok = gen_transact(socket, protocol->exit_cmd);
- if (alarmed || ok == 0)
- ok = PS_SUCCESS;
- close(socket);
- goto closeUp;
- }
- else if (check_only) {
- ok = ((count > 0) ? PS_SUCCESS : PS_NOMAIL);
- goto closeUp;
- }
- else {
- ok = gen_transact(socket, protocol->exit_cmd);
- if (ok == 0)
- ok = PS_NOMAIL;
- close(socket);
- goto closeUp;
+ gen_transact(sockfp, protocol->exit_cmd);
+ fclose(sockfp);
+ }
}
-cleanUp:
- if (ok != 0 && ok != PS_SOCKET)
- {
- gen_transact(socket, protocol->exit_cmd);
- close(socket);
- }
+ signal(SIGVTALRM, sigsave);
closeUp:
- if (mboxfd != -1)
- {
- SMTP_quit(mboxfd);
- close(mboxfd);
- }
- alarm(0);
- signal(SIGALRM, sigsave);
return(ok);
}
-/*********************************************************************
- function: gen_send
- description: Assemble command in print style and send to the server
-
- arguments:
- socket socket to which the server is connected.
- fmt printf-style format
-
- return value: none.
- calls: SockPuts.
- globals: reads outlevel.
- *********************************************************************/
-
-void gen_send(socket, fmt, va_alist)
-int socket;
-const char *fmt;
+#if defined(HAVE_STDARG_H)
+void gen_send(FILE *sockfp, char *fmt, ... )
+/* assemble command in printf(3) style and send to the server */
+{
+#else
+void gen_send(sockfp, fmt, va_alist)
+/* assemble command in printf(3) style and send to the server */
+FILE *sockfp; /* socket to which server is connected */
+const char *fmt; /* printf-style format */
va_dcl {
+#endif
char buf [POPBUFSIZE+1];
va_list ap;
else
buf[0] = '\0';
+#if defined(HAVE_STDARG_H)
+ va_start(ap, fmt) ;
+#else
va_start(ap);
+#endif
vsprintf(buf + strlen(buf), fmt, ap);
va_end(ap);
- SockPuts(socket, buf);
+ strcat(buf, "\r\n");
+ SockWrite(buf, strlen(buf), sockfp);
if (outlevel == O_VERBOSE)
{
if (shroud && (cp = strstr(buf, shroud)))
memset(cp, '*', strlen(shroud));
- fprintf(stderr,"> %s\n", buf);
+ fprintf(stderr,"> %s", buf);
}
}
-/*********************************************************************
- function: gen_transact
- description: Assemble command in print style and send to the server.
- then accept a protocol-dependent response.
-
- arguments:
- socket socket to which the server is connected.
- fmt printf-style format
-
- return value: none.
- calls: SockPuts.
- globals: reads outlevel.
- *********************************************************************/
-
-int gen_transact(socket, fmt, va_alist)
-int socket;
-const char *fmt;
+#if defined(HAVE_STDARG_H)
+int gen_transact(FILE *sockfp, char *fmt, ... )
+/* assemble command in printf(3) style, send to server, accept a response */
+{
+#else
+int gen_transact(sockfp, fmt, va_alist)
+/* assemble command in printf(3) style, send to server, accept a response */
+FILE *sockfp; /* socket to which server is connected */
+const char *fmt; /* printf-style format */
va_dcl {
+#endif
int ok;
char buf [POPBUFSIZE+1];
else
buf[0] = '\0';
+#if defined(HAVE_STDARG_H)
+ va_start(ap, fmt) ;
+#else
va_start(ap);
+#endif
vsprintf(buf + strlen(buf), fmt, ap);
va_end(ap);
- SockPuts(socket, buf);
+ strcat(buf, "\r\n");
+ SockWrite(buf, strlen(buf), sockfp);
if (outlevel == O_VERBOSE)
{
char *cp;
if (shroud && (cp = strstr(buf, shroud)))
memset(cp, '*', strlen(shroud));
- fprintf(stderr,"> %s\n", buf);
+ fprintf(stderr,"> %s", buf);
}
/* we presume this does its own response echoing */
- ok = (protocol->parse_response)(socket, buf);
+ ok = (protocol->parse_response)(sockfp, buf);
+ vtalarm(mytimeout);
return(ok);
}
-/*********************************************************************
- function: strcrlf
- description: replace LFs with CR-LF
-
- arguments:
- dst new string with CR-LFs
- src original string with LFs
- count length of src
-
- return value: length of dst
- calls: none.
- globals: none.
- *********************************************************************/
-
-int strcrlf(dst, src, count)
-char *dst;
-char *src;
-int count;
-{
- int len = count;
-
- while (count--)
- {
- if (*src == '\n')
- {
- *dst++ = '\r';
- len++;
- }
- *dst++ = *src++;
- }
- *dst = '\0';
- return len;
-}
-/*********************************************************************
- function: alarm_handler
- description: In real life process can get stuck waiting for
- something. This deadlock is avoided here by this
- sending SIGALRM
-
- arguments:
- signal hopefully SIGALRM
-
- return value: none.
- calls: none
- globals: sets alarmed to 1
- *********************************************************************/
-void
-alarm_handler (int signal)
-{
- alarmed = 1;
- fprintf(stderr,
- "fetchmail: timeout after %d seconds.\n", mytimeout);
-}
+/* driver.c ends here */