From c0221cbe360b83eac652d474c6c72a1eb3016268 Mon Sep 17 00:00:00 2001 From: "Eric S. Raymond" Date: Sun, 24 Jun 2001 19:24:02 +0000 Subject: Implemented fetchdomains. svn path=/trunk/; revision=3373 --- NEWS | 3 + conf.c | 1 + design-notes.html | 9 +- etrn.c | 2 +- fetchmail-FAQ.html | 9 +- fetchmail.c | 177 ++++---- fetchmail.h | 2 + fetchmail.man | 38 +- fetchmailconf | 1250 ++++++++++++++++++++++++++-------------------------- odmr.c | 20 +- options.c | 67 +-- rcfile_l.l | 1 + rcfile_y.y | 9 +- sink.c | 2 +- 14 files changed, 825 insertions(+), 765 deletions(-) diff --git a/NEWS b/NEWS index 9bd42513..e8a5cab8 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,9 @@ * Fixes for Debian bugs #101792, #101950. * Updated Danish translation by Byrial Jensen. * ODMR fixes from Matt Armstrong . +* The smtphost option has been split. It is no longer overloaded to set + the list of domains to be queried in ETRN and ODMR modes. Instead, use + the `fetchdomains' option. fetchmail-5.8.8 (Wed Jun 20 17:22:26 EDT 2001), 20782 lines: diff --git a/conf.c b/conf.c index f172dfe3..16a67c00 100644 --- a/conf.c +++ b/conf.c @@ -383,6 +383,7 @@ void dump_config(struct runctl *runp, struct query *querylist) numdump("expunge", ctl->expunge); stringdump("properties", ctl->properties); listdump("smtphunt", ctl->smtphunt); + listdump("fetchdomains", ctl->domainlist); stringdump("smtpaddress", ctl->smtpaddress); stringdump("smtpname", ctl->smtpname); diff --git a/design-notes.html b/design-notes.html index c6dd2cce..27b4ccf3 100644 --- a/design-notes.html +++ b/design-notes.html @@ -10,7 +10,7 @@
Back to Fetchmail Home Page To Site Map -$Date: 2001/03/11 17:32:55 $ +$Date: 2001/06/24 19:23:57 $

Design Notes On Fetchmail

@@ -300,7 +300,7 @@ ABOUT_NLS in the source distribution). This places some minor constraints on the code.

Strings that must be subject to translation should be wrapped with _() -or N_() -- the former in functuib arguments, the latter in static +or N_() -- the former in function arguments, the latter in static initializers and other non-function-argument contexts.

Checklist for Adding Options

