aboutsummaryrefslogtreecommitdiffstats
path: root/design-notes.html
blob: 4aaba5cb2bc30269c457e0674dd80db4068af70b (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
129
130
<?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 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 was handed over in a pretty poor shape, security-wise. It will
happily talk to the network with root privileges, use 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>
on */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/*
 * options.c -- command-line option processing
 *
 * Copyright 1998 by Eric S. Raymond.
 * For license terms, see the file COPYING in this directory.
 */

#include "config.h"

#include <stdio.h>
#include <pwd.h>
#include <string.h>
#include <errno.h>
#if defined(STDC_HEADERS)
#include  <stdlib.h>
#include  <limits.h>
#else
#include  <ctype.h>
#endif

#include "getopt.h"
#include "fetchmail.h"
#include "i18n.h"

enum {
    LA_INVISIBLE = 256,
    LA_PIDFILE,
    LA_SYSLOG,
    LA_NOSYSLOG,
    LA_POSTMASTER,
    LA_NOBOUNCE,
    LA_AUTH,
    LA_FETCHDOMAINS,
    LA_BSMTP,
    LA_LMTP,
    LA_PLUGIN,
    LA_PLUGOUT,
    LA_CONFIGDUMP,
    LA_SMTPNAME,
    LA_SHOWDOTS,
    LA_PRINCIPAL,
    LA_TRACEPOLLS,
    LA_SSL,
    LA_SSLKEY,
    LA_SSLCERT,
    LA_SSLPROTO,
    LA_SSLCERTCK,
    LA_SSLCERTPATH,
    LA_SSLFINGERPRINT,
    LA_FETCHSIZELIMIT,
    LA_FASTUIDL,
    LA_LIMITFLUSH,
    LA_IDLE
};

/* options still left: CgGhHjJoORTWxXYz */
static const char *shortoptions = 
	"?Vcsvd:NqL:f:i:p:UP:A:t:E:Q:u:akKFnl:r:S:Z:b:B:e:m:I:M:yw:D:";

static const struct option longoptions[] = {
/* this can be const because all flag fields are 0 and will never get set */
  {"help",	no_argument,	   (int *) 0, '?' },
  {"version",	no_argument,	   (int *) 0, 'V' },
  {"check",	no_argument,	   (int *) 0, 'c' },
  {"silent",	no_argument,	   (int *) 0, 's' },
  {"verbose",	no_argument,	   (int *) 0, 'v' },
  {"daemon",	required_argument, (int *) 0, 'd' },
  {"nodetach",	no_argument,	   (int *) 0, 'N' },
  {"quit",	no_argument,	   (int *) 0, 'q' },
  {"logfile",	required_argument, (int *) 0, 'L' },
  {"invisible",	no_argument,	   (int *) 0, LA_INVISIBLE },
  {"showdots",	no_argument,	   (int *) 0, LA_SHOWDOTS },
  {"syslog",	no_argument,	   (int *) 0, LA_SYSLOG },
  {"nosyslog",	no_argument,	   (int *) 0, LA_NOSYSLOG },
  {"fetchmailrc",required_argument,(int *) 0, 'f' },
  {"idfile",	required_argument, (int *) 0, 'i' },
  {"pidfile",	required_argument, (int *) 0, LA_PIDFILE },
  {"postmaster",required_argument, (int *) 0, LA_POSTMASTER },
  {"nobounce",	no_argument,	   (int *) 0, LA_NOBOUNCE },

  {"protocol",	required_argument, (int *) 0, 'p' },
  {"proto",	required_argument, (int *) 0, 'p' },
  {"uidl",	no_argument,	   (int *) 0, 'U' },
  {"idle",	no_argument,	   (int *) 0, LA_IDLE},
  {"port",	required_argument, (int *) 0, 'P' },
  {"service",	required_argument, (int *) 0, 'P' },
  {"auth",	required_argument, (int *) 0, LA_AUTH},
  {"timeout",	required_argument, (int *) 0, 't' },
  {"envelope",	required_argument, (int *) 0, 'E' },
  {"qvirtual",	required_argument, (int *) 0, 'Q' },

  {"user",	required_argument, (int *) 0, 'u' },
  {"username",	required_argument, (int *) 0, 'u' },

  {"all",	no_argument,	   (int *) 0, 'a' },
  {"fetchall",	no_argument,	   (int *) 0, 'a' },
  {"nokeep",	no_argument,	   (int *) 0, 'K' },
  {"keep",	no_argument,	   (int *) 0, 'k' },
  {"flush",	no_argument,	   (int *) 0, 'F' },
  {"limitflush",	no_argument, (int *) 0, LA_LIMITFLUSH },
  {"norewrite",	no_argument,	   (int *) 0, 'n' },
  {"limit",	required_argument, (int *) 0, 'l' },
  {"warnings",	required_argument, (int *) 0, 'w' },

  {"folder",	required_argument, (int *) 0, 'r' },
  {"smtphost",	required_argument, (int *) 0, 'S' },
  {"fetchdomains",	required_argument, (int *) 0, LA_FETCHDOMAINS },
  {"smtpaddress", required_argument, (int *) 0, 'D' },
  {"smtpname",	required_argument, (int *) 0, LA_SMTPNAME },
  {"antispam",	required_argument, (int *) 0, 'Z' },

  {"batchlimit",required_argument, (int *) 0, 'b' },
  {"fetchlimit",required_argument, (int *) 0, 'B' },
  {"fetchsizelimit",required_argument, (int *) 0, LA_FETCHSIZELIMIT },
  {"fastuidl",	required_argument, (int *) 0, LA_FASTUIDL },
  {"expunge",	required_argument, (int *) 0, 'e' },
  {"mda",	required_argument, (int *) 0, 'm' },
  {"bsmtp",	required_argument, (int *) 0, LA_BSMTP },
  {"lmtp",	no_argument,	   (int *) 0, LA_LMTP },

#ifdef SSL_ENABLE
  {"ssl",	no_argument,	   (int *) 0, LA_SSL },
  {"sslkey",	required_argument, (int *) 0, LA_SSLKEY },
  {"sslcert",	required_argument, (int *) 0, LA_SSLCERT },
  {"sslproto",	 required_argument, (int *) 0, LA_SSLPROTO },
  {"sslcertck", no_argument,	   (int *) 0, LA_SSLCERTCK },
  {"sslcertpath",   required_argument, (int *) 0, LA_SSLCERTPATH },
  {"sslfingerprint",   required_argument, (int *) 0, LA_SSLFINGERPRINT },
#endif

  {"principal", required_argument, (int *) 0, LA_PRINCIPAL },

#ifdef CAN_MONITOR
  {"interface",	required_argument, (int *) 0, 'I' },
  {"monitor",	required_argument, (int *) 0, 'M' },
#endif /* CAN_MONITOR */
  {"plugin",	required_argument, (int *) 0, LA_PLUGIN },
  {"plugout",	required_argument, (int *) 0, LA_PLUGOUT },

  {"configdump",no_argument,	   (int *) 0, LA_CONFIGDUMP },

  {"yydebug",	no_argument,	   (int *) 0, 'y' },

  {"tracepolls",no_argument,	   (int *) 0, LA_TRACEPOLLS },

  {(char *) 0,	no_argument,	   (int *) 0, 0 }
};

static int xatoi(char *s, int *errflagptr)
/* do safe conversion from string to number */
{
#if defined (STDC_HEADERS) && defined (LONG_MAX) && defined (INT_MAX)
    /* parse and convert numbers, but also check for invalid characters in
     * numbers
     */

    char *endptr;
    long value;

    errno = 0;

    value = strtol(s, &endptr, 0);

    /* any invalid chars in string? */
    if ( (endptr == s) || (*endptr != '\0') ) {
    	(void) fprintf(stderr, GT_("String '%s' is not a valid number string.\n"), s);
	(*errflagptr)++;
	return 0;
    }

    /* is the range valid? */
    if ( (((value == LONG_MAX) || (value == LONG_MIN)) && (errno == ERANGE)) ||
				(value > INT_MAX) || (value < INT_MIN)) {

    	(void) fprintf(stderr, GT_("Value of string '%s' is %s than %d.\n"), s,
					(value < 0) ? GT_("smaller"): GT_("larger"),
					(value < 0) ? INT_MIN : INT_MAX);
	(*errflagptr)++;
	return 0;
    }

    return (int) value;  /* shut up, I know what I'm doing */
#else
    int	i;
    char *dp;
# if defined (STDC_HEADERS)
    size_t	len;
# else
    int		len;
# endif

    /* We do only base 10 conversions here (atoi)! */

    len = strlen(s);
    /* check for leading white spaces */
    for (i = 0; (i < len) && isspace((unsigned char)s[i]); i++)
    	;

    dp = &s[i];

    /* check for +/- */
    if (i < len && (s[i] == '+' || s[i] == '-'))	i++;

    /* skip over digits */
    for ( /* no init */ ; (i < len) && isdigit((unsigned char)s[i]); i++)
    	;

    /* check for trailing garbage */
    if (i != len) {
    	(void) fprintf(stderr, GT_("String '%s' is not a valid number string.\n"), s);
    	(*errflagptr)++;
	return 0;
    }

    /* atoi should be safe by now, except for number range over/underflow */
    return atoi(dp);
#endif
}

/** parse and validate the command line options */
int parsecmdline (int argc /** argument count */,
		  char **argv /** argument strings */,
		  struct runctl *rctl /** global run controls to modify */,
		  struct query *ctl /** option record to initialize */)
{
    /*
     * return value: if positive, argv index of last parsed option + 1
     * (presumes one or more server names follows).  if zero, the
     * command line switches are such that no server names are
     * required (e.g. --version).  if negative, the command line is
     * has one or more syntax errors.
     */

    int c;
    int ocount = 0;	/* count of destinations specified */
    int errflag = 0;	/* TRUE when a syntax error is detected */
    int helpflag = 0;	/* TRUE when option help was explicitly requested */
    int option_index;
    char *buf, *cp;

    rctl->poll_interval = -1;

    memset(ctl, '\0', sizeof(struct query));    /* start clean */
    ctl->smtp_socket = -1;

    while (!errflag && 
	   (c = getopt_long(argc,argv,shortoptions,
			    longoptions, &option_index)) != -1)
    {
	switch (c) {
	case 'V':
	    versioninfo = TRUE;
	    break;
	case 'c':
	    check_only = TRUE;
	    break;
	case 's':
	    outlevel = O_SILENT;
	    break;
	case 'v':
	    if (outlevel >= O_VERBOSE)
		outlevel = O_DEBUG;
	    else
		outlevel = O_VERBOSE;
	    break;
	case 'd':
	    rctl->poll_interval = xatoi(optarg, &errflag);
	    break;
	case 'N':
	    nodetach = TRUE;
	    break;
	case 'q':
	    quitmode = TRUE;
	    quitind = optind;
	    break;
	case 'L':
	    rctl->logfile = prependdir (optarg, currentwd);
	    break;
	case LA_INVISIBLE:
	    rctl->invisible = TRUE;
	    break;
	case LA_SHOWDOTS:
	    rctl->showdots = FLAG_TRUE;
	    break;
	case 'f':
	    xfree(rcfile);
	    rcfile = prependdir (optarg, currentwd);
	    break;
	case 'i':
	    rctl->idfile = prependdir (optarg, currentwd);
	    break;
	case LA_PIDFILE:
	    rctl->pidfile = prependdir (optarg, currentwd);
	    break;
	case LA_POSTMASTER:
	    rctl->postmaster = (char *) xstrdup(optarg);
	    break;
	case LA_NOBOUNCE:
	    run.bouncemail = FALSE;
	    break;
	case 'p':
	    /* XXX -- should probably use a table lookup here */
	    if (strcasecmp(optarg,"auto") == 0)
		ctl->server.protocol = P_AUTO;
	    else if (strcasecmp(optarg,"pop2") == 0)
		ctl->server.protocol = P_POP2;
#ifdef SDPS_ENABLE
	    else if (strcasecmp(optarg,"sdps") == 0)
	    {
		ctl->server.protocol = P_POP3; 
		ctl->server.sdps = TRUE;
	    }
#endif /* SDPS_ENABLE */
	    else if (strcasecmp(optarg,"pop3") == 0)
		ctl->server.protocol = P_POP3;
	    else if (strcasecmp(optarg,"apop") == 0)
		ctl->server.protocol = P_APOP;
	    else if (strcasecmp(optarg,"rpop") == 0)
		ctl->server.protocol = P_RPOP;
	    else if (strcasecmp(optarg,"kpop") == 0)
	    {
		ctl->server.protocol = P_POP3;
		ctl->server.service = KPOP_PORT;
#ifdef KERBEROS_V5
		ctl->server.authenticate =  A_KERBEROS_V5;
#else
		ctl->server.authenticate =  A_KERBEROS_V4;
#endif /* KERBEROS_V5 */
	    }
	    else if (strcasecmp(optarg,"imap") == 0)
		ctl->server.protocol = P_IMAP;
	    else if (strcasecmp(optarg,"etrn") == 0)
		ctl->server.protocol = P_ETRN;
	    else if (strcasecmp(optarg,"odmr") == 0)
		ctl->server.protocol = P_ODMR;
	    else {
		fprintf(stderr,GT_("Invalid protocol `%s' specified.\n"), optarg);
		errflag++;
	    }
	    break;
	case 'U':
	    ctl->server.uidl = FLAG_TRUE;
	    break;
	case LA_IDLE:
	    ctl->idle = FLAG_TRUE;
	    break;
	case 'P':
	    ctl->server.service = optarg;
	    break;
	case LA_AUTH:
	    if (strcmp(optarg, "password") == 0)
		ctl->server.authenticate = A_PASSWORD;
	    else if (strcmp(optarg, "kerberos") == 0)
#ifdef KERBEROS_V5
		ctl->server.authenticate = A_KERBEROS_V5;
#else
		ctl->server.authenticate = A_KERBEROS_V4;
#endif /* KERBEROS_V5 */
	    else if (strcmp(optarg, "kerberos_v5") == 0)
		ctl->server.authenticate = A_KERBEROS_V5;
	    else if (strcmp(optarg, "kerberos_v4") == 0)
		ctl->server.authenticate = A_KERBEROS_V4;
	    else if (strcmp(optarg, "ssh") == 0)
		ctl->server.authenticate = A_SSH;
	    else if (strcmp(optarg, "otp") == 0)
		ctl->server.authenticate = A_OTP;
	    else if (strcmp(optarg, "opie") == 0)
		ctl->server.authenticate = A_OTP;
	    else if (strcmp(optarg, "ntlm") == 0)
		ctl->server.authenticate = A_NTLM;
	    else if (strcmp(optarg, "cram") == 0)
		ctl->server.authenticate = A_CRAM_MD5;
	    else if (strcmp(optarg, "cram-md5") == 0)
		ctl->server.authenticate = A_CRAM_MD5;
	    else if (strcmp(optarg, "gssapi") == 0)
		ctl->server.authenticate = A_GSSAPI;
	    else if (strcmp(optarg, "any") == 0)
		ctl->server.authenticate = A_ANY;
	    else if (strcmp(optarg, "msn") == 0)
		ctl->server.authenticate = A_MSN;
	    else {
		fprintf(stderr,GT_("Invalid authentication `%s' specified.\n"), optarg);
		errflag++;
	    }
	    break;
	case 't':
	    ctl->server.timeout = xatoi(optarg, &errflag);
	    if (ctl->server.timeout == 0)
		ctl->server.timeout = -1;
	    break;
	case 'E':
	    ctl->server.envelope = xstrdup(optarg);
	    break;
	case 'Q':    
	    ctl->server.qvirtual = xstrdup(optarg);
	    break;

	case 'u':
	    ctl->remotename = xstrdup(optarg);
	    break;
	case 'a':
	    ctl->fetchall = FLAG_TRUE;
	    break;
	case 'K':
	    ctl->keep = FLAG_FALSE;
	    break;
	case 'k':
	    ctl->keep = FLAG_TRUE;
	    break;
	case 'F':
	    ctl->flush = FLAG_TRUE;
	    break;
	case LA_LIMITFLUSH:
	    ctl->limitflush = FLAG_TRUE;
	    break;
	case 'n':
	    ctl->rewrite = FLAG_FALSE;
	    break;
	case 'l':
	    c = xatoi(optarg, &errflag);
	    ctl->limit = NUM_VALUE_IN(c);
	    break;
	case 'r':
	    buf = xstrdup(optarg);
	    cp = strtok(buf, ",");
	    do {
		save_str(&ctl->mailboxes, cp, 0);
	    } while
		((cp = strtok((char *)NULL, ",")));
	    free(buf);
	    break;
	case 'S':
	    buf = xstrdup(optarg);
	    cp = strtok(buf, ",");
	    do {
		save_str(&ctl->smtphunt, cp, TRUE);
	    } while
		((cp = strtok((char *)NULL, ",")));
	    free(buf);
	    ocount++;
	    break;
	case LA_FETCHDOMAINS:
	    buf = xstrdup(optarg);
	    cp = strtok(buf, ",");
	    do {
		save_str(&ctl->domainlist, cp, TRUE);
	    } while
		((cp = strtok((char *)NULL, ",")));
	    free(buf);
	    break;
	case 'D':
	    ctl->smtpaddress = xstrdup(optarg);
	    break;
	case LA_SMTPNAME:
	  ctl->smtpname = xstrdup(optarg);
	  break;
	case 'Z':
	    buf = xstrdup(optarg);
	    cp = strtok(buf, ",");
	    do {
		struct idlist	*idp = save_str(&ctl->antispam, NULL, 0);;

		idp->val.status.num = xatoi(cp, &errflag);
	    } while
		((cp = strtok((char *)NULL, ",")));
	    free(buf);
	    break;
	case 'b':
	    c = xatoi(optarg, &errflag);
	    ctl->batchlimit = NUM_VALUE_IN(c);
	    break;
	case 'B':
	    c = xatoi(optarg, &errflag);
	    ctl->fetchlimit = NUM_VALUE_IN(c);
	    break;
	case LA_FETCHSIZELIMIT:
	    c = xatoi(optarg, &errflag);
	    ctl->fetchsizelimit = NUM_VALUE_IN(c);
	    break;
	case LA_FASTUIDL:
	    c = xatoi(optarg, &errflag);
	    ctl->fastuidl = NUM_VALUE_IN(c);
	    break;
	case 'e':
	    c = xatoi(optarg, &errflag);
	    ctl->expunge = NUM_VALUE_IN(c);
	    break;
	case 'm':
	    ctl->mda = xstrdup(optarg);
	    ocount++;
	    break;
	case LA_BSMTP:
	    ctl->bsmtp = prependdir (optarg, currentwd);
	    ocount++;
	    break;
	case LA_LMTP:
	    ctl->listener = LMTP_MODE;
	    break;

#ifdef CAN_MONITOR
	case 'I':
	    interface_parse(optarg, &ctl->server);
	    break;
	case 'M':
	    ctl->server.monitor = xstrdup(optarg);
	    break;
#endif /* CAN_MONITOR */
	case LA_PLUGIN:
	    ctl->server.plugin = xstrdup(optarg);
	    break;
	case LA_PLUGOUT:
	    ctl->server.plugout = xstrdup(optarg);
	    break;

#ifdef SSL_ENABLE
	case LA_SSL:
	    ctl->use_ssl = FLAG_TRUE;
	    break;

	case LA_SSLKEY:
	    ctl->sslkey = prependdir (optarg, currentwd);
	    break;

	case LA_SSLCERT:
	    ctl->sslcert = prependdir (optarg, currentwd);
	    break;

	case LA_SSLPROTO:
	    ctl->sslproto = xstrdup(optarg);
	    break;

	case LA_SSLCERTCK:
	    ctl->sslcertck = FLAG_TRUE;
	    break;

	case LA_SSLCERTPATH:
	    ctl->sslcertpath = prependdir(optarg, currentwd);
	    break;

	case LA_SSLFINGERPRINT:
	    ctl->sslfingerprint = xstrdup(optarg);
	    break;
#endif

	case LA_PRINCIPAL:
	    ctl->server.principal = xstrdup(optarg);
	    break;

	case 'y':
	    yydebug = TRUE;
	    break;

	case 'w':
	    c = xatoi(optarg, &errflag);
	    ctl->warnings = NUM_VALUE_IN(c);
	    break;

	case LA_CONFIGDUMP:
	    configdump = TRUE;
	    break;

	case LA_SYSLOG:
	    rctl->use_syslog = FLAG_TRUE;
	    break;

	case LA_NOSYSLOG:
	    rctl->use_syslog = FLAG_FALSE;
	    break;

	case LA_TRACEPOLLS:
	    ctl->server.tracepolls = FLAG_TRUE;
	    break;

	case '?':
	default:
	    helpflag++;
	}
    }

    if (errflag || ocount > 1 || helpflag) {
	/* squawk if syntax errors were detected */
#define P(s)    fputs(s, helpflag ? stdout : stderr)
	P(GT_("usage:  fetchmail [options] [server ...]\n"));
	P(GT_("  Options are as follows:\n"));
	P(GT_("  -?, --help        display this option help\n"));
	P(GT_("  -V, --version     display version info\n"));

	P(GT_("  -c, --check       check for messages without fetching\n"));
	P(GT_("  -s, --silent      work silently\n"));
	P(GT_("  -v, --verbose     work noisily (diagnostic output)\n"));
	P(GT_("  -d, --daemon      run as a daemon once per n seconds\n"));
	P(GT_("  -N, --nodetach    don't detach daemon process\n"));
	P(GT_("  -q, --quit        kill daemon process\n"));
	P(GT_("  -L, --logfile     specify logfile name\n"));
	P(GT_("      --syslog      use syslog(3) for most messages when running as a daemon\n"));
	P(GT_("      --invisible   don't write Received & enable host spoofing\n"));
	P(GT_("  -f, --fetchmailrc specify alternate run control file\n"));
	P(GT_("  -i, --idfile      specify alternate UIDs file\n"));
	P(GT_("      --pidfile     specify alternate PID (lock) file\n"));
	P(GT_("      --postmaster  specify recipient of last resort\n"));
	P(GT_("      --nobounce    redirect bounces from user to postmaster.\n"));
#ifdef CAN_MONITOR
	P(GT_("  -I, --interface   interface required specification\n"));
	P(GT_("  -M, --monitor     monitor interface for activity\n"));
#endif
#if defined( SSL_ENABLE )
	P(GT_("      --ssl         enable ssl encrypted session\n"));
	P(GT_("      --sslkey      ssl private key file\n"));
	P(GT_("      --sslcert     ssl client certificate\n"));
	P(GT_("      --sslcertck   do strict server certificate check (recommended)\n"));
	P(GT_("      --sslcertpath path to ssl certificates\n"));
	P(GT_("      --sslfingerprint fingerprint that must match that of the server's cert.\n"));
	P(GT_("      --sslproto    force ssl protocol (ssl2/ssl3/tls1)\n"));
#endif
	P(GT_("      --plugin      specify external command to open connection\n"));
	P(GT_("      --plugout     specify external command to open smtp connection\n"));

	P(GT_("  -p, --protocol    specify retrieval protocol (see man page)\n"));
	P(GT_("  -U, --uidl        force the use of UIDLs (pop3 only)\n"));
	P(GT_("      --port        TCP port to connect to (obsolete, use --service)\n"));
	P(GT_("  -P, --service     TCP service to connect to (can be numeric TCP port)\n"));
	P(GT_("      --auth        authentication type (password/kerberos/ssh/otp)\n"));
	P(GT_("  -t, --timeout     server nonresponse timeout\n"));
	P(GT_("  -E, --envelope    envelope address header\n"));
	P(GT_("  -Q, --qvirtual    prefix to remove from local user id\n"));
	P(GT_("      --principal   mail service principal\n"));
	P(GT_("      --tracepolls  add poll-tracing information to Received header\n"));

	P(GT_("  -u, --username    specify users's login on server\n"));
	P(GT_("  -a, --[fetch]all  retrieve old and new messages\n"));
	P(GT_("  -K, --nokeep      delete new messages after retrieval\n"));
	P(GT_("  -k, --keep        save new messages after retrieval\n"));
	P(GT_("  -F, --flush       delete old messages from server\n"));
	P(GT_("      --limitflush  delete oversized messages\n"));
	P(GT_("  -n, --norewrite   don't rewrite header addresses\n"));
	P(GT_("  -l, --limit       don't fetch messages over given size\n"));
	P(GT_("  -w, --warnings    interval between warning mail notification\n"));

	P(GT_("  -S, --smtphost    set SMTP forwarding host\n"));
	P(GT_("      --fetchdomains fetch mail for specified domains\n"));
	P(GT_("  -D, --smtpaddress set SMTP delivery domain to use\n"));
	P(GT_("      --smtpname    set SMTP full name username@domain\n"));
	P(GT_("  -Z, --antispam,   set antispam response values\n"));
	P(GT_("  -b, --batchlimit  set batch limit for SMTP connections\n"));
	P(GT_("  -B, --fetchlimit  set fetch limit for server connections\n"));
	P(GT_("      --fetchsizelimit set fetch message size limit\n"));
	P(GT_("      --fastuidl    do a binary search for UIDLs\n"));
	P(GT_("  -e, --expunge     set max deletions between expunges\n"));
	P(GT_("  -m, --mda         set MDA to use for forwarding\n"));
	P(GT_("      --bsmtp       set output BSMTP file\n"));
	P(GT_("      --lmtp        use LMTP (RFC2033) for delivery\n"));
	P(GT_("  -r, --folder      specify remote folder name\n"));
	P(GT_("      --showdots    show progress dots even in logfiles\n"));
#undef P
	/* undocumented:
	 * --configdump (internal use by fetchmailconf, dumps
	 *               configuration as Python source code)
	 * --yydebug    (developer use, enables parser debugging) */

	if (helpflag)
	    exit(PS_SUCCESS);
	else
	    exit(PS_SYNTAX);
    }

    return(optind);
}

/* options.c ends here */