aboutsummaryrefslogtreecommitdiffstats
path: root/design-notes.html
blob: fc4a2c3b25f07892cd9eef085b591e6d4a4d0219 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Updated design notes on fetchmail</title>
<link rev="made" href="mailto:matthias.andree@gmx.de" />
<meta name="description" content="Updated design notes on fetchmail." />
<meta name="keywords" content="fetchmail, POP, POP2, POP3, IMAP, ETRN, ODMR, remote mail" />
<style type="text/css">
/*<![CDATA[*/
 h1.c1 {text-align: center}
/*]]>*/
</style>
</head>
<body>
<table width="100%" cellpadding="0" summary="Canned page header">
<tr>
<td width="30%">Back to <a href="index.html">Fetchmail Home Page</a></td>
<td width="30%" align="right">$Date$</td>
</tr>
</table>

<hr />
<h1 class="c1">Design Notes On Fetchmail</h1>

<h2>Introduction</h2>

<p>This document's contents were last updated in 2006, around fetchmail 6.3.4/6.3.5 time.
It is supposed to complement <a
    href="esrs-design-notes.html">Eric S. Raymond's (ESR's)
    design notes.</a> The new maintainers don't agree with some of the decisions
ESR made previously, and the differences and new directions will be laid
out in this document. It is therefore a sort of a TODO document, until
the necessary code revisions have been made.</p>

<h2>Security</h2>

        <p>
                Fetchmail 6.2.x was handed over in a pretty poor shape, security-wise. It would happily talk to the network with root privileges, used sscanf() to read remotely received data into fixed-length stack-based buffers without length limitation and so on. A full audit is required and security concepts will have to be applied. Random bits are:
        </p>
<ul>
    <li>code talking to the network does not require root privileges and
    needs to run without root permissions</li>
    <li>all input must be validated, all strings must be length checked,
    all integers range checked</li>
    <li>all types will need to be reviewed whether they are signed or
    unsigned</li>
</ul>

<h2>SMTP forwarding</h2>

<p>Fetchmail's multidrop and rewrite options will process addresses
received from remote sites. Special care must be taken so these
features cannot be abused to relay mail to foreign sites.</p>

<p>ESR's attempt to make fetchmail use SMTP exclusively failed,
fetchmail got LMTP and --mda options &ndash; the latter has a lot of
flaws unfortunately, is inconsistent with the SMTP forwarder and needs
to be reviewed and probably bugfixed. --mda doesn't properly work with
multiple recipients, it cannot properly communicate errors and is best
avoided for now.</p>

<h2>Server-side vs. client-side state.</h2>

<h3>Why we need client-side tracking</h3>

<p>ESR asserted that server-side state were essential and those persons
responsible for removing the LAST command from POP3 deserved to
suffer. ESR is right in stating that the POP3 UID tracks which messages
have been read <em>by this client</em> &ndash; and that is exactly what
we need to do.</p>

<p>If fetchmail is supposed to retrieve all mail from a mailbox
reliably, without being disturbed by someone occasionally using another
client on another host, or a webmailer, or similar, then
<em>client</em>-side tracking of the state is indispensable. This is
also needed to match behavior to ETRN and ODMR or to support read-only
mailboxes in --keep mode.</p>

<h3>Present and future</h3>

<p>Fetchmail supports client-side state in POP3 if the UIDL option is
used (which is strongly recommended). Similar effort needs to be made to
track IMAP state by means of UIDVALIDITY and UID.</p>

<p>This will also mean that the UID handling code be revised an perhaps
use one file per account or per folder.</p>

<h2>Concurrent queries/concurrent fetchmail instances</h2>

<p>ESR refused to make fetchmail query multiple hosts or accounts
concurrently, on the grounds that finer-grained locks would be hard to
implement portably.</p>

<p>The idea of using one file per folder or account to track UIDs on the
client-side will make solving this locking problem easy &ndash; the lock can
be placed on the UID file instead.</p>

<h2>Multidrop issues</h2>

<p>Fetchmail tries to guess recipients from headers that are not routing
relevant, for instance, To:, Cc:, or Resent-headers (which are rare
anyways). It is important that fetchmail insists on the real envelope
operation for multidrop. This is detailed in <a
    href="http://home.pages.de/~mandree/mail/multidrop">my
    article &quot;Requisites for working multidrop
    mailboxes&quot;</a>.</p>