@@ -340,9 +340,6 @@ following minimum steps. two changes; one to the collected table of options, and one full text description of the option. -
  • Add the new token and a brief description to the header comment of - sample.rcfile. -
  • Hack fetchmailconf to configure it. Bump the fetchmailconf version.
  • Hack conf.c to dump the option so we won't have a version-skew problem. @@ -554,7 +551,7 @@ RFC2221 IMAP4 Login Referrals
    Back to Fetchmail Home Page To Site Map -$Date: 2001/03/11 17:32:55 $ +$Date: 2001/06/24 19:23:57 $

    Eric S. Raymond <esr@snark.thyrsus.com>
    diff --git a/etrn.c b/etrn.c index 76c12121..38944665 100644 --- a/etrn.c +++ b/etrn.c @@ -60,7 +60,7 @@ static int etrn_getrange(int sock, struct query *ctl, const char *id, * By default, the hostlist has a single entry, the fetchmail host's * canonical DNS name. */ - for (qnp = ctl->smtphunt; qnp; qnp = qnp->next) + for (qnp = ctl->domainlist; qnp; qnp = qnp->next) { /* ship the actual poll and get the response */ gen_send(sock, "ETRN %s", qnp->id); diff --git a/fetchmail-FAQ.html b/fetchmail-FAQ.html index a894356f..3951dcc5 100644 --- a/fetchmail-FAQ.html +++ b/fetchmail-FAQ.html @@ -10,7 +10,7 @@
    Back to Fetchmail Home Page To Site Map -$Date: 2001/06/17 15:33:06 $ +$Date: 2001/06/24 19:23:57 $

    Frequently Asked Questions About Fetchmail

    @@ -708,6 +708,11 @@ once you have installed the `bind' package.

    F1. Why does my old .fetchmailrc file no longer work?

    +

    If your file predates 5.8.9

    + +

    If you were using ETRN mode, change your smtphost option to +a fetchdomains option. +

    If your file predates 5.8.3

    The `via localhost' special case for use with ssh tunnelling is gone. @@ -2859,7 +2864,7 @@ date from the last Received header.

    Back to Fetchmail Home Page To Site Map -$Date: 2001/06/17 15:33:06 $ +$Date: 2001/06/24 19:23:57 $

    Eric S. Raymond <esr@snark.thyrsus.com>
    diff --git a/fetchmail.c b/fetchmail.c index 4e405d35..7b3ca71c 100644 --- a/fetchmail.c +++ b/fetchmail.c @@ -780,6 +780,7 @@ static void optmerge(struct query *h2, struct query *h1, int force) list_merge(&h2->localnames, &h1->localnames, force); list_merge(&h2->mailboxes, &h1->mailboxes, force); list_merge(&h2->smtphunt, &h1->smtphunt, force); + list_merge(&h2->domainlist, &h1->domainlist, force); list_merge(&h2->antispam, &h1->antispam, force); #define FLAG_MERGE(fld) if (force ? !!h1->fld : !h2->fld) h2->fld = h1->fld @@ -1050,6 +1051,12 @@ static int load_params(int argc, char **argv, int optind) if (!ctl->smtphunt) save_str(&ctl->smtphunt, fetchmailhost, FALSE); + /* + * Make sure we have a nonempty list of domains to fetch from. + */ + if ((ctl->server.protocol==P_ETRN || ctl->server.protocol==P_ODMR) && !ctl->domainlist) + save_str(&ctl->domainlist, fetchmailhost, FALSE); + /* if `user' doesn't name a real local user, try to run as root */ if ((pw = getpwnam(user)) == (struct passwd *)NULL) ctl->uid = 0; @@ -1580,80 +1587,94 @@ static void dump_params (struct runctl *runp, else printf(".\n"); - if (MAILBOX_PROTOCOL(ctl)) { - if (!ctl->mailboxes->id) - printf(_(" Default mailbox selected.\n")); - else - { - struct idlist *idp; + if (MAILBOX_PROTOCOL(ctl)) + { + if (!ctl->mailboxes->id) + printf(_(" Default mailbox selected.\n")); + else + { + struct idlist *idp; - printf(_(" Selected mailboxes are:")); - for (idp = ctl->mailboxes; idp; idp = idp->next) - printf(" %s", idp->id); - printf("\n"); - } - printf(_(" %s messages will be retrieved (--all %s).\n"), - ctl->fetchall ? _("All") : _("Only new"), - ctl->fetchall ? "on" : "off"); - printf(_(" Fetched messages %s be kept on the server (--keep %s).\n"), - ctl->keep ? _("will") : _("will not"), - ctl->keep ? "on" : "off"); - printf(_(" Old messages %s be flushed before message retrieval (--flush %s).\n"), - ctl->flush ? _("will") : _("will not"), - ctl->flush ? "on" : "off"); - printf(_(" Rewrite of server-local addresses is %s (--norewrite %s).\n"), - ctl->rewrite ? _("enabled") : _("disabled"), - ctl->rewrite ? "off" : "on"); - printf(_(" Carriage-return stripping is %s (stripcr %s).\n"), - ctl->stripcr ? _("enabled") : _("disabled"), - ctl->stripcr ? "on" : "off"); - printf(_(" Carriage-return forcing is %s (forcecr %s).\n"), - ctl->forcecr ? _("enabled") : _("disabled"), - ctl->forcecr ? "on" : "off"); - printf(_(" Interpretation of Content-Transfer-Encoding is %s (pass8bits %s).\n"), - ctl->pass8bits ? _("disabled") : _("enabled"), - ctl->pass8bits ? "on" : "off"); - printf(_(" MIME decoding is %s (mimedecode %s).\n"), - ctl->mimedecode ? _("enabled") : _("disabled"), - ctl->mimedecode ? "on" : "off"); - printf(_(" Idle after poll is %s (idle %s).\n"), - ctl->idle ? _("enabled") : _("disabled"), - ctl->idle ? "on" : "off"); - printf(_(" Nonempty Status lines will be %s (dropstatus %s)\n"), - ctl->dropstatus ? _("discarded") : _("kept"), - ctl->dropstatus ? "on" : "off"); - printf(_(" Delivered-To lines will be %s (dropdelivered %s)\n"), - ctl->dropdelivered ? _("discarded") : _("kept"), - ctl->dropdelivered ? "on" : "off"); + printf(_(" Selected mailboxes are:")); + for (idp = ctl->mailboxes; idp; idp = idp->next) + printf(" %s", idp->id); + printf("\n"); + } + printf(_(" %s messages will be retrieved (--all %s).\n"), + ctl->fetchall ? _("All") : _("Only new"), + ctl->fetchall ? "on" : "off"); + printf(_(" Fetched messages %s be kept on the server (--keep %s).\n"), + ctl->keep ? _("will") : _("will not"), + ctl->keep ? "on" : "off"); + printf(_(" Old messages %s be flushed before message retrieval (--flush %s).\n"), + ctl->flush ? _("will") : _("will not"), + ctl->flush ? "on" : "off"); + printf(_(" Rewrite of server-local addresses is %s (--norewrite %s).\n"), + ctl->rewrite ? _("enabled") : _("disabled"), + ctl->rewrite ? "off" : "on"); + printf(_(" Carriage-return stripping is %s (stripcr %s).\n"), + ctl->stripcr ? _("enabled") : _("disabled"), + ctl->stripcr ? "on" : "off"); + printf(_(" Carriage-return forcing is %s (forcecr %s).\n"), + ctl->forcecr ? _("enabled") : _("disabled"), + ctl->forcecr ? "on" : "off"); + printf(_(" Interpretation of Content-Transfer-Encoding is %s (pass8bits %s).\n"), + ctl->pass8bits ? _("disabled") : _("enabled"), + ctl->pass8bits ? "on" : "off"); + printf(_(" MIME decoding is %s (mimedecode %s).\n"), + ctl->mimedecode ? _("enabled") : _("disabled"), + ctl->mimedecode ? "on" : "off"); + printf(_(" Idle after poll is %s (idle %s).\n"), + ctl->idle ? _("enabled") : _("disabled"), + ctl->idle ? "on" : "off"); + printf(_(" Nonempty Status lines will be %s (dropstatus %s)\n"), + ctl->dropstatus ? _("discarded") : _("kept"), + ctl->dropstatus ? "on" : "off"); + printf(_(" Delivered-To lines will be %s (dropdelivered %s)\n"), + ctl->dropdelivered ? _("discarded") : _("kept"), + ctl->dropdelivered ? "on" : "off"); + if (NUM_NONZERO(ctl->limit)) + { if (NUM_NONZERO(ctl->limit)) - { - if (NUM_NONZERO(ctl->limit)) - printf(_(" Message size limit is %d octets (--limit %d).\n"), - ctl->limit, ctl->limit); - else if (outlevel >= O_VERBOSE) - printf(_(" No message size limit (--limit 0).\n")); - if (run.poll_interval > 0) - printf(_(" Message size warning interval is %d seconds (--warnings %d).\n"), - ctl->warnings, ctl->warnings); - else if (outlevel >= O_VERBOSE) - printf(_(" Size warnings on every poll (--warnings 0).\n")); - } - if (NUM_NONZERO(ctl->fetchlimit)) - printf(_(" Received-message limit is %d (--fetchlimit %d).\n"), - ctl->fetchlimit, ctl->fetchlimit); + printf(_(" Message size limit is %d octets (--limit %d).\n"), + ctl->limit, ctl->limit); else if (outlevel >= O_VERBOSE) - printf(_(" No received-message limit (--fetchlimit 0).\n")); - if (NUM_NONZERO(ctl->batchlimit)) - printf(_(" SMTP message batch limit is %d.\n"), ctl->batchlimit); + printf(_(" No message size limit (--limit 0).\n")); + if (run.poll_interval > 0) + printf(_(" Message size warning interval is %d seconds (--warnings %d).\n"), + ctl->warnings, ctl->warnings); else if (outlevel >= O_VERBOSE) - printf(_(" No SMTP message batch limit (--batchlimit 0).\n")); - if (MAILBOX_PROTOCOL(ctl)) - { - if (NUM_NONZERO(ctl->expunge)) - printf(_(" Deletion interval between expunges forced to %d (--expunge %d).\n"), ctl->expunge, ctl->expunge); - else if (outlevel >= O_VERBOSE) - printf(_(" No forced expunges (--expunge 0).\n")); - } + printf(_(" Size warnings on every poll (--warnings 0).\n")); + } + if (NUM_NONZERO(ctl->fetchlimit)) + printf(_(" Received-message limit is %d (--fetchlimit %d).\n"), + ctl->fetchlimit, ctl->fetchlimit); + else if (outlevel >= O_VERBOSE) + printf(_(" No received-message limit (--fetchlimit 0).\n")); + if (NUM_NONZERO(ctl->batchlimit)) + printf(_(" SMTP message batch limit is %d.\n"), ctl->batchlimit); + else if (outlevel >= O_VERBOSE) + printf(_(" No SMTP message batch limit (--batchlimit 0).\n")); + if (MAILBOX_PROTOCOL(ctl)) + { + if (NUM_NONZERO(ctl->expunge)) + printf(_(" Deletion interval between expunges forced to %d (--expunge %d).\n"), ctl->expunge, ctl->expunge); + else if (outlevel >= O_VERBOSE) + printf(_(" No forced expunges (--expunge 0).\n")); + } + } + else /* ODMR or ETRN */ + { + struct idlist *idp; + + printf(_(" Domains for which mail will be fetched are:")); + for (idp = ctl->domainlist; idp; idp = idp->next) + { + printf(" %s", idp->id); + if (!idp->val.status.mark) + printf(_(" (default)")); + } + printf("\n"); } if (ctl->bsmtp) printf(_(" Messages will be appended to %s as BSMTP\n"), visbuf(ctl->bsmtp)); @@ -1663,14 +1684,18 @@ static void dump_params (struct runctl *runp, { struct idlist *idp; - printf(_(" Messages will be %cMTP-forwarded to:"), ctl->listener); - for (idp = ctl->smtphunt; idp; idp = idp->next) + if (ctl->smtphunt) { - printf(" %s", idp->id); - if (!idp->val.status.mark) - printf(_(" (default)")); + printf(_(" Messages will be %cMTP-forwarded to:"), + ctl->listener); + for (idp = ctl->smtphunt; idp; idp = idp->next) + { + printf(" %s", idp->id); + if (!idp->val.status.mark) + printf(_(" (default)")); + } + printf("\n"); } - printf("\n"); if (ctl->smtpaddress) printf(_(" Host part of MAIL FROM line will be %s\n"), ctl->smtpaddress); diff --git a/fetchmail.h b/fetchmail.h index 947b4ba6..f171d67c 100644 --- a/fetchmail.h +++ b/fetchmail.h @@ -255,6 +255,7 @@ struct query /* per-forwarding-target data */ struct idlist *smtphunt; /* list of SMTP hosts to try forwarding to */ + struct idlist *domainlist; /* domainlist to fetch from */ char *smtpaddress; /* address to force in RCPT TO */ char *smtpname; /* full RCPT TO name, including domain */ struct idlist *antispam; /* list of listener's antispam response */ @@ -461,6 +462,7 @@ extern int mytimeout; int interruptible_idle(int interval); /* sink.c: forwarding */ +int smtp_open(struct query *); int stuffline(struct query *, char *); int open_sink(struct query*, struct msgblk *, int*, int*); void release_sink(struct query *); diff --git a/fetchmail.man b/fetchmail.man index c71cadd9..73ebd39d 100644 --- a/fetchmail.man +++ b/fetchmail.man @@ -352,23 +352,28 @@ match. This can be used to prevent man-in-the-middle attacks. .B \-S , --smtphost (Keyword: smtp[host]) Specify a hunt list of hosts to forward mail to (one or more -hostnames, comma-separated). In ETRN mode, set the host that the -mailserver is asked to ship mail to. Hosts are tried in list order; -the first one that is up becomes the forwarding target for the current -run. Normally, `localhost' is added to the end of the list as an -invisible default. However, when using ETRN or ODMR mode or Kerberos -authentication, the FQDN of the machine running fetchmail is added to -the end of the list as an invisible default. Each hostname may have a -port number following the host name. The port number is separated -from the host name by a slash; the default port is 25 (or ``smtp'' -under IPv6). If you specify an absolute pathname (beginning with a -/), it will be interpreted as the name of a UNIX socket accepting LMTP -connections (such as is supported by the Cyrus IMAP daemon) Example: +hostnames, comma-separated). Hosts are tried in list order; the first +one that is up becomes the forwarding target for the current run. +Normally, `localhost' is added to the end of the list as an invisible +default. However, when using Kerberos authentication, the FQDN of the +machine running fetchmail is added to the end of the list as an +invisible default. Each hostname may have a port number following the +host name. The port number is separated from the host name by a +slash; the default port is 25 (or ``smtp'' under IPv6). If you +specify an absolute pathname (beginning with a /), it will be +interpreted as the name of a UNIX socket accepting LMTP connections +(such as is supported by the Cyrus IMAP daemon) Example: --smtphost server1,server2/2525,server3,/var/imap/socket/lmtp -In ODMR mode, this option specifies the list of domains the ODMR -server should ship mail for once the connection is turned around. +This option can be used with ODMR, and will make fetchmail a relay +between the ODMR server and and SMTP or LMTP receiver. +.TP +.B --fetchdomains +(Keyword: fetchdomains) +In ETRN or ODMR mode, this option specifies the list of domains the +server should ship mail for once the connection is turned around. The +default is the FQDN of the machine running fetchmail. .TP .B \-D , --smtpaddress (Keyword: smtpaddress) @@ -1332,6 +1337,9 @@ T} smtphost -S T{ Specify smtp host(s) to forward to T} +fetchdomains \& T{ +Specify domains for which mail should be fetched +T} smtpaddress -D T{ Specify the domain to be put in RCPT TO lines T} @@ -2153,8 +2161,6 @@ you, switch to IMAP4. Usuing LMTP with a localname that is not a full email name (containing @ and having a host part) will fail. .PP -ODMR is very new. The ODMR code is untested. -.PP The `principal' option only handles Kerberos IV, not V. .PP Send comments, bug reports, gripes, and the like to the diff --git a/fetchmailconf b/fetchmailconf index c84a9b4e..a92e542c 100755 --- a/fetchmailconf +++ b/fetchmailconf @@ -3,8 +3,8 @@ # A GUI configurator for generating fetchmail configuration files. # by Eric S. Raymond, . # Requires Python with Tkinter, and the following OS-dependent services: -# posix, posixpath, socket -version = "1.36" +# posix, posixpath, socket +version = "1.37" from Tkinter import * from Dialog import * @@ -15,37 +15,37 @@ import sys, time, os, string, socket, getopt, tempfile # class Configuration: def __init__(self): - self.poll_interval = 0 # Normally, run in foreground - self.logfile = None # No logfile, initially - self.idfile = os.environ["HOME"] + "/.fetchids" # Default idfile, initially - self.postmaster = None # No last-resort address, initially - self.bouncemail = TRUE # Bounce errors to users - self.spambounce = FALSE # Bounce spam errors - self.properties = None # No exiguous properties - self.invisible = FALSE # Suppress Received line & spoof? - self.syslog = FALSE # Use syslogd for logging? - self.servers = [] # List of included sites - Configuration.typemap = ( - ('poll_interval', 'Int'), - ('logfile', 'String'), - ('idfile', 'String'), - ('postmaster', 'String'), - ('bouncemail', 'Boolean'), - ('spambounce', 'Boolean'), - ('properties', 'String'), - ('syslog', 'Boolean'), - ('invisible', 'Boolean')) + self.poll_interval = 0 # Normally, run in foreground + self.logfile = None # No logfile, initially + self.idfile = os.environ["HOME"] + "/.fetchids" # Default idfile, initially + self.postmaster = None # No last-resort address, initially + self.bouncemail = TRUE # Bounce errors to users + self.spambounce = FALSE # Bounce spam errors + self.properties = None # No exiguous properties + self.invisible = FALSE # Suppress Received line & spoof? + self.syslog = FALSE # Use syslogd for logging? + self.servers = [] # List of included sites + Configuration.typemap = ( + ('poll_interval', 'Int'), + ('logfile', 'String'), + ('idfile', 'String'), + ('postmaster', 'String'), + ('bouncemail', 'Boolean'), + ('spambounce', 'Boolean'), + ('properties', 'String'), + ('syslog', 'Boolean'), + ('invisible', 'Boolean')) def __repr__(self): - str = ""; - if self.syslog != ConfigurationDefaults.syslog: - str = str + ("set syslog\n") - elif self.logfile: - str = str + ("set logfile \"%s\"\n" % (self.logfile,)); - if self.idfile != ConfigurationDefaults.idfile: - str = str + ("set idfile \"%s\"\n" % (self.idfile,)); - if self.postmaster != ConfigurationDefaults.postmaster: - str = str + ("set postmaster \"%s\"\n" % (self.postmaster,)); + str = ""; + if self.syslog != ConfigurationDefaults.syslog: + str = str + ("set syslog\n") + elif self.logfile: + str = str + ("set logfile \"%s\"\n" % (self.logfile,)); + if self.idfile != ConfigurationDefaults.idfile: + str = str + ("set idfile \"%s\"\n" % (self.idfile,)); + if self.postmaster != ConfigurationDefaults.postmaster: + str = str + ("set postmaster \"%s\"\n" % (self.postmaster,)); if self.bouncemail: str = str + ("set bouncemail\n") else: @@ -54,13 +54,13 @@ class Configuration: str = str + ("set spambounce\n") else: str = str + ("set no spambounce\n") - if self.properties != ConfigurationDefaults.properties: - str = str + ("set properties \"%s\"\n" % (self.properties,)); - if self.poll_interval > 0: - str = str + "set daemon " + `self.poll_interval` + "\n" - for site in self.servers: - str = str + repr(site) - return str + if self.properties != ConfigurationDefaults.properties: + str = str + ("set properties \"%s\"\n" % (self.properties,)); + if self.poll_interval > 0: + str = str + "set daemon " + `self.poll_interval` + "\n" + for site in self.servers: + str = str + repr(site) + return str def __delitem__(self, name): for si in range(len(self.servers)): @@ -69,126 +69,126 @@ class Configuration: break def __str__(self): - return "[Configuration: " + repr(self) + "]" + return "[Configuration: " + repr(self) + "]" class Server: def __init__(self): - self.pollname = None # Poll label - self.via = None # True name of host - self.active = TRUE # Poll status - self.interval = 0 # Skip interval - self.protocol = 'auto' # Default to auto protocol - self.port = 0 # Port number to use - self.uidl = FALSE # Don't use RFC1725 UIDLs by default - self.auth = 'any' # Default to password authentication - self.timeout = 300 # 5-minute timeout - self.envelope = 'Received' # Envelope-address header - self.envskip = 0 # Number of envelope headers to skip - self.qvirtual = None # Name prefix to strip - self.aka = [] # List of DNS aka names - self.dns = TRUE # Enable DNS lookup on multidrop - self.localdomains = [] # Domains to be considered local - self.interface = None # IP address and range - self.monitor = None # IP address and range - self.plugin = None # Plugin command for going to server - self.plugout = None # Plugin command for going to listener - self.netsec = None # IPV6 security options - self.principal = None # Kerberos principal - self.tracepolls = FALSE # Add trace-poll info to headers - self.users = [] # List of user entries for site - Server.typemap = ( - ('pollname', 'String'), - ('via', 'String'), - ('active', 'Boolean'), - ('interval', 'Int'), - ('protocol', 'String'), - ('port', 'Int'), - ('uidl', 'Boolean'), - ('auth', 'String'), - ('timeout', 'Int'), - ('envelope', 'String'), - ('envskip', 'Int'), - ('qvirtual', 'String'), - # leave aka out - ('dns', 'Boolean'), - # leave localdomains out - ('interface', 'String'), - ('monitor', 'String'), - ('plugin', 'String'), - ('plugout', 'String'), - ('netsec', 'String'), - ('principal', 'String'), + self.pollname = None # Poll label + self.via = None # True name of host + self.active = TRUE # Poll status + self.interval = 0 # Skip interval + self.protocol = 'auto' # Default to auto protocol + self.port = 0 # Port number to use + self.uidl = FALSE # Don't use RFC1725 UIDLs by default + self.auth = 'any' # Default to password authentication + self.timeout = 300 # 5-minute timeout + self.envelope = 'Received' # Envelope-address header + self.envskip = 0 # Number of envelope headers to skip + self.qvirtual = None # Name prefix to strip + self.aka = [] # List of DNS aka names + self.dns = TRUE # Enable DNS lookup on multidrop + self.localdomains = [] # Domains to be considered local + self.interface = None # IP address and range + self.monitor = None # IP address and range + self.plugin = None # Plugin command for going to server + self.plugout = None # Plugin command for going to listener + self.netsec = None # IPV6 security options + self.principal = None # Kerberos principal + self.tracepolls = FALSE # Add trace-poll info to headers + self.users = [] # List of user entries for site + Server.typemap = ( + ('pollname', 'String'), + ('via', 'String'), + ('active', 'Boolean'), + ('interval', 'Int'), + ('protocol', 'String'), + ('port', 'Int'), + ('uidl', 'Boolean'), + ('auth', 'String'), + ('timeout', 'Int'), + ('envelope', 'String'), + ('envskip', 'Int'), + ('qvirtual', 'String'), + # leave aka out + ('dns', 'Boolean'), + # leave localdomains out + ('interface', 'String'), + ('monitor', 'String'), + ('plugin', 'String'), + ('plugout', 'String'), + ('netsec', 'String'), + ('principal', 'String'), ('tracepolls','Boolean')) def dump(self, folded): - res = "" - if self.active: res = res + "poll" - else: res = res + "skip" - res = res + (" " + self.pollname) - if self.via: - res = res + (" via " + str(self.via) + "\n"); - if self.protocol != ServerDefaults.protocol: - res = res + " with proto " + self.protocol - if self.port != defaultports[self.protocol] and self.port != 0: - res = res + " port " + `self.port` - if self.timeout != ServerDefaults.timeout: - res = res + " timeout " + `self.timeout` - if self.interval != ServerDefaults.interval: - res = res + " interval " + `self.interval` - if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip: - if self.envskip: - res = res + " envelope " + `self.envskip` + " " + self.envelope - else: - res = res + " envelope " + self.envelope - if self.qvirtual: - res = res + (" qvirtual " + str(self.qvirtual) + "\n"); - if self.auth != ServerDefaults.auth: - res = res + " auth " + self.auth - if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl: - res = res + " and options" - if self.dns != ServerDefaults.dns: - res = res + flag2str(self.dns, 'dns') - if self.uidl != ServerDefaults.uidl: - res = res + flag2str(self.uidl, 'uidl') - if folded: res = res + "\n " - else: res = res + " " - - if self.aka: - res = res + "aka" - for x in self.aka: - res = res + " " + x - if self.aka and self.localdomains: res = res + " " - if self.localdomains: - res = res + ("localdomains") - for x in self.localdomains: - res = res + " " + x + res = "" + if self.active: res = res + "poll" + else: res = res + "skip" + res = res + (" " + self.pollname) + if self.via: + res = res + (" via " + str(self.via) + "\n"); + if self.protocol != ServerDefaults.protocol: + res = res + " with proto " + self.protocol + if self.port != defaultports[self.protocol] and self.port != 0: + res = res + " port " + `self.port` + if self.timeout != ServerDefaults.timeout: + res = res + " timeout " + `self.timeout` + if self.interval != ServerDefaults.interval: + res = res + " interval " + `self.interval` + if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip: + if self.envskip: + res = res + " envelope " + `self.envskip` + " " + self.envelope + else: + res = res + " envelope " + self.envelope + if self.qvirtual: + res = res + (" qvirtual " + str(self.qvirtual) + "\n"); + if self.auth != ServerDefaults.auth: + res = res + " auth " + self.auth + if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl: + res = res + " and options" + if self.dns != ServerDefaults.dns: + res = res + flag2str(self.dns, 'dns') + if self.uidl != ServerDefaults.uidl: + res = res + flag2str(self.uidl, 'uidl') + if folded: res = res + "\n " + else: res = res + " " + + if self.aka: + res = res + "aka" + for x in self.aka: + res = res + " " + x + if self.aka and self.localdomains: res = res + " " + if self.localdomains: + res = res + ("localdomains") + for x in self.localdomains: + res = res + " " + x if (self.aka or self.localdomains): - if folded: - res = res + "\n " - else: - res = res + " " + if folded: + res = res + "\n " + else: + res = res + " " - if self.tracepolls: - res = res + "tracepolls\n" + if self.tracepolls: + res = res + "tracepolls\n" - if self.interface: + if self.interface: res = res + "interface " + str(self.interface) - if self.monitor: + if self.monitor: res = res + " monitor " + str(self.monitor) - if self.netsec: + if self.netsec: res = res + " netsec " + str(self.netsec) - if self.principal: - res = res + " principal " + `self.principal` - if self.interface or self.monitor or self.netsec or self.principal: - if folded: - res = res + "\n" + if self.principal: + res = res + " principal " + `self.principal` + if self.interface or self.monitor or self.netsec or self.principal: + if folded: + res = res + "\n" - if res[-1] == " ": res = res[0:-1] + if res[-1] == " ": res = res[0:-1] - for user in self.users: - res = res + repr(user) - res = res + "\n" - return res; + for user in self.users: + res = res + repr(user) + res = res + "\n" + return res; def __delitem__(self, name): for ui in range(len(self.users)): @@ -197,151 +197,152 @@ class Server: break def __repr__(self): - return self.dump(TRUE) + return self.dump(TRUE) def __str__(self): - return "[Server: " + self.dump(FALSE) + "]" + return "[Server: " + self.dump(FALSE) + "]" class User: def __init__(self): if os.environ.has_key("USER"): - self.remote = os.environ["USER"] # Remote username + self.remote = os.environ["USER"] # Remote username elif os.environ.has_key("LOGNAME"): self.remote = os.environ["LOGNAME"] else: print "Can't get your username!" sys.exit(1) - self.localnames = [self.remote,]# Local names - self.password = None # Password for mail account access - self.mailboxes = [] # Remote folders to retrieve from - self.smtphunt = [] # Hosts to forward to - self.smtpaddress = None # Append this to MAIL FROM line - self.smtpname = None # Use this for RCPT TO - self.preconnect = None # Connection setup - self.postconnect = None # Connection wrapup - self.mda = None # Mail Delivery Agent - self.bsmtp = None # BSMTP output file - self.lmtp = FALSE # Use LMTP rather than SMTP? - self.antispam = "571 550 501" # Listener's spam-block code - self.keep = FALSE # Keep messages - self.flush = FALSE # Flush messages - self.fetchall = FALSE # Fetch old messages - self.rewrite = TRUE # Rewrite message headers - self.forcecr = FALSE # Force LF -> CR/LF - self.stripcr = FALSE # Strip CR - self.pass8bits = FALSE # Force BODY=7BIT - self.mimedecode = FALSE # Undo MIME armoring - self.dropstatus = FALSE # Drop incoming Status lines + self.localnames = [self.remote,]# Local names + self.password = None # Password for mail account access + self.mailboxes = [] # Remote folders to retrieve from + self.smtphunt = [] # Hosts to forward to + self.fetchdomains = [] # Domains to fetch from + self.smtpaddress = None # Append this to MAIL FROM line + self.smtpname = None # Use this for RCPT TO + self.preconnect = None # Connection setup + self.postconnect = None # Connection wrapup + self.mda = None # Mail Delivery Agent + self.bsmtp = None # BSMTP output file + self.lmtp = FALSE # Use LMTP rather than SMTP? + self.antispam = "571 550 501" # Listener's spam-block code + self.keep = FALSE # Keep messages + self.flush = FALSE # Flush messages + self.fetchall = FALSE # Fetch old messages + self.rewrite = TRUE # Rewrite message headers + self.forcecr = FALSE # Force LF -> CR/LF + self.stripcr = FALSE # Strip CR + self.pass8bits = FALSE # Force BODY=7BIT + self.mimedecode = FALSE # Undo MIME armoring + self.dropstatus = FALSE # Drop incoming Status lines self.dropdelivered = FALSE # Drop incoming Delivered-To lines - self.idle = FALSE # IDLE after poll - self.limit = 0 # Message size limit - self.warnings = 0 # Size warning interval - self.fetchlimit = 0 # Max messages fetched per batch - self.batchlimit = 0 # Max message forwarded per batch - self.expunge = 0 # Interval between expunges (IMAP) - self.ssl = 0 # Enable Seccure Socket Layer - self.sslkey = None # SSL key filename - self.sslcert = None # SSL certificate filename - self.sslproto = None # Force SSL? - self.sslcertck = 0 # Enable strict SSL cert checking - self.sslcertpath = None # Path to trusted certificates - self.sslfingerprint = None # SSL key fingerprint to check - self.properties = None # Extension properties - User.typemap = ( - ('remote', 'String'), - # leave out mailboxes and localnames - ('password', 'String'), - # Leave out smtphunt - ('smtpaddress', 'String'), - ('smtpname', 'String'), - ('preconnect', 'String'), - ('postconnect', 'String'), - ('mda', 'String'), - ('bsmtp', 'String'), + self.idle = FALSE # IDLE after poll + self.limit = 0 # Message size limit + self.warnings = 0 # Size warning interval + self.fetchlimit = 0 # Max messages fetched per batch + self.batchlimit = 0 # Max message forwarded per batch + self.expunge = 0 # Interval between expunges (IMAP) + self.ssl = 0 # Enable Seccure Socket Layer + self.sslkey = None # SSL key filename + self.sslcert = None # SSL certificate filename + self.sslproto = None # Force SSL? + self.sslcertck = 0 # Enable strict SSL cert checking + self.sslcertpath = None # Path to trusted certificates + self.sslfingerprint = None # SSL key fingerprint to check + self.properties = None # Extension properties + User.typemap = ( + ('remote', 'String'), + # leave out mailboxes and localnames + ('password', 'String'), + # Leave out smtphunt, fetchdomains + ('smtpaddress', 'String'), + ('smtpname', 'String'), + ('preconnect', 'String'), + ('postconnect', 'String'), + ('mda', 'String'), + ('bsmtp', 'String'), ('lmtp', 'Boolean'), - ('antispam', 'String'), - ('keep', 'Boolean'), - ('flush', 'Boolean'), - ('fetchall', 'Boolean'), - ('rewrite', 'Boolean'), - ('forcecr', 'Boolean'), - ('stripcr', 'Boolean'), - ('pass8bits', 'Boolean'), - ('mimedecode', 'Boolean'), - ('dropstatus', 'Boolean'), + ('antispam', 'String'), + ('keep', 'Boolean'), + ('flush', 'Boolean'), + ('fetchall', 'Boolean'), + ('rewrite', 'Boolean'), + ('forcecr', 'Boolean'), + ('stripcr', 'Boolean'), + ('pass8bits', 'Boolean'), + ('mimedecode', 'Boolean'), + ('dropstatus', 'Boolean'), ('dropdelivered', 'Boolean'), - ('idle', 'Boolean'), - ('limit', 'Int'), - ('warnings', 'Int'), - ('fetchlimit', 'Int'), - ('batchlimit', 'Int'), - ('expunge', 'Int'), - ('ssl', 'Boolean'), - ('sslkey', 'String'), - ('sslcert', 'String'), - ('sslcertck', 'Boolean'), - ('sslcertpath', 'String'), - ('sslfingerprint', 'String'), + ('idle', 'Boolean'), + ('limit', 'Int'), + ('warnings', 'Int'), + ('fetchlimit', 'Int'), + ('batchlimit', 'Int'), + ('expunge', 'Int'), + ('ssl', 'Boolean'), + ('sslkey', 'String'), + ('sslcert', 'String'), + ('sslcertck', 'Boolean'), + ('sslcertpath', 'String'), + ('sslfingerprint', 'String'), ('properties', 'String')) def __repr__(self): - res = " " - res = res + "user " + `self.remote` + " there "; - if self.password: + res = " " + res = res + "user " + `self.remote` + " there "; + if self.password: res = res + "with password " + `self.password` + " " - if self.localnames: + if self.localnames: res = res + "is" for x in self.localnames: - res = res + " " + `x` + res = res + " " + `x` res = res + " here" - if (self.keep != UserDefaults.keep - or self.flush != UserDefaults.flush - or self.fetchall != UserDefaults.fetchall - or self.rewrite != UserDefaults.rewrite - or self.forcecr != UserDefaults.forcecr - or self.stripcr != UserDefaults.stripcr - or self.pass8bits != UserDefaults.pass8bits - or self.mimedecode != UserDefaults.mimedecode - or self.dropstatus != UserDefaults.dropstatus - or self.dropdelivered != UserDefaults.dropdelivered - or self.idle != UserDefaults.idle): - res = res + " options" - if self.keep != UserDefaults.keep: - res = res + flag2str(self.keep, 'keep') - if self.flush != UserDefaults.flush: - res = res + flag2str(self.flush, 'flush') - if self.fetchall != UserDefaults.fetchall: - res = res + flag2str(self.fetchall, 'fetchall') - if self.rewrite != UserDefaults.rewrite: - res = res + flag2str(self.rewrite, 'rewrite') - if self.forcecr != UserDefaults.forcecr: - res = res + flag2str(self.forcecr, 'forcecr') - if self.stripcr != UserDefaults.stripcr: - res = res + flag2str(self.stripcr, 'stripcr') - if self.pass8bits != UserDefaults.pass8bits: - res = res + flag2str(self.pass8bits, 'pass8bits') - if self.mimedecode != UserDefaults.mimedecode: - res = res + flag2str(self.mimedecode, 'mimedecode') - if self.dropstatus != UserDefaults.dropstatus: - res = res + flag2str(self.dropstatus, 'dropstatus') - if self.dropdelivered != UserDefaults.dropdelivered: - res = res + flag2str(self.dropdelivered, 'dropdelivered') - if self.idle != UserDefaults.idle: - res = res + flag2str(self.idle, 'idle') - if self.limit != UserDefaults.limit: - res = res + " limit " + `self.limit` - if self.warnings != UserDefaults.warnings: - res = res + " warnings " + `self.warnings` - if self.fetchlimit != UserDefaults.fetchlimit: - res = res + " fetchlimit " + `self.fetchlimit` - if self.batchlimit != UserDefaults.batchlimit: - res = res + " batchlimit " + `self.batchlimit` - if self.ssl and self.ssl != UserDefaults.ssl: - res = res + flag2str(self.ssl, 'ssl') - if self.sslkey and self.sslkey != UserDefaults.sslkey: - res = res + " sslkey " + `self.sslkey` - if self.sslcert and self.sslcert != UserDefaults.sslcert: - res = res + " sslcert " + `self.sslcert` + if (self.keep != UserDefaults.keep + or self.flush != UserDefaults.flush + or self.fetchall != UserDefaults.fetchall + or self.rewrite != UserDefaults.rewrite + or self.forcecr != UserDefaults.forcecr + or self.stripcr != UserDefaults.stripcr + or self.pass8bits != UserDefaults.pass8bits + or self.mimedecode != UserDefaults.mimedecode + or self.dropstatus != UserDefaults.dropstatus + or self.dropdelivered != UserDefaults.dropdelivered + or self.idle != UserDefaults.idle): + res = res + " options" + if self.keep != UserDefaults.keep: + res = res + flag2str(self.keep, 'keep') + if self.flush != UserDefaults.flush: + res = res + flag2str(self.flush, 'flush') + if self.fetchall != UserDefaults.fetchall: + res = res + flag2str(self.fetchall, 'fetchall') + if self.rewrite != UserDefaults.rewrite: + res = res + flag2str(self.rewrite, 'rewrite') + if self.forcecr != UserDefaults.forcecr: + res = res + flag2str(self.forcecr, 'forcecr') + if self.stripcr != UserDefaults.stripcr: + res = res + flag2str(self.stripcr, 'stripcr') + if self.pass8bits != UserDefaults.pass8bits: + res = res + flag2str(self.pass8bits, 'pass8bits') + if self.mimedecode != UserDefaults.mimedecode: + res = res + flag2str(self.mimedecode, 'mimedecode') + if self.dropstatus != UserDefaults.dropstatus: + res = res + flag2str(self.dropstatus, 'dropstatus') + if self.dropdelivered != UserDefaults.dropdelivered: + res = res + flag2str(self.dropdelivered, 'dropdelivered') + if self.idle != UserDefaults.idle: + res = res + flag2str(self.idle, 'idle') + if self.limit != UserDefaults.limit: + res = res + " limit " + `self.limit` + if self.warnings != UserDefaults.warnings: + res = res + " warnings " + `self.warnings` + if self.fetchlimit != UserDefaults.fetchlimit: + res = res + " fetchlimit " + `self.fetchlimit` + if self.batchlimit != UserDefaults.batchlimit: + res = res + " batchlimit " + `self.batchlimit` + if self.ssl and self.ssl != UserDefaults.ssl: + res = res + flag2str(self.ssl, 'ssl') + if self.sslkey and self.sslkey != UserDefaults.sslkey: + res = res + " sslkey " + `self.sslkey` + if self.sslcert and self.sslcert != UserDefaults.sslcert: + res = res + " sslcert " + `self.sslcert` if self.sslproto and self.sslproto != UserDefaults.sslproto: res = res + " sslproto " + `self.sslproto` if self.sslcertck and self.sslcertck != UserDefaults.sslcertck: @@ -350,8 +351,8 @@ class User: res = res + " sslcertpath " + `self.sslcertpath` if self.sslfingerprint and self.sslfingerprint != UserDefaults.sslfingerprint: res = res + " sslfingerprint " + `self.sslfingerprint` - if self.expunge != UserDefaults.expunge: - res = res + " expunge " + `self.expunge` + if self.expunge != UserDefaults.expunge: + res = res + " expunge " + `self.expunge` res = res + "\n" trimmed = self.smtphunt; if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost": @@ -363,34 +364,42 @@ class User: for x in trimmed: res = res + " " + x res = res + "\n" - if self.mailboxes: - res = res + " folder" - for x in self.mailboxes: - res = res + " " + x - res = res + "\n" + trimmed = self.fetchdomains; + if trimmed != [] and trimmed[len(trimmed) - 1] == hostname: + trimmed = trimmed[0:len(trimmed) - 1] + if trimmed != []: + res = res + " fetchdomains " + for x in trimmed: + res = res + " " + x + res = res + "\n" + if self.mailboxes: + res = res + " folder" + for x in self.mailboxes: + res = res + " " + x + res = res + "\n" for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'): if getattr(self, fld): res = res + " %s %s\n" % (fld, `getattr(self, fld)`) - if self.lmtp != UserDefaults.lmtp: - res = res + flag2str(self.lmtp, 'lmtp') + if self.lmtp != UserDefaults.lmtp: + res = res + flag2str(self.lmtp, 'lmtp') if self.antispam != UserDefaults.antispam: res = res + " antispam " + self.antispam + "\n" - return res; + return res; def __str__(self): - return "[User: " + repr(self) + "]" + return "[User: " + repr(self) + "]" # # Helper code # defaultports = {"auto":0, - "POP2":109, - "POP3":110, + "POP2":109, + "POP3":110, "APOP":110, "KPOP":1109, "IMAP":143, - "ETRN":25, + "ETRN":25, "ODMR":366} authlist = ("any", "password", "gssapi", "kerberos", "ssh") @@ -406,23 +415,23 @@ def flag2str(value, string): # make a string representation of a .fetchmailrc flag or negated flag str = "" if value != None: - str = str + (" ") - if value == FALSE: str = str + ("no ") - str = str + string; + str = str + (" ") + if value == FALSE: str = str + ("no ") + str = str + string; return str class LabeledEntry(Frame): # widget consisting of entry field with caption to left def bind(self, key, action): - self.E.bind(key, action) + self.E.bind(key, action) def focus_set(self): - self.E.focus_set() + self.E.focus_set() def __init__(self, Master, text, textvar, lwidth, ewidth=12): - Frame.__init__(self, Master) - self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'}) - self.E = Entry(self, {'textvar':textvar, 'width':ewidth}) - self.L.pack({'side':'left'}) - self.E.pack({'side':'left', 'expand':'1', 'fill':'x'}) + Frame.__init__(self, Master) + self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'}) + self.E = Entry(self, {'textvar':textvar, 'width':ewidth}) + self.L.pack({'side':'left'}) + self.E.pack({'side':'left', 'expand':'1', 'fill':'x'}) def ButtonBar(frame, legend, ref, alternatives, depth, command): # array of radio buttons, caption to left, picking from a string list @@ -430,20 +439,20 @@ def ButtonBar(frame, legend, ref, alternatives, depth, command): width = (len(alternatives)+1) / depth; Label(bar, text=legend).pack(side=LEFT) for column in range(width): - subframe = Frame(bar) - for row in range(depth): - ind = width * row + column + subframe = Frame(bar) + for row in range(depth): + ind = width * row + column if ind < len(alternatives): Radiobutton(subframe, - {'text':alternatives[ind], - 'variable':ref, - 'value':alternatives[ind], - 'command':command}).pack(side=TOP, anchor=W) + {'text':alternatives[ind], + 'variable':ref, + 'value':alternatives[ind], + 'command':command}).pack(side=TOP, anchor=W) else: # This is just a spacer Radiobutton(subframe, - {'text':" ",'state':DISABLED}).pack(side=TOP, anchor=W) - subframe.pack(side=LEFT) + {'text':" ",'state':DISABLED}).pack(side=TOP, anchor=W) + subframe.pack(side=LEFT) bar.pack(side=TOP); return bar @@ -463,7 +472,7 @@ def helpwin(helpdict): scroll.pack(side=RIGHT, fill=BOTH) helpwin.textwidget.insert(END, helpdict['text']); Button(helpwin, text='Done', - command=lambda x=helpwin: Widget.destroy(x), bd=2).pack() + command=lambda x=helpwin: Widget.destroy(x), bd=2).pack() textframe.pack(side=TOP) def make_icon_window(base, image): @@ -473,7 +482,7 @@ def make_icon_window(base, image): icon_window = Toplevel() Label(icon_window, image=icon_image, bg='black').pack() base.master.iconwindow(icon_window) - # Avoid TkInter brain death. PhotoImage objects go out of + # Avoid TkInter brain death. PhotoImage objects go out of # scope when the enclosing function returns. Therefore # we have to explicitly link them to something. base.keepalive.append(icon_image) @@ -483,51 +492,51 @@ def make_icon_window(base, image): class ListEdit(Frame): # edit a list of values (duplicates not allowed) with a supplied editor hook def __init__(self, newlegend, list, editor, deletor, master, helptxt): - self.editor = editor - self.deletor = deletor - self.list = list - - # Set up a widget to accept new elements - self.newval = StringVar(master) - newwin = LabeledEntry(master, newlegend, self.newval, '12') - newwin.bind('', self.handleNew) - newwin.bind('', self.handleNew) - newwin.pack(side=TOP, fill=X, anchor=E) - - # Edit the existing list - listframe = Frame(master) - scroll = Scrollbar(listframe) - self.listwidget = Listbox(listframe, height=0, selectmode='browse') + self.editor = editor + self.deletor = deletor + self.list = list + + # Set up a widget to accept new elements + self.newval = StringVar(master) + newwin = LabeledEntry(master, newlegend, self.newval, '12') + newwin.bind('', self.handleNew) + newwin.bind('', self.handleNew) + newwin.pack(side=TOP, fill=X, anchor=E) + + # Edit the existing list + listframe = Frame(master) + scroll = Scrollbar(listframe) + self.listwidget = Listbox(listframe, height=0, selectmode='browse') if self.list: for x in self.list: self.listwidget.insert(END, x) - listframe.pack(side=TOP, expand=YES, fill=BOTH) - self.listwidget.config(yscrollcommand=scroll.set) - self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH) - scroll.config(command=self.listwidget.yview) - scroll.pack(side=RIGHT, fill=BOTH) - self.listwidget.config(selectmode=SINGLE, setgrid=TRUE) - self.listwidget.bind('', self.handleList); - self.listwidget.bind('', self.handleList); - - bf = Frame(master); - if self.editor: - Button(bf, text='Edit', command=self.editItem).pack(side=LEFT) - Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT) - if helptxt: - self.helptxt = helptxt - Button(bf, text='Help', fg='blue', - command=self.help).pack(side=RIGHT) - bf.pack(fill=X) + listframe.pack(side=TOP, expand=YES, fill=BOTH) + self.listwidget.config(yscrollcommand=scroll.set) + self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH) + scroll.config(command=self.listwidget.yview) + scroll.pack(side=RIGHT, fill=BOTH) + self.listwidget.config(selectmode=SINGLE, setgrid=TRUE) + self.listwidget.bind('', self.handleList); + self.listwidget.bind('', self.handleList); + + bf = Frame(master); + if self.editor: + Button(bf, text='Edit', command=self.editItem).pack(side=LEFT) + Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT) + if helptxt: + self.helptxt = helptxt + Button(bf, text='Help', fg='blue', + command=self.help).pack(side=RIGHT) + bf.pack(fill=X) def help(self): - helpwin(self.helptxt) + helpwin(self.helptxt) def handleList(self, event): self.editItem(); def handleNew(self, event): - item = self.newval.get() + item = self.newval.get() if item: entire = self.listwidget.get(0, self.listwidget.index('end')); if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))): @@ -538,21 +547,21 @@ class ListEdit(Frame): self.newval.set('') def editItem(self): - select = self.listwidget.curselection() - if not select: - helpwin(listboxhelp) - else: - index = select[0] - if index and self.editor: - label = self.listwidget.get(index); + select = self.listwidget.curselection() + if not select: + helpwin(listboxhelp) + else: + index = select[0] + if index and self.editor: + label = self.listwidget.get(index); if self.editor: apply(self.editor, (label,)) def deleteItem(self): - select = self.listwidget.curselection() - if not select: - helpwin(listboxhelp) - else: + select = self.listwidget.curselection() + if not select: + helpwin(listboxhelp) + else: index = string.atoi(select[0]) label = self.listwidget.get(index); self.listwidget.delete(index) @@ -563,11 +572,11 @@ class ListEdit(Frame): def ConfirmQuit(frame, context): ans = Dialog(frame, - title = 'Quit?', - text = 'Really quit ' + context + ' without saving?', - bitmap = 'question', - strings = ('Yes', 'No'), - default = 1) + title = 'Quit?', + text = 'Really quit ' + context + ' without saving?', + bitmap = 'question', + strings = ('Yes', 'No'), + default = 1) return ans.num == 0 def dispose_window(master, legend, help, savelegend='OK'): @@ -585,20 +594,20 @@ def dispose_window(master, legend, help, savelegend='OK'): class MyWidget: # Common methods for Tkinter widgets -- deals with Tkinter declaration def post(self, widgetclass, field): - for x in widgetclass.typemap: - if x[1] == 'Boolean': - setattr(self, x[0], BooleanVar(self)) - elif x[1] == 'String': - setattr(self, x[0], StringVar(self)) - elif x[1] == 'Int': - setattr(self, x[0], IntVar(self)) - source = getattr(getattr(self, field), x[0]) + for x in widgetclass.typemap: + if x[1] == 'Boolean': + setattr(self, x[0], BooleanVar(self)) + elif x[1] == 'String': + setattr(self, x[0], StringVar(self)) + elif x[1] == 'Int': + setattr(self, x[0], IntVar(self)) + source = getattr(getattr(self, field), x[0]) if source: getattr(self, x[0]).set(source) def fetch(self, widgetclass, field): - for x in widgetclass.typemap: - setattr(getattr(self, field), x[0], getattr(self, x[0]).get()) + for x in widgetclass.typemap: + setattr(getattr(self, field), x[0], getattr(self, x[0]).get()) # # First, code to set the global fetchmail run controls. @@ -667,13 +676,13 @@ Postmaster root. If that user is root, fetchmail sends to `postmaster'. Bounces to sender? - If this option is on (the default) error mail goes to the sender. + If this option is on (the default) error mail goes to the sender. Otherwise it goes to the postmaster. Send spam bounces? - If this option is on, spam bounces are sent to the sender or - postmaster (depending on the "Bounces to sender?" option. Otherwise, - spam bounces are not sent (the default). + If this option is on, spam bounces are sent to the sender or + postmaster (depending on the "Bounces to sender?" option. Otherwise, + spam bounces are not sent (the default). Invisible If false (the default) fetchmail generates a Received line into @@ -700,7 +709,7 @@ This will take you to a site configuration dialogue. class ConfigurationEdit(Frame, MyWidget): def __init__(self, configuration, outfile, master, onexit): self.subwidgets = {} - self.configuration = configuration + self.configuration = configuration self.outfile = outfile self.container = master self.onexit = onexit @@ -709,7 +718,7 @@ class ConfigurationEdit(Frame, MyWidget): } def server_edit(self, sitename): - self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel()) + self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel()) def server_delete(self, sitename): try: @@ -717,28 +726,28 @@ class ConfigurationEdit(Frame, MyWidget): user.destruct() del self.configuration[sitename] except: - pass + pass def edit(self, mode): self.mode = mode - Frame.__init__(self, self.container) - self.master.title('fetchmail ' + self.mode + ' configurator'); - self.master.iconname('fetchmail ' + self.mode + ' configurator'); + Frame.__init__(self, self.container) + self.master.title('fetchmail ' + self.mode + ' configurator'); + self.master.iconname('fetchmail ' + self.mode + ' configurator'); self.master.protocol('WM_DELETE_WINDOW', self.nosave) - self.keepalive = [] # Use this to anchor the PhotoImage object + self.keepalive = [] # Use this to anchor the PhotoImage object make_icon_window(self, fetchmail_icon) - Pack.config(self) + Pack.config(self) self.post(Configuration, 'configuration') - dispose_window(self, + dispose_window(self, 'Configurator ' + self.mode + ' Controls', ConfigurationEdit.mode_to_help[self.mode], 'Save') - gf = Frame(self, relief=RAISED, bd = 5) - Label(gf, - text='Fetchmail Run Controls', - bd=2).pack(side=TOP, pady=10) + gf = Frame(self, relief=RAISED, bd = 5) + Label(gf, + text='Fetchmail Run Controls', + bd=2).pack(side=TOP, pady=10) df = Frame(gf) @@ -758,48 +767,48 @@ class ConfigurationEdit(Frame, MyWidget): if self.mode != 'novice': pf = Frame(gf) Checkbutton(pf, - {'text':'Bounces to sender?', - 'variable':self.bouncemail, - 'relief':GROOVE}).pack(side=LEFT, anchor=W) + {'text':'Bounces to sender?', + 'variable':self.bouncemail, + 'relief':GROOVE}).pack(side=LEFT, anchor=W) pf.pack(fill=X) sb = Frame(gf) Checkbutton(sb, - {'text':'send spam bounces?', - 'variable':self.spambounce, - 'relief':GROOVE}).pack(side=LEFT, anchor=W) + {'text':'send spam bounces?', + 'variable':self.spambounce, + 'relief':GROOVE}).pack(side=LEFT, anchor=W) sb.pack(fill=X) sf = Frame(gf) Checkbutton(sf, - {'text':'Log to syslog?', - 'variable':self.syslog, - 'relief':GROOVE}).pack(side=LEFT, anchor=W) + {'text':'Log to syslog?', + 'variable':self.syslog, + 'relief':GROOVE}).pack(side=LEFT, anchor=W) log = LabeledEntry(sf, ' Logfile:', self.logfile, '14') log.pack(side=RIGHT, anchor=E) sf.pack(fill=X) Checkbutton(gf, - {'text':'Invisible mode?', - 'variable':self.invisible, + {'text':'Invisible mode?', + 'variable':self.invisible, 'relief':GROOVE}).pack(side=LEFT, anchor=W) # Set the idfile log = LabeledEntry(gf, ' Idfile:', self.idfile, '14') log.pack(side=RIGHT, anchor=E) - gf.pack(fill=X) + gf.pack(fill=X) # Expert mode allows us to edit multiple sites - lf = Frame(self, relief=RAISED, bd=5) - Label(lf, - text='Remote Mail Server Configurations', - bd=2).pack(side=TOP, pady=10) - ListEdit('New Server:', - map(lambda x: x.pollname, self.configuration.servers), - lambda site, self=self: self.server_edit(site), - lambda site, self=self: self.server_delete(site), + lf = Frame(self, relief=RAISED, bd=5) + Label(lf, + text='Remote Mail Server Configurations', + bd=2).pack(side=TOP, pady=10) + ListEdit('New Server:', + map(lambda x: x.pollname, self.configuration.servers), + lambda site, self=self: self.server_edit(site), + lambda site, self=self: self.server_delete(site), lf, remotehelp) - lf.pack(fill=X) + lf.pack(fill=X) def destruct(self): for sitename in self.subwidgets.keys(): @@ -808,8 +817,8 @@ class ConfigurationEdit(Frame, MyWidget): self.onexit() def nosave(self): - if ConfirmQuit(self, self.mode + " configuration editor"): - self.destruct() + if ConfirmQuit(self, self.mode + " configuration editor"): + self.destruct() def save(self): for sitename in self.subwidgets.keys(): @@ -819,11 +828,11 @@ class ConfigurationEdit(Frame, MyWidget): if not self.outfile: fm = sys.stdout elif not os.path.isfile(self.outfile) or Dialog(self, - title = 'Overwrite existing run control file?', - text = 'Really overwrite existing run control file?', - bitmap = 'question', - strings = ('Yes', 'No'), - default = 1).num == 0: + title = 'Overwrite existing run control file?', + text = 'Really overwrite existing run control file?', + bitmap = 'question', + strings = ('Yes', 'No'), + default = 1).num == 0: fm = open(self.outfile, 'w') if fm: fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time())) @@ -978,30 +987,30 @@ user's options on that site. class ServerEdit(Frame, MyWidget): def __init__(self, host, parent): self.parent = parent - self.server = None + self.server = None self.subwidgets = {} - for site in parent.configuration.servers: - if site.pollname == host: - self.server = site - if (self.server == None): - self.server = Server() - self.server.pollname = host - self.server.via = None - parent.configuration.servers.append(self.server) + for site in parent.configuration.servers: + if site.pollname == host: + self.server = site + if (self.server == None): + self.server = Server() + self.server.pollname = host + self.server.via = None + parent.configuration.servers.append(self.server) def edit(self, mode, master=None): - Frame.__init__(self, master) - Pack.config(self) - self.master.title('Fetchmail host ' + self.server.pollname); - self.master.iconname('Fetchmail host ' + self.server.pollname); - self.post(Server, 'server') - self.makeWidgets(self.server.pollname, mode) - self.keepalive = [] # Use this to anchor the PhotoImage object + Frame.__init__(self, master) + Pack.config(self) + self.master.title('Fetchmail host ' + self.server.pollname); + self.master.iconname('Fetchmail host ' + self.server.pollname); + self.post(Server, 'server') + self.makeWidgets(self.server.pollname, mode) + self.keepalive = [] # Use this to anchor the PhotoImage object make_icon_window(self, fetchmail_icon) -# self.grab_set() -# self.focus_set() -# self.wait_window() - return self +# self.grab_set() +# self.focus_set() +# self.wait_window() + return self def destruct(self): for username in self.subwidgets.keys(): @@ -1010,17 +1019,17 @@ class ServerEdit(Frame, MyWidget): Widget.destroy(self.master) def nosave(self): - if ConfirmQuit(self, 'server option editing'): - self.destruct() + if ConfirmQuit(self, 'server option editing'): + self.destruct() def save(self): - self.fetch(Server, 'server') + self.fetch(Server, 'server') for username in self.subwidgets.keys(): self.subwidgets[username].save() - self.destruct() + self.destruct() def refreshPort(self): - proto = self.protocol.get() + proto = self.protocol.get() # We used to only reset the port if it had a default (zero) value. # This turns out to be a bad idea especially in Novice mode -- if # you set POP3 and then set IMAP, the port invisibly remained 110. @@ -1028,7 +1037,7 @@ class ServerEdit(Frame, MyWidget): # a custom port number you should be in expert mode and playing # close enough attention to notice this... self.port.set(defaultports[proto]) - if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED + if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED def user_edit(self, username, mode): self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel()) @@ -1039,23 +1048,23 @@ class ServerEdit(Frame, MyWidget): del self.server[username] def makeWidgets(self, host, mode): - topwin = dispose_window(self, "Server options for querying " + host, serverhelp) + topwin = dispose_window(self, "Server options for querying " + host, serverhelp) - leftwin = Frame(self); - leftwidth = '25'; + leftwin = Frame(self); + leftwidth = '25'; if mode != 'novice': ctlwin = Frame(leftwin, relief=RAISED, bd=5) Label(ctlwin, text="Run Controls").pack(side=TOP) Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP) LabeledEntry(ctlwin, 'True name of ' + host + ':', - self.via, leftwidth).pack(side=TOP, fill=X) + self.via, leftwidth).pack(side=TOP, fill=X) LabeledEntry(ctlwin, 'Cycles to skip between polls:', - self.interval, leftwidth).pack(side=TOP, fill=X) + self.interval, leftwidth).pack(side=TOP, fill=X) LabeledEntry(ctlwin, 'Server timeout (seconds):', - self.timeout, leftwidth).pack(side=TOP, fill=X) + self.timeout, leftwidth).pack(side=TOP, fill=X) Button(ctlwin, text='Help', fg='blue', - command=lambda: helpwin(controlhelp)).pack(side=RIGHT) + command=lambda: helpwin(controlhelp)).pack(side=RIGHT) ctlwin.pack(fill=X) # Compute the available protocols from the compile-time options @@ -1073,34 +1082,34 @@ class ServerEdit(Frame, MyWidget): if 'odmr' in feature_options: protolist.append("ODMR") - protwin = Frame(leftwin, relief=RAISED, bd=5) - Label(protwin, text="Protocol").pack(side=TOP) - ButtonBar(protwin, '', + protwin = Frame(leftwin, relief=RAISED, bd=5) + Label(protwin, text="Protocol").pack(side=TOP) + ButtonBar(protwin, '', self.protocol, protolist, 2, self.refreshPort) if mode != 'novice': LabeledEntry(protwin, 'On server TCP/IP port:', - self.port, leftwidth).pack(side=TOP, fill=X) + self.port, leftwidth).pack(side=TOP, fill=X) self.refreshPort() Checkbutton(protwin, - text="POP3: track `seen' with client-side UIDLs?", - variable=self.uidl).pack(side=TOP) - Button(protwin, text='Probe for supported protocols', fg='blue', - command=self.autoprobe).pack(side=LEFT) - Button(protwin, text='Help', fg='blue', - command=lambda: helpwin(protohelp)).pack(side=RIGHT) - protwin.pack(fill=X) - - userwin = Frame(leftwin, relief=RAISED, bd=5) - Label(userwin, text="User entries for " + host).pack(side=TOP) - ListEdit("New user: ", + text="POP3: track `seen' with client-side UIDLs?", + variable=self.uidl).pack(side=TOP) + Button(protwin, text='Probe for supported protocols', fg='blue', + command=self.autoprobe).pack(side=LEFT) + Button(protwin, text='Help', fg='blue', + command=lambda: helpwin(protohelp)).pack(side=RIGHT) + protwin.pack(fill=X) + + userwin = Frame(leftwin, relief=RAISED, bd=5) + Label(userwin, text="User entries for " + host).pack(side=TOP) + ListEdit("New user: ", map(lambda x: x.remote, self.server.users), lambda u, m=mode, s=self: s.user_edit(u, m), lambda u, s=self: s.user_delete(u), userwin, suserhelp) - userwin.pack(fill=X) + userwin.pack(fill=X) - leftwin.pack(side=LEFT, anchor=N, fill=X); + leftwin.pack(side=LEFT, anchor=N, fill=X); if mode != 'novice': rightwin = Frame(self); @@ -1108,37 +1117,37 @@ class ServerEdit(Frame, MyWidget): mdropwin = Frame(rightwin, relief=RAISED, bd=5) Label(mdropwin, text="Multidrop options").pack(side=TOP) LabeledEntry(mdropwin, 'Envelope address header:', - self.envelope, '22').pack(side=TOP, fill=X) + self.envelope, '22').pack(side=TOP, fill=X) LabeledEntry(mdropwin, 'Envelope headers to skip:', - self.envskip, '22').pack(side=TOP, fill=X) + self.envskip, '22').pack(side=TOP, fill=X) LabeledEntry(mdropwin, 'Name prefix to strip:', - self.qvirtual, '22').pack(side=TOP, fill=X) + self.qvirtual, '22').pack(side=TOP, fill=X) Checkbutton(mdropwin, text="Enable multidrop DNS lookup?", - variable=self.dns).pack(side=TOP) + variable=self.dns).pack(side=TOP) Label(mdropwin, text="DNS aliases").pack(side=TOP) ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None) Label(mdropwin, text="Domains to be considered local").pack(side=TOP) ListEdit("New domain: ", - self.server.localdomains, None, None, mdropwin, multihelp) + self.server.localdomains, None, None, mdropwin, multihelp) mdropwin.pack(fill=X) if os_type in ('linux', 'freebsd') or 'netsec' in feature_options: secwin = Frame(rightwin, relief=RAISED, bd=5) Label(secwin, text="Security").pack(side=TOP) # Don't actually let users set this. KPOP sets it implicitly - # ButtonBar(secwin, 'Authorization mode:', - # self.auth, authlist, 1, None).pack(side=TOP) + # ButtonBar(secwin, 'Authorization mode:', + # self.auth, authlist, 1, None).pack(side=TOP) if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers: LabeledEntry(secwin, 'IP range to check before poll:', - self.interface, leftwidth).pack(side=TOP, fill=X) + self.interface, leftwidth).pack(side=TOP, fill=X) if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers: LabeledEntry(secwin, 'Interface to monitor:', - self.monitor, leftwidth).pack(side=TOP, fill=X) + self.monitor, leftwidth).pack(side=TOP, fill=X) if 'netsec' in feature_options or 'netsec' in dictmembers: LabeledEntry(secwin, 'IPV6 security options:', - self.netsec, leftwidth).pack(side=TOP, fill=X) - # Someday this should handle Kerberos 5 too - if 'kerberos' in feature_options: + self.netsec, leftwidth).pack(side=TOP, fill=X) + # Someday this should handle Kerberos 5 too + if 'kerberos' in feature_options: LabeledEntry(secwin, 'Principal:', self.principal, '12').pack(side=TOP, fill=X) Button(secwin, text='Help', fg='blue', @@ -1194,7 +1203,7 @@ switch --enable-POP2. ### POP3 servers start here - if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0: + if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0: warnings = warnings + """ This appears to be an old version of the UC Davis POP server. These are dangerously unreliable (among other problems, they may drop your mailbox @@ -1222,7 +1231,7 @@ FAQ includes pointers to good ones. # # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko> # - if string.find(greetline, "Cubic Circle") > 0: + if string.find(greetline, "Cubic Circle") > 0: warnings = warnings + """ I see your server is running cucipop. Better make sure the server box isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc() @@ -1233,7 +1242,7 @@ Also, some versions of cucipop don't assert an exclusive lock on your mailbox when it's being queried. This means that if you have more than one fetchmail query running against the same mailbox, bad things can happen. """ - if string.find(greetline, "David POP3 Server") > 0: + if string.find(greetline, "David POP3 Server") > 0: warnings = warnings + """ This POP3 server is badly broken. You should get rid of it -- and the brain-dead Microsoft operating system it rode in on. @@ -1242,7 +1251,7 @@ brain-dead Microsoft operating system it rode in on. # The greeting line on the server known to be buggy is: # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01) # - if string.find(greetline, "FTGate") > 0: + if string.find(greetline, "FTGate") > 0: warnings = warnings + """ This POP server has a weird bug; it says OK twice in response to TOP. Its response to RETR is normal, so use the `fetchall' option. @@ -1265,7 +1274,7 @@ Beware! The UIDL support in this thing is known to be completely broken, and other things probably are too. """ - if string.find(greetline, "POP-Max") > 0: + if string.find(greetline, "POP-Max") > 0: warnings = warnings + """ The Mail Max POP3 server screws up on mail with attachments. It reports the message size with attachments included, but doesn't @@ -1280,7 +1289,7 @@ Some server that uses this greeting line has been observed to choke on TOP %d 99999999. Use the fetchall option. if necessary, to force RETR. """ - if string.find(greetline, "QPOP") > 0: + if string.find(greetline, "QPOP") > 0: warnings = warnings + """ This appears to be a version of Eudora qpopper. That's good. Fetchmail knows all about qpopper. However, be aware that the 2.53 version of @@ -1482,30 +1491,30 @@ of sending it to your local system. class UserEdit(Frame, MyWidget): def __init__(self, username, parent): self.parent = parent - self.user = None - for user in parent.server.users: - if user.remote == username: - self.user = user - if self.user == None: - self.user = User() - self.user.remote = username - self.user.localnames = [username] - parent.server.users.append(self.user) + self.user = None + for user in parent.server.users: + if user.remote == username: + self.user = user + if self.user == None: + self.user = User() + self.user.remote = username + self.user.localnames = [username] + parent.server.users.append(self.user) def edit(self, mode, master=None): - Frame.__init__(self, master) - Pack.config(self) - self.master.title('Fetchmail user ' + self.user.remote + Frame.__init__(self, master) + Pack.config(self) + self.master.title('Fetchmail user ' + self.user.remote + ' querying ' + self.parent.server.pollname); - self.master.iconname('Fetchmail user ' + self.user.remote); - self.post(User, 'user') - self.makeWidgets(mode, self.parent.server.pollname) - self.keepalive = [] # Use this to anchor the PhotoImage object + self.master.iconname('Fetchmail user ' + self.user.remote); + self.post(User, 'user') + self.makeWidgets(mode, self.parent.server.pollname) + self.keepalive = [] # Use this to anchor the PhotoImage object make_icon_window(self, fetchmail_icon) -# self.grab_set() -# self.focus_set() -# self.wait_window() - return self +# self.grab_set() +# self.focus_set() +# self.wait_window() + return self def destruct(self): # Yes, this test can fail -- if you delete the parent window. @@ -1514,28 +1523,28 @@ class UserEdit(Frame, MyWidget): Widget.destroy(self.master) def nosave(self): - if ConfirmQuit(self, 'user option editing'): + if ConfirmQuit(self, 'user option editing'): self.destruct() def save(self): - ok = 0 - for x in self.user.localnames: ok = ok + (string.find(x, '@') != -1) - if ok == 0 or Dialog(self, - title = "Really accept an embedded '@' ?", + ok = 0 + for x in self.user.localnames: ok = ok + (string.find(x, '@') != -1) + if ok == 0 or Dialog(self, + title = "Really accept an embedded '@' ?", text = "Local names with an embedded '@', such as in foo@bar " - "might result in your mail being sent to foo@bar.com " - "instead of your local system.\n Are you sure you want " - "a local user name with an '@' in it?", + "might result in your mail being sent to foo@bar.com " + "instead of your local system.\n Are you sure you want " + "a local user name with an '@' in it?", bitmap = 'question', strings = ('Yes', 'No'), default = 1).num == 0: - self.fetch(User, 'user') - self.destruct() + self.fetch(User, 'user') + self.destruct() def makeWidgets(self, mode, servername): - dispose_window(self, - "User options for " + self.user.remote + " querying " + servername, - userhelp) + dispose_window(self, + "User options for " + self.user.remote + " querying " + servername, + userhelp) if mode != 'novice': leftwin = Frame(self); @@ -1545,7 +1554,7 @@ class UserEdit(Frame, MyWidget): secwin = Frame(leftwin, relief=RAISED, bd=5) Label(secwin, text="Authentication").pack(side=TOP) LabeledEntry(secwin, 'Password:', - self.password, '12').pack(side=TOP, fill=X) + self.password, '12').pack(side=TOP, fill=X) secwin.pack(fill=X, anchor=N) if 'ssl' in feature_options or 'ssl' in dictmembers: @@ -1553,15 +1562,15 @@ class UserEdit(Frame, MyWidget): Checkbutton(sslwin, text="Use SSL?", variable=self.ssl).pack(side=TOP, fill=X) LabeledEntry(sslwin, 'SSL key:', - self.sslkey, '14').pack(side=TOP, fill=X) + self.sslkey, '14').pack(side=TOP, fill=X) LabeledEntry(sslwin, 'SSL certificate:', - self.sslcert, '14').pack(side=TOP, fill=X) + self.sslcert, '14').pack(side=TOP, fill=X) Checkbutton(sslwin, text="Check server SSL certificate?", variable=self.sslcertck).pack(side=TOP, fill=X) LabeledEntry(sslwin, 'SSL trusted certificate directory:', - self.sslcertpath, '14').pack(side=TOP, fill=X) + self.sslcertpath, '14').pack(side=TOP, fill=X) LabeledEntry(sslwin, 'SSL key fingerprint:', - self.sslfingerprint, '14').pack(side=TOP, fill=X) + self.sslfingerprint, '14').pack(side=TOP, fill=X) sslwin.pack(fill=X, anchor=N) names = Frame(leftwin, relief=RAISED, bd=5) @@ -1576,22 +1585,25 @@ class UserEdit(Frame, MyWidget): Label(targwin, text="Listeners to forward to").pack(side=TOP) ListEdit("New listener:", self.user.smtphunt, None, None, targwin, None) + Label(targwin, text="Domains to fetch from (ODMR/ETRN only)").pack(side=TOP) + ListEdit("Domains:", + self.user.fetchdomains, None, None, targwin, None) LabeledEntry(targwin, 'Append to MAIL FROM line:', - self.smtpaddress, '26').pack(side=TOP, fill=X) + self.smtpaddress, '26').pack(side=TOP, fill=X) LabeledEntry(targwin, 'Set RCPT To address:', - self.smtpname, '26').pack(side=TOP, fill=X) + self.smtpname, '26').pack(side=TOP, fill=X) LabeledEntry(targwin, 'Connection setup command:', - self.preconnect, '26').pack(side=TOP, fill=X) + self.preconnect, '26').pack(side=TOP, fill=X) LabeledEntry(targwin, 'Connection wrapup command:', - self.postconnect, '26').pack(side=TOP, fill=X) + self.postconnect, '26').pack(side=TOP, fill=X) LabeledEntry(targwin, 'Local delivery agent:', - self.mda, '26').pack(side=TOP, fill=X) + self.mda, '26').pack(side=TOP, fill=X) LabeledEntry(targwin, 'BSMTP output file:', - self.bsmtp, '26').pack(side=TOP, fill=X) + self.bsmtp, '26').pack(side=TOP, fill=X) LabeledEntry(targwin, 'Listener spam-block codes:', - self.antispam, '26').pack(side=TOP, fill=X) + self.antispam, '26').pack(side=TOP, fill=X) LabeledEntry(targwin, 'Pass-through properties:', - self.properties, '26').pack(side=TOP, fill=X) + self.properties, '26').pack(side=TOP, fill=X) Checkbutton(targwin, text="Use LMTP?", variable=self.lmtp).pack(side=TOP, fill=X) targwin.pack(fill=X, anchor=N) @@ -1602,47 +1614,47 @@ class UserEdit(Frame, MyWidget): else: rightwin = self - optwin = Frame(rightwin, relief=RAISED, bd=5) - Label(optwin, text="Processing Options").pack(side=TOP) - Checkbutton(optwin, text="Suppress deletion of messages after reading", - variable=self.keep).pack(side=TOP, anchor=W) - Checkbutton(optwin, text="Fetch old messages as well as new", - variable=self.fetchall).pack(side=TOP, anchor=W) + optwin = Frame(rightwin, relief=RAISED, bd=5) + Label(optwin, text="Processing Options").pack(side=TOP) + Checkbutton(optwin, text="Suppress deletion of messages after reading", + variable=self.keep).pack(side=TOP, anchor=W) + Checkbutton(optwin, text="Fetch old messages as well as new", + variable=self.fetchall).pack(side=TOP, anchor=W) if mode != 'novice': Checkbutton(optwin, text="Flush seen messages before retrieval", - variable=self.flush).pack(side=TOP, anchor=W) + variable=self.flush).pack(side=TOP, anchor=W) Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply", - variable=self.rewrite).pack(side=TOP, anchor=W) + variable=self.rewrite).pack(side=TOP, anchor=W) Checkbutton(optwin, text="Force CR/LF at end of each line", - variable=self.forcecr).pack(side=TOP, anchor=W) + variable=self.forcecr).pack(side=TOP, anchor=W) Checkbutton(optwin, text="Strip CR from end of each line", - variable=self.stripcr).pack(side=TOP, anchor=W) + variable=self.stripcr).pack(side=TOP, anchor=W) Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT", - variable=self.pass8bits).pack(side=TOP, anchor=W) + variable=self.pass8bits).pack(side=TOP, anchor=W) Checkbutton(optwin, text="Undo MIME armoring on header and body", - variable=self.mimedecode).pack(side=TOP, anchor=W) + variable=self.mimedecode).pack(side=TOP, anchor=W) Checkbutton(optwin, text="Drop Status lines from forwarded messages", - variable=self.dropstatus).pack(side=TOP, anchor=W) + variable=self.dropstatus).pack(side=TOP, anchor=W) Checkbutton(optwin, text="Drop Delivered-To lines from forwarded messages", - variable=self.dropdelivered).pack(side=TOP, anchor=W) - optwin.pack(fill=X) + variable=self.dropdelivered).pack(side=TOP, anchor=W) + optwin.pack(fill=X) if mode != 'novice': limwin = Frame(rightwin, relief=RAISED, bd=5) Label(limwin, text="Resource Limits").pack(side=TOP) LabeledEntry(limwin, 'Message size limit:', - self.limit, '30').pack(side=TOP, fill=X) + self.limit, '30').pack(side=TOP, fill=X) LabeledEntry(limwin, 'Size warning interval:', - self.warnings, '30').pack(side=TOP, fill=X) + self.warnings, '30').pack(side=TOP, fill=X) LabeledEntry(limwin, 'Max messages to fetch per poll:', - self.fetchlimit, '30').pack(side=TOP, fill=X) + self.fetchlimit, '30').pack(side=TOP, fill=X) LabeledEntry(limwin, 'Max messages to forward per poll:', - self.batchlimit, '30').pack(side=TOP, fill=X) + self.batchlimit, '30').pack(side=TOP, fill=X) if self.parent.server.protocol not in ('ETRN', 'ODMR'): LabeledEntry(limwin, 'Interval between expunges:', self.expunge, '30').pack(side=TOP, fill=X) Checkbutton(limwin, text="Idle after each poll (IMAP only)", - variable=self.idle).pack(side=TOP, anchor=W) + variable=self.idle).pack(side=TOP, anchor=W) limwin.pack(fill=X) if self.parent.server.protocol == 'IMAP': @@ -1665,45 +1677,45 @@ class UserEdit(Frame, MyWidget): class Configurator(Frame): def __init__(self, outfile, master, onexit, parent): - Frame.__init__(self, master) + Frame.__init__(self, master) self.outfile = outfile self.onexit = onexit self.parent = parent - self.master.title('fetchmail configurator'); - self.master.iconname('fetchmail configurator'); - Pack.config(self) - self.keepalive = [] # Use this to anchor the PhotoImage object + self.master.title('fetchmail configurator'); + self.master.iconname('fetchmail configurator'); + Pack.config(self) + self.keepalive = [] # Use this to anchor the PhotoImage object make_icon_window(self, fetchmail_icon) - Message(self, text=""" + Message(self, text=""" Use `Novice Configuration' for basic fetchmail setup; with this, you can easily set up a single-drop connection to one remote mail server. """, width=600).pack(side=TOP) - Button(self, text='Novice Configuration', - fg='blue', command=self.novice).pack() + Button(self, text='Novice Configuration', + fg='blue', command=self.novice).pack() - Message(self, text=""" + Message(self, text=""" Use `Expert Configuration' for advanced fetchmail setup, including multiple-site or multidrop connections. """, width=600).pack(side=TOP) - Button(self, text='Expert Configuration', - fg='blue', command=self.expert).pack() + Button(self, text='Expert Configuration', + fg='blue', command=self.expert).pack() - Message(self, text=""" + Message(self, text=""" Or you can just select `Quit' to leave the configurator now and return to the main panel. """, width=600).pack(side=TOP) - Button(self, text='Quit', fg='blue', command=self.leave).pack() + Button(self, text='Quit', fg='blue', command=self.leave).pack() master.protocol("WM_DELETE_WINDOW", self.leave) def novice(self): - self.master.destroy() - ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice') + self.master.destroy() + ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice') def expert(self): - self.master.destroy() - ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert') + self.master.destroy() + ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert') def leave(self): self.master.destroy() @@ -1713,31 +1725,31 @@ return to the main panel. class RunWindow(Frame): def __init__(self, command, master, parent): - Frame.__init__(self, master) + Frame.__init__(self, master) self.master = master - self.master.title('fetchmail run window'); - self.master.iconname('fetchmail run window'); - Pack.config(self) - Label(self, - text="Running "+command, - bd=2).pack(side=TOP, pady=10) - self.keepalive = [] # Use this to anchor the PhotoImage object + self.master.title('fetchmail run window'); + self.master.iconname('fetchmail run window'); + Pack.config(self) + Label(self, + text="Running "+command, + bd=2).pack(side=TOP, pady=10) + self.keepalive = [] # Use this to anchor the PhotoImage object make_icon_window(self, fetchmail_icon) # This is a scrolling text window - textframe = Frame(self) - scroll = Scrollbar(textframe) - self.textwidget = Text(textframe, setgrid=TRUE) - textframe.pack(side=TOP, expand=YES, fill=BOTH) - self.textwidget.config(yscrollcommand=scroll.set) - self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH) - scroll.config(command=self.textwidget.yview) - scroll.pack(side=RIGHT, fill=BOTH) + textframe = Frame(self) + scroll = Scrollbar(textframe) + self.textwidget = Text(textframe, setgrid=TRUE) + textframe.pack(side=TOP, expand=YES, fill=BOTH) + self.textwidget.config(yscrollcommand=scroll.set) + self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH) + scroll.config(command=self.textwidget.yview) + scroll.pack(side=RIGHT, fill=BOTH) textframe.pack(side=TOP) - Button(self, text='Quit', fg='blue', command=self.leave).pack() + Button(self, text='Quit', fg='blue', command=self.leave).pack() - self.update() # Draw widget before executing fetchmail + self.update() # Draw widget before executing fetchmail child_stdout = os.popen(command + " 2>&1", "r") while 1: @@ -1755,15 +1767,15 @@ class RunWindow(Frame): class MainWindow(Frame): def __init__(self, outfile, master=None): - Frame.__init__(self, master) + Frame.__init__(self, master) self.outfile = outfile - self.master.title('fetchmail launcher'); - self.master.iconname('fetchmail launcher'); - Pack.config(self) - Label(self, - text='Fetchmailconf ' + version, - bd=2).pack(side=TOP, pady=10) - self.keepalive = [] # Use this to anchor the PhotoImage object + self.master.title('fetchmail launcher'); + self.master.iconname('fetchmail launcher'); + Pack.config(self) + Label(self, + text='Fetchmailconf ' + version, + bd=2).pack(side=TOP, pady=10) + self.keepalive = [] # Use this to anchor the PhotoImage object make_icon_window(self, fetchmail_icon) self.debug = 0 @@ -1772,31 +1784,31 @@ class MainWindow(Frame): # Label(self, image=icon_image).pack(side=TOP, pady=10) # self.keepalive.append(icon_image) - Message(self, text=""" + Message(self, text=""" Use `Configure fetchmail' to tell fetchmail about the remote servers it should poll (the host name, your username there, whether to use POP or IMAP, and so forth). """, width=600).pack(side=TOP) - self.configbutton = Button(self, text='Configure fetchmail', - fg='blue', command=self.configure) + self.configbutton = Button(self, text='Configure fetchmail', + fg='blue', command=self.configure) self.configbutton.pack() - Message(self, text=""" + Message(self, text=""" Use `Test fetchmail' to run fetchmail with debugging enabled. This is a good way to test out a new configuration. """, width=600).pack(side=TOP) - Button(self, text='Test fetchmail',fg='blue', command=self.test).pack() + Button(self, text='Test fetchmail',fg='blue', command=self.test).pack() - Message(self, text=""" + Message(self, text=""" Use `Run fetchmail' to run fetchmail in foreground. Progress messages will be shown, but not debug messages. """, width=600).pack(side=TOP) - Button(self, text='Run fetchmail', fg='blue', command=self.run).pack() + Button(self, text='Run fetchmail', fg='blue', command=self.run).pack() - Message(self, text=""" + Message(self, text=""" Or you can just select `Quit' to exit the launcher now. """, width=600).pack(side=TOP) - Button(self, text='Quit', fg='blue', command=self.leave).pack() + Button(self, text='Quit', fg='blue', command=self.leave).pack() def configure(self): self.configbutton.configure(state=DISABLED) @@ -1804,10 +1816,10 @@ Or you can just select `Quit' to exit the launcher now. lambda self=self: self.configbutton.configure(state=NORMAL), self) def test(self): - RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self) + RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self) def run(self): - RunWindow("fetchmail -d0", Toplevel(), self) + RunWindow("fetchmail -d0", Toplevel(), self) def leave(self): self.quit() @@ -1818,16 +1830,16 @@ def intersect(list1, list2): # Compute set intersection of lists res = [] for x in list1: - if x in list2: - res.append(x) + if x in list2: + res.append(x) return res def setdiff(list1, list2): # Compute set difference of lists res = [] for x in list1: - if not x in list2: - res.append(x) + if not x in list2: + res.append(x) return res def copy_instance(toclass, fromdict): @@ -1841,28 +1853,28 @@ def copy_instance(toclass, fromdict): optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert', 'sslproto', 'sslcertck', - 'sslcertpath', 'sslfingerprint', 'showdots') + 'sslcertpath', 'sslfingerprint', 'showdots') class_sig = setdiff(toclass.__dict__.keys(), optional) class_sig.sort() dict_keys = setdiff(fromdict.keys(), optional) dict_keys.sort() common = intersect(class_sig, dict_keys) if 'typemap' in class_sig: - class_sig.remove('typemap') + class_sig.remove('typemap') if tuple(class_sig) != tuple(dict_keys): - print "Fields don't match what fetchmailconf expected:" -# print "Class signature: " + `class_sig` -# print "Dictionary keys: " + `dict_keys` - diff = setdiff(class_sig, common) + print "Fields don't match what fetchmailconf expected:" +# print "Class signature: " + `class_sig` +# print "Dictionary keys: " + `dict_keys` + diff = setdiff(class_sig, common) if diff: print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff` diff = setdiff(dict_keys, common) if diff: print "Not matched in dictionary keys: " + `diff` - sys.exit(1) + sys.exit(1) else: - for x in fromdict.keys(): - setattr(toclass, x, fromdict[x]) + for x in fromdict.keys(): + setattr(toclass, x, fromdict[x]) # # And this is the main sequence. How it works: @@ -1961,7 +1973,7 @@ gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7 try: s = os.system(cmd) - if s != 0: + if s != 0: print "`" + cmd + "' run failure, status " + `s` raise SystemExit except: @@ -1988,14 +2000,14 @@ gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7 copy_instance(Fetchmailrc, fetchmailrc) Fetchmailrc.servers = []; for server in fetchmailrc['servers']: - Newsite = Server() - copy_instance(Newsite, server) - Fetchmailrc.servers.append(Newsite) - Newsite.users = []; - for user in server['users']: - Newuser = User() - copy_instance(Newuser, user) - Newsite.users.append(Newuser) + Newsite = Server() + copy_instance(Newsite, server) + Fetchmailrc.servers.append(Newsite) + Newsite.users = []; + for user in server['users']: + Newuser = User() + copy_instance(Newuser, user) + Newsite.users.append(Newuser) # We may want to display the configuration and quit if dump: diff --git a/odmr.c b/odmr.c index e97a755e..06f9437d 100644 --- a/odmr.c +++ b/odmr.c @@ -74,7 +74,7 @@ static int odmr_getrange(int sock, struct query *ctl, const char *id, * canonical DNS name. */ buf[0] = '\0'; - for (qnp = ctl->smtphunt; qnp; qnp = qnp->next) + for (qnp = ctl->domainlist; qnp; qnp = qnp->next) if (strlen(buf) + strlen(qnp->id) + 1 >= sizeof(buf)) break; else @@ -125,20 +125,12 @@ static int odmr_getrange(int sock, struct query *ctl, const char *id, /* * OK, if we got here it's time to become a pipe between the ODMR - * remote server (sending) and the local SMTP daemon (receiving). - * We're npt going to try to be a protocol machine; instead, we'll - * use select(2) to watch the read sides of both sockets and just - * throw their data at each other. + * remote server (sending) and the SMTP listener we've designated + * (receiving). We're not going to try to be a protocol machine; + * instead, we'll use select(2) to watch the read sides of both + * sockets and just throw their data at each other. */ - /* - * FIXME: we hardcode "localhost" here because ODMR is fighting - * over the ETRN meaning of smtphost and the POP/IMAP meaning. - * ODMR needs both meanings, but there is only one config var. So - * for now ODMR always uses the "localhost" SMTP server to connect - * with locally. - */ - smtp_sock = SockOpen("localhost", SMTP_PORT, NULL, NULL); - if (smtp_sock == -1) + if ((smtp_sock = smtp_open(ctl)) == -1) return(PS_SOCKET); else { diff --git a/options.c b/options.c index d9e5aa14..32060a9e 100644 --- a/options.c +++ b/options.c @@ -41,7 +41,7 @@ #define LA_PROTOCOL 17 #define LA_UIDL 18 #define LA_PORT 19 -#define LA_AUTH 20 +#define LA_AUTH 20 #define LA_TIMEOUT 21 #define LA_ENVELOPE 22 #define LA_QVIRTUAL 23 @@ -55,37 +55,37 @@ #define LA_WARNINGS 31 #define LA_FOLDER 32 #define LA_SMTPHOST 33 -#define LA_SMTPADDR 34 -#define LA_ANTISPAM 35 -#define LA_BATCHLIMIT 36 -#define LA_FETCHLIMIT 37 -#define LA_EXPUNGE 38 -#define LA_MDA 39 -#define LA_BSMTP 40 -#define LA_LMTP 41 -#define LA_PLUGIN 42 -#define LA_PLUGOUT 43 -#define LA_NETSEC 44 -#define LA_INTERFACE 45 -#define LA_MONITOR 46 -#define LA_CONFIGDUMP 47 -#define LA_YYDEBUG 48 -#define LA_SMTPNAME 49 +#define LA_FETCHDOMAINS 34 +#define LA_SMTPADDR 35 +#define LA_ANTISPAM 36 +#define LA_BATCHLIMIT 37 +#define LA_FETCHLIMIT 38 +#define LA_EXPUNGE 39 +#define LA_MDA 40 +#define LA_BSMTP 41 +#define LA_LMTP 42 +#define LA_PLUGIN 43 +#define LA_PLUGOUT 44 +#define LA_NETSEC 45 +#define LA_INTERFACE 46 +#define LA_MONITOR 47 +#define LA_CONFIGDUMP 48 +#define LA_YYDEBUG 49 +#define LA_SMTPNAME 50 +#define LA_SHOWDOTS 51 +#define LA_PRINCIPAL 52 +#define LA_TRACEPOLLS 53 #ifdef SSL_ENABLE -#define LA_SSL 50 -#define LA_SSLKEY 51 -#define LA_SSLCERT 52 -#define LA_SSLPROTO 53 -#define LA_SSLCERTCK 54 -#define LA_SSLCERTPATH 55 -#define LA_SSLFINGERPRINT 56 +#define LA_SSL 54 +#define LA_SSLKEY 55 +#define LA_SSLCERT 56 +#define LA_SSLPROTO 57 +#define LA_SSLCERTCK 58 +#define LA_SSLCERTPATH 59 +#define LA_SSLFINGERPRINT 60 #endif -#define LA_SHOWDOTS 57 -#define LA_PRINCIPAL 58 - -#define LA_TRACEPOLLS 59 /* options still left: CDgGhHjJoORwWxXYz */ static const char *shortoptions = @@ -133,6 +133,7 @@ static const struct option longoptions[] = { {"folder", required_argument, (int *) 0, LA_FOLDER }, {"smtphost", required_argument, (int *) 0, LA_SMTPHOST }, + {"fetchdomains", required_argument, (int *) 0, LA_FETCHDOMAINS }, {"smtpaddress", required_argument, (int *) 0, LA_SMTPADDR }, {"smtpname", required_argument, (int *) 0, LA_SMTPNAME }, {"antispam", required_argument, (int *) 0, LA_ANTISPAM }, @@ -488,6 +489,15 @@ struct query *ctl; /* option record to be initialized */ ((cp = strtok((char *)NULL, ","))); ocount++; break; + case LA_FETCHDOMAINS: + xalloca(buf, char *, strlen(optarg) + 1); + strcpy(buf, optarg); + cp = strtok(buf, ","); + do { + save_str(&ctl->domainlist, cp, TRUE); + } while + ((cp = strtok((char *)NULL, ","))); + break; case 'D': case LA_SMTPADDR: ctl->smtpaddress = xstrdup(optarg); @@ -687,6 +697,7 @@ struct query *ctl; /* option record to be initialized */ P(_(" -T, --netsec set IP security request\n")); #endif /* NET_SECURITY */ P(_(" -S, --smtphost set SMTP forwarding host\n")); + P(_(" --fetchdomains fetch mail for specified domains\n")); P(_(" -D, --smtpaddress set SMTP delivery domain to use\n")); P(_(" --smtpname set SMTP full name username@domain\n")); P(_(" -Z, --antispam, set antispam response values\n")); diff --git a/rcfile_l.l b/rcfile_l.l index 6ada8b67..39574586 100644 --- a/rcfile_l.l +++ b/rcfile_l.l @@ -103,6 +103,7 @@ user(name)? {SETSTATE(NAME); return USERNAME; } pass(word)? {SETSTATE(NAME); return PASSWORD; } folder(s)? { return FOLDER; } smtp(host)? { return SMTPHOST; } +fetchdomains { return FETCHDOMAINS; } smtpaddress { return SMTPADDRESS; } smtpname { return SMTPNAME; } antispam { return SPAMRESPONSE; } diff --git a/rcfile_y.y b/rcfile_y.y index e2c4c82c..dbb1ad38 100644 --- a/rcfile_y.y +++ b/rcfile_y.y @@ -59,8 +59,8 @@ extern char * yytext; } %token DEFAULTS POLL SKIP VIA AKA LOCALDOMAINS PROTOCOL -%token AUTHENTICATE TIMEOUT KPOP SDPS -%token ENVELOPE QVIRTUAL USERNAME PASSWORD FOLDER SMTPHOST MDA BSMTP LMTP +%token AUTHENTICATE TIMEOUT KPOP SDPS ENVELOPE QVIRTUAL +%token USERNAME PASSWORD FOLDER SMTPHOST FETCHDOMAINS MDA BSMTP LMTP %token SMTPADDRESS SMTPNAME SPAMRESPONSE PRECONNECT POSTCONNECT LIMIT WARNINGS %token NETSEC INTERFACE MONITOR PLUGIN PLUGOUT %token IS HERE THERE TO MAP WILDCARD @@ -286,6 +286,10 @@ smtp_list : STRING {save_str(¤t.smtphunt, $1,TRUE);} | smtp_list STRING {save_str(¤t.smtphunt, $2,TRUE);} ; +domain_list : STRING {save_str(¤t.domainlist, $1,TRUE);} + | domain_list STRING {save_str(¤t.domainlist, $2,TRUE);} + ; + num_list : NUMBER { struct idlist *id; @@ -309,6 +313,7 @@ user_option : TO localnames HERE | PASSWORD STRING {current.password = xstrdup($2);} | FOLDER folder_list | SMTPHOST smtp_list + | FETCHDOMAINS domain_list | SMTPADDRESS STRING {current.smtpaddress = xstrdup($2);} | SMTPNAME STRING {current.smtpname = xstrdup($2);} | SPAMRESPONSE num_list diff --git a/sink.c b/sink.c index 35f5a411..20c23919 100644 --- a/sink.c +++ b/sink.c @@ -45,7 +45,7 @@ /* makes the open_sink()/close_sink() pair non-reentrant */ static int lmtp_responses; -static int smtp_open(struct query *ctl) +int smtp_open(struct query *ctl) /* try to open a socket to the appropriate SMTP server for this query */ { /* maybe it's time to close the socket in order to force delivery */ -- cgit v1.2.3