<p>As Terry Lambert pointed out in the FreeBSD-arch mailing list on
2001-02-17 under the subject "UUCP must stay; fetchmail sucks",
fetchmail performs DNS MX lookups to determine domains for which
multidrop is valid, on the assumption that the receiving SMTP host
upstream were the same as the IMAP or POP3 server.</p>

<hr />
<table width="100%" cellpadding="0" summary="Canned page footer">
<tr>
<td width="30%">Back to <a href="index.html">Fetchmail Home Page</a></td>
<td width="30%" align="right">$Date$</td>
</tr>
</table>

<br clear="left" />
<address>Matthias Andree <a
	href="mailto:matthias.andree@gmx.de">&lt;matthias.andree@gmx.de&gt;</a></address>
</body>
</html>
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
/*
 * 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.
 */

#include  <config.h>
#include  <stdio.h>
#if defined(STDC_HEADERS)
#include  <stdlib.h>
#include  <string.h>
#endif
#if defined(HAVE_STDARG_H)
#include  <stdarg.h>
#else
#include  <varargs.h>
#endif
#include  <sys/time.h>
#include  <signal.h>

#ifdef HAVE_GETHOSTBYNAME
#include <netdb.h>
#include "mx.h"
#endif /* HAVE_GETHOSTBYNAME */

#ifdef KERBEROS_V4
#include <krb.h>
#include <des.h>
#include <netinet/in.h>		/* must be included before "socket.h".*/
#include <netdb.h>
#endif /* KERBEROS_V4 */
#include  "socket.h"
#include  "fetchmail.h"
#include  "smtp.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 char *srvname;	/* current server name for timeout message */

char tag[TAGLEN];
static int tagnum;
#define GENSYM	(sprintf(tag, "a%04d", ++tagnum), tag)

static char *shroud;

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 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 == '(')
		state = 4;    
	    else if (*from == '<' || isalnum(*from))
		state = 5;
	    else if (isspace(*from))
		state = 2;
	    else if (*from == ',')
		tokencount = 0;
	    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 == ')')
		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);
		state = 1;
	    }

	    /* a single local name alone on the line */
	    else if (*from == '\n' && tokencount == 0)
	    {
		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	state;

    /*
     * 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 */
		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 == ')')
		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
#define MX_RETRIES	3

static int is_host_alias(name, queryctl)
/* determine whether name is a DNS alias of the hostname */
const char *name;
struct hostrec	*queryctl;
{
    struct hostent	*he;
    int			i, n;

    /*
     * 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, queryctl->servername) == 0)
	return(TRUE);
    else if (strcmp(name, queryctl->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(queryctl->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;
	int j;

	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, queryctl, xmit_names)
/* parse names out of a RFC822 header into an ID list */
const char *hdr;		/* RFC822 header in question */
struct hostrec *queryctl;	/* 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 = strchr(cp, '@');

		if (atsign)
		    if (queryctl->norewrite)
			continue;
		    else
		    {
			if (!is_host_alias(atsign+1, queryctl))
			    continue;
			atsign[0] = '\0';
		    }
		lname = idpair_find(&queryctl->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 int gen_readmsg (socket, mboxfd, len, delimited, queryctl)
/* read message content and ship to SMTP or MDA */
int socket;	/* to which the server is connected */
int mboxfd;	/* descriptor to which retrieved message will be written */
long len;	/* length of message */
int delimited;	/* does the protocol use a message delimiter? */
struct hostrec *queryctl;	/* 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;  

    /* read the message content from the server */
    inheaders = 1;
    headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
    lines = 0;
    sizeticker = 0;
    while (delimited || len > 0)
    {
	if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
	    return(PS_SOCKET);

	/* write the message size dots */
	if (n > 0)
	{
	    sizeticker += n;
	    while (sizeticker >= SIZETICKER)
	    {
		if (outlevel > O_SILENT && outlevel < O_VERBOSE)
		    fputc('.',stderr);
		sizeticker -= SIZETICKER;
	    }
	}
	len -= n;
	bufp = buf;
	if (buf[0] == '\0' || buf[0] == '\r' || buf[0] == '\n')
	    inheaders = 0;
	if (delimited && *bufp == '.') {
	    bufp++;
	    if (*bufp == 0)
		break;  /* end of message */
	}
	strcat(bufp, "\n");
     
	if (inheaders)
        {
	    if (!queryctl->norewrite)
		reply_hack(bufp, queryctl->servername);

	    if (!lines)
	    {
		oldlen = strlen(bufp);
		headers = malloc(oldlen + 1);
		if (headers == NULL)
		    return(PS_IOERR);
		(void) strcpy(headers, bufp);
		bufp = headers;
	    }
	    else
	    {
		int	newlen;

		/*
		 * 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
		 * 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.
		 */
		if (isspace(bufp[0]))
		    headers[oldlen-1] = '\r';

		newlen = oldlen + strlen(bufp);
		headers = realloc(headers, newlen + 1);
		if (headers == NULL)
		    return(PS_IOERR);
		strcpy(headers + oldlen, bufp);
		bufp = headers + oldlen;
		oldlen = newlen;
	    }

	    if (!strncmp(bufp,"From ",5))
		unixfrom = bufp;
	    else if (!strncasecmp("From:", bufp, 5))
		fromhdr = bufp;
	    else if (!strncasecmp("To:", bufp, 3))
		tohdr = bufp;
	    else if (!strncasecmp("Cc:", bufp, 3))
		cchdr = bufp;
	    else if (!strncasecmp("Bcc:", bufp, 4))
		bcchdr = bufp;

	    goto skipwrite;
	}
	else if (headers)
	{
	    char		*cp;

	    if (!queryctl->mda[0])
	    {
		if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
		    return(PS_SMTP);

#ifdef HAVE_GETHOSTBYNAME
		/* is this a multidrop box? */
		if (queryctl->localnames != (struct idlist *)NULL
		    && queryctl->localnames->next != (struct idlist *)NULL)
		{
		    struct idlist 	*idp, *xmit_names;

		    /* compute the local address list */
		    xmit_names = (struct idlist *)NULL;
		    find_server_names(tohdr,  queryctl, &xmit_names);
		    find_server_names(cchdr,  queryctl, &xmit_names);
		    find_server_names(bcchdr, queryctl, &xmit_names);

		    /* if nothing supplied localnames, default appropriately */
		    if (!xmit_names)
			save_uid(&xmit_names, -1, dfltuser);

		    for (idp = xmit_names; idp; idp = idp->next)
			if (SMTP_rcpt(mboxfd, idp->id) != SM_OK)
			    return(PS_SMTP);
		    free_uid_list(&xmit_names);
		}
		else	/* it's a single-drop box, use first localname */
#endif /* HAVE_GETHOSTBYNAME */
		{
		    if (queryctl->localnames)
			cp = queryctl->localnames->id;
		    else
			cp = dfltuser;

		    if (SMTP_rcpt(mboxfd, cp) != SM_OK)
			return(PS_SMTP);
		}

		SMTP_data(mboxfd);
		if (outlevel == O_VERBOSE)
		    fputs("SMTP> ", stderr);
	    }

	    /* change continuation markers back to regular newlines */
	    for (cp = headers; cp < headers +  oldlen; cp++)
		if (*cp == '\r')
		    *cp = '\n';

	    /* replace all LFs with CR-LF before sending to the SMTP server */
	    if (!queryctl->mda[0])
	    {
		char *newheaders = malloc(1 + oldlen * 2);

		if (newheaders == NULL)
		    return(PS_IOERR);
		oldlen = strcrlf(newheaders, headers, oldlen);
		free(headers);
		headers = newheaders;
	    }
	    if (write(mboxfd,headers,oldlen) < 0)
	    {
		free(headers);
		headers = NULL;
		perror("gen_readmsg: writing RFC822 headers");
		return(PS_IOERR);
	    }
	    else if (outlevel == O_VERBOSE)
		fputs("#", stderr);
	    free(headers);
	    headers = NULL;
	}

	/* SMTP byte-stuffing */
	if (*bufp == '.' && queryctl->mda[0] == 0)
	    write(mboxfd, ".", 1);

	/* write this line to the file after replacing all LFs with CR-LF */
	if (!queryctl->mda[0])
	{
	    char *newbufp = malloc(1 + strlen(bufp) * 2);

	    if (newbufp == NULL)
		return(PS_IOERR);
	    strcrlf(newbufp, bufp, strlen(bufp));
	    bufp = newbufp;
	}
	n = write(mboxfd,bufp,strlen(bufp));
	if (!queryctl->mda[0])
	    free(bufp);
	if (n < 0)
	{
	    perror("gen_readmsg: writing message text");
	    return(PS_IOERR);
	}
	else if (outlevel == O_VERBOSE)
	    fputc('*', stderr);

    skipwrite:;
	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
int
kerberos_auth (socket, servername) 
/* authenticate to the server host using Kerberos V4 */
int socket;		/* socket to server host */
char *servername;	/* server name */
{
    char * host_primary;
    KTEXT ticket;
    MSG_DAT msg_data;
    CREDENTIALS cred;
    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))),
			 ((unsigned long) 0),
			 (&msg_data),
			 (&cred),
			 (schedule),
			 ((struct sockaddr_in *) 0),
			 ((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)));
	return (PS_ERROR);
    }
    return (0);
}
#endif /* KERBEROS_V4 */

int do_protocol(queryctl, proto)
/* retrieve messages from server using given protocol method table */
struct hostrec *queryctl;	/* parsed options with merged-in defaults */
struct method *proto;		/* protocol method table */
{
    int ok, len;
    int mboxfd = -1;
    char buf [POPBUFSIZE+1], host[HOSTLEN+1];
    int socket;
    void (*sigsave)();
    int num, count, new, deletions = 0;

    srvname = queryctl->servername;
    alarmed = 0;
    sigsave = signal(SIGALRM, alarm_handler);
    alarm (mytimeout = queryctl->timeout);

#ifndef KERBEROS_V4
    if (queryctl->authenticate == A_KERBEROS)
    {
	fputs("fetchmail: Kerberos support not linked.\n", stderr);
	return(PS_ERROR);
    }
#endif /* KERBEROS_V4 */

    /* lacking methods, there are some options that may fail */
    if (!proto->is_old)
    {
	/* check for unsupported options */
	if (queryctl->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) {
	    fprintf(stderr,
		    "Option --all is not supported with %s\n",
		    proto->name);
            alarm(0);
            signal(SIGALRM, sigsave);
	    return(PS_SYNTAX);
	}
    }

    tagnum = 0;
    tag[0] = '\0';	/* nuke any tag hanging out from previous query */
    protocol = proto;

    /* open a socket to the mail server */
    if ((socket = Socket(queryctl->servername,
			 queryctl->port ? queryctl->port : protocol->port))<0 
         || alarmed)
    {
	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 (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 number of messages and number of new messages waiting */
    if ((protocol->getrange)(socket, queryctl, &count, &new) != 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 from %s@%s\n", 
		    queryctl->remotename,
		    queryctl->servername);
	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",
		    queryctl->remotename,
		    queryctl->servername);
	}

    if (check_only)
    {
	if (new == -1 || queryctl->fetchall)
	    new = count;
	ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
	goto closeUp;
    }
    else if (count > 0)
    {
	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)
	    {
		ok = PS_SMTP;
		close(mboxfd);
		mboxfd = -1;
		goto cleanUp;
	    }
    
	/* read, forward, and delete messages */
	for (num = 1; num <= count; num++)
	{
	    int	fetch_it = queryctl->fetchall ||
		!(protocol->is_old && (protocol->is_old)(socket,queryctl,num));

	    /* we may want to reject this message if it's old */
	    if (!fetch_it)
		fprintf(stderr, "skipping message %d ", num);
	    else
	    {
		/* request a message */
		(protocol->fetch)(socket, num, &len);

		if (outlevel > O_SILENT)
		{
		    fprintf(stderr, "reading message %d", num);
		    if (len > 0)
			fprintf(stderr, " (%d bytes)", len);
		    if (outlevel == O_VERBOSE)
			fputc('\n', stderr);
		    else
			fputc(' ', stderr);
		}

		/* open the delivery pipe now if we're using an MDA */
		if (queryctl->mda[0])
		{
#ifdef HAVE_SETEUID
		    /*
		     * 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)
		     */
		    seteuid(queryctl->uid);
#endif /* HAVE_SETEUID */
		    mboxfd = openmailpipe(queryctl);
#ifdef HAVE_SETEUID
		    /* this will fail quietly if we didn't start as root */
		    seteuid(0);
#endif /* HAVE_SETEUID */

		    if (mboxfd < 0)
			goto cleanUp;
		}

		/* read the message and ship it to the output sink */
		ok = gen_readmsg(socket, mboxfd,
				 len, 
				 protocol->delimited,
				 queryctl);

		/* close the delivery pipe, we'll reopen before next message */
		if (queryctl->mda[0])
		    if ((ok = closemailpipe(mboxfd)) != 0 || alarmed)
			goto cleanUp;

		/* tell the server we got it OK and resynchronize */
		if (protocol->trail)
		    (protocol->trail)(socket, queryctl, num);
		if (alarmed || ok != 0)
		    goto cleanUp;
	    }

	    /*
	     * 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
		&& (fetch_it ? !queryctl->keep : queryctl->flush))
	    {
		deletions++;
		if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
		    fprintf(stderr, " flushed\n", num);
		ok = (protocol->delete)(socket, queryctl, num);
		if (alarmed || 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);
	    }
	}

	/* remove all messages flagged for deletion */
        if (protocol->expunge_cmd && deletions > 0)
	{
	    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 {
	ok = gen_transact(socket, protocol->exit_cmd);
	if (ok == 0)
	    ok = PS_NOMAIL;
	close(socket);
	goto closeUp;
    }

cleanUp:
    if (ok != 0 && ok != PS_SOCKET)
    {
	gen_transact(socket, protocol->exit_cmd);
	close(socket);
    }

closeUp:
    if (mboxfd != -1)
    {
        if (!queryctl->mda[0])
	    SMTP_quit(mboxfd);
	close(mboxfd);
    }
    alarm(0);
    signal(SIGALRM, sigsave);
    return(ok);
}

#if defined(HAVE_STDARG_H)
void gen_send(int socket, char *fmt, ... )
/* assemble command in printf(3) style and send to the server */
{
#else
void gen_send(socket, fmt, va_alist)
/* assemble command in printf(3) style and send to the server */
int socket;		/* socket to which server is connected */
const char *fmt;	/* printf-style format */
va_dcl {
#endif

    char buf [POPBUFSIZE+1];
    va_list ap;

    if (protocol->tagged)
	(void) sprintf(buf, "%s ", GENSYM);
    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);

    if (outlevel == O_VERBOSE)
    {
	char *cp;

	if (shroud && (cp = strstr(buf, shroud)))
	    memset(cp, '*', strlen(shroud));
	fprintf(stderr,"> %s\n", buf);
    }
}

#if defined(HAVE_STDARG_H)
int gen_transact(int socket, char *fmt, ... )
/* assemble command in printf(3) style, send to server, accept a response */
{
#else
int gen_transact(socket, fmt, va_alist)
/* assemble command in printf(3) style, send to server, accept a response */
int socket;		/* socket to which server is connected */
const char *fmt;	/* printf-style format */
va_dcl {
#endif

  int ok;
  char buf [POPBUFSIZE+1];
  va_list ap;

  if (protocol->tagged)
      (void) sprintf(buf, "%s ", GENSYM);
  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);
  if (outlevel == O_VERBOSE)
  {
      char *cp;

      if (shroud && (cp = strstr(buf, shroud)))
	  memset(cp, '*', strlen(shroud));
      fprintf(stderr,"> %s\n", buf);
  }

  /* we presume this does its own response echoing */
  ok = (protocol->parse_response)(socket, buf);

  return(ok);
}

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;
}

void 
alarm_handler (int signal)
/* handle server-timeout signal */
{
    alarmed = 1;
    fprintf(stderr,
	    "fetchmail: timeout after %d seconds waiting for %s.\n",
	    mytimeout, srvname);
}

/* driver.c ends here */