]> Pileus Git - ~andy/fetchmail/blobdiff - fetchmailconf
Better skip handling.
[~andy/fetchmail] / fetchmailconf
index cc933e226bacf096424b822d163f75a7de909c4e..a92e542c89a53e734b06204d3f279466f6b632e3 100755 (executable)
-#!/usr/bin/python
+#!/usr/bin/env python
 #
-# A GUI configurator for generating Fetchmail configuration files
-# by Eric S. Raymond, <esr@snark.thyrsus.com>.  Requires Python with Tkinter.
+# A GUI configurator for generating fetchmail configuration files.
+# by Eric S. Raymond, <esr@snark.thyrsus.com>.
+# Requires Python with Tkinter, and the following OS-dependent services:
+#       posix, posixpath, socket
+version = "1.37"
 
 from Tkinter import *
 from Dialog import *
-import sys
-import time
-import os
-import string
+import sys, time, os, string, socket, getopt, tempfile
 
 #
 # Define the data structures the GUIs will be tossing around
 #
 class Configuration:
     def __init__(self):
-       self.poll_interval = 300        # Default to 5-minute timeout
-       self.syslog = FALSE             # Use syslogd for logging?
-       self.logfile = None             # No logfile, initially
-       self.idfile = os.environ["HOME"] + "/.fetchids"         # Default idfile, initially
-       self.invisible = FALSE          # Suppress Received line & spoof?
-       self.servers = []               # List of included sites
-       Configuration.typemap = (
-           ('poll_interval',   'String'),
-           ('syslog',          'Boolean'),
-           ('logfile',         'String'),
-           ('idfile',          'String'),
-           ('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.poll_interval != ConfigurationDefaults.poll_interval:
-           str = str + ("set daemon %s\n" % (self.poll_interval,))
-       for site in self.servers:
-           str = str + repr(site)
-       return str
+        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:
+            str = str + ("set nobouncemail\n")
+        if self.spambounce:
+            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
+
+    def __delitem__(self, name):
+        for si in range(len(self.servers)):
+            if self.servers[si].pollname == name:
+                del self.servers[si]
+                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 = 'password'          # 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.users = []                 # List of user entries for site
-       Server.typemap = (
-           ('pollname',  'String'),
-           ('via',       'String'),
-           ('active',    'Boolean'),
-           ('interval',  'Int'),
-           ('protocol',  'String'),
-           ('interval',  'Int'),
-           ('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'))
+        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):
-       str = ""
-       if self.active:   str = str + "poll"
-       else:             str = str + "skip"
-       str = str + (" " + self.pollname)
-       if self.via:
-           str = str + " via " + self.via
-       if self.protocol != ServerDefaults.protocol:
-           str = str + " with proto " + self.protocol 
-       if self.port != defaultports[self.protocol] and self.port != 0:
-           str = str + " port " + `self.port`
-       if self.timeout != ServerDefaults.timeout:
-           str = str + " timeout " + `self.timeout`
-       if self.interval != ServerDefaults.interval: 
-           str = str + " interval " + `self.interval`
-       if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
-           if self.envskip:
-               str = str + " envelope " + self.envskip + " " + self.envelope
-           else:
-               str = str + " envelope " + self.envelope
-       if self.qvirtual != ServerDefaults.qvirtual:
-           str = str + " qvirtual " + self.qvirtual
-       if self.auth != ServerDefaults.auth:
-           str = str + " auth " + self.auth
-       if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
-           str = str + " and options"
-       if self.dns != ServerDefaults.dns:
-           str = str + flag2str(self.dns, 'dns')
-       if self.uidl != ServerDefaults.uidl:
-           str = str + flag2str(self.uidl, 'uidl')
-       if folded:        str = str + "\n    "
-       else:             str = str + " "
-
-       if self.aka:
-            str = str + "aka"
-            for x in self.aka:
-               str = str + " " + x
-       if self.aka and self.localdomains: str = str + " "
-       if self.localdomains:
-            str = str + ("localdomains")
-            for x in self.localdomains:
-               str = str + " " + 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:
-               str = str + "\n    "
-           else:
-               str = str + " "
-
-       if self.interface: str = str + "interface " + self.interface
-       if self.monitor: str = str + "monitor " + self.monitor
-       if (self.interface or self.monitor):
-           if folded:
-               str = str + "\n"
-
-       if str[-1] == " ": str = str[0:-1]
-
-       for user in self.users:
-           str = str + repr(user)
-       str = str + "\n"
-       return str;
+            if folded:
+                res = res + "\n    "
+            else:
+                res = res + " "
+
+        if self.tracepolls:
+           res = res + "tracepolls\n"
+
+        if self.interface:
+            res = res + "interface " + str(self.interface)
+        if self.monitor:
+            res = res + " monitor " + str(self.monitor)
+        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 res[-1] == " ": res = res[0:-1]
+
+        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)):
+            if self.users[ui].remote == name:
+                del self.users[ui]
+                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):
-       self.remote = ""                # Remote username
-       self.localnames = None          # Local names
-       self.password = ""              # Password for mail account access
-       self.mailboxes = None           # Remote folders to retrieve from
-       self.smtphunt = None            # Hosts to forward to
-       self.smtpaddress = None;        # Append this to MAIL FROM line
-       self.preconnect = ""            # Connection setup
-       self.postconnect = ""           # Connection wrapup
-       self.mda = ""                   # Mail Delivery Agent
-       self.antispam = 571;            # 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.limit = 0                  # Message size limit
-       self.fetchlimit = 0             # Max messages fetched per batch
-       self.batchlimit = 0             # Max message forwarded per batch
-       self.expunge = 1                # Interval between expunges (IMAP)
-       User.typemap = (
-           ('remote',      'String'),
-           # leave out mailboxes and localnames
-           ('password',    'String'),
-            # Leave out smtphunt
-           ('smtpaddress',  'String'),
-           ('preconnect',  'String'),
-           ('postconnect', 'String'),
-           ('mda',         'String'),
-           ('antispam',    'Int'),
-           ('keep',        'Boolean'),
-           ('flush',       'Boolean'),
-           ('fetchall',    'Boolean'),
-           ('rewrite',     'Boolean'),
-           ('forcecr',     'Boolean'),
-           ('stripcr',     'Boolean'),
-           ('pass8bits',   'Boolean'),
-           ('mimedecode',  'Boolean'),
-           ('dropstatus',  'Boolean'),
-           ('limit',       'Int'),
-           ('fetchlimit',  'Int'),
-           ('batchlimit',  'Int'),
-           ('expunge',     'Int'))
+        if os.environ.has_key("USER"):
+            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.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, 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'),
+            ('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'),
+            ('properties',  'String'))
 
     def __repr__(self):
-       str = "    "
-       str = str + "user " + self.remote + " there ";
-       if self.password: str = str + "with password " + self.password + ' '
-       if self.localnames:
-            str = str + "is"
-            for x in self.localnames:
-               str = str + " " + x
-            str = str + " 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):
-           str = str + " options"
-       if self.keep != UserDefaults.keep:
-           str = str + flag2str(self.keep, 'keep')
-       if self.flush != UserDefaults.flush:
-           str = str + flag2str(self.flush, 'flush')
-       if self.fetchall != UserDefaults.fetchall:
-           str = str + flag2str(self.fetchall, 'fetchall')
-       if self.rewrite != UserDefaults.rewrite:
-           str = str + flag2str(self.rewrite, 'rewrite')
-       if self.forcecr != UserDefaults.forcecr:
-           str = str + flag2str(self.forcecr, 'forcecr')
-       if self.stripcr != UserDefaults.stripcr:
-           str = str + flag2str(self.stripcr, 'stripcr')
-       if self.pass8bits != UserDefaults.pass8bits:
-           str = str + flag2str(self.pass8bits, 'pass8bits')
-       if self.mimedecode != UserDefaults.mimedecode:
-           str = str + flag2str(self.mimedecode, 'mimedecode')
-       if self.dropstatus != UserDefaults.dropstatus:
-           str = str + flag2str(self.dropstatus, 'dropstatus')
-       if self.limit != UserDefaults.limit:
-           str = str + " limit " + `self.limit`
-       if self.fetchlimit != UserDefaults.fetchlimit:
-           str = str + " fetchlimit " + `self.fetchlimit`
-       if self.batchlimit != UserDefaults.batchlimit:
-           str = str + " batchlimit " + `self.batchlimit`
-       if self.expunge != UserDefaults.expunge:
-           str = str + " expunge " + `self.expunge`
-       if self.mailboxes:
-            str = str + "\n    folder"
-            for x in self.mailboxes:
-               str = str + " " + x
-            str = str + " here"
-       return str;
+        res = "    "
+        res = res + "user " + `self.remote` + " there ";
+        if self.password:
+            res = res + "with password " + `self.password` + " "
+        if self.localnames:
+            res = res + "is"
+            for x in self.localnames:
+                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.sslproto and self.sslproto != UserDefaults.sslproto:
+            res = res + " sslproto " + `self.sslproto`
+        if self.sslcertck and self.sslcertck != UserDefaults.sslcertck:
+            res = res +  flag2str(self.sslcertck, 'sslcertck')
+        if self.sslcertpath and self.sslcertpath != UserDefaults.sslcertpath:
+            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`
+        res = res + "\n"
+        trimmed = self.smtphunt;
+        if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
+            trimmed = trimmed[0:len(trimmed) - 1]
+        if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
+            trimmed = trimmed[0:len(trimmed) - 1]
+        if trimmed != []:
+            res = res + "    smtphost "
+            for x in trimmed:
+                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.antispam != UserDefaults.antispam:
+            res = res + "    antispam " + self.antispam + "\n"
+        return res;
 
     def __str__(self):
-       return "[User: " + repr(self) + "]"
+        return "[User: " + repr(self) + "]"
 
 #
 # Helper code
 #
 
 defaultports = {"auto":0,
-               "POP2":109, 
-               "POP3":110, "APOP":110, "KPOP":1109, "IMAP":143,
-               "IMAP-K4":143,
-               "ETRN":25}
-
-protolist = ("auto", "POP2", "POP3", "APOP", "KPOP", "IMAP", "IMAP-K4", "ETRN")
+                "POP2":109, 
+                "POP3":110,
+                "APOP":110,
+                "KPOP":1109,
+                "IMAP":143,
+                "ETRN":25,
+                "ODMR":366}
 
-authlist = ("password", "kerberos")
+authlist = ("any", "password", "gssapi", "kerberos", "ssh")
 
 listboxhelp = {
     'title' : 'List Selection Help',
@@ -281,39 +415,44 @@ 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
     bar = Frame(frame)
-    width = len(alternatives) / depth;
+    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
-           Radiobutton(subframe,
-                       {'text':alternatives[ind], 
-                        'variable':ref,
-                        'value':alternatives[ind],
-                        'command':command}).pack(side=TOP, anchor=W)
-       subframe.pack(side=LEFT)
+        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)
+            else:
+                # This is just a spacer
+                Radiobutton(subframe,
+                        {'text':" ",'state':DISABLED}).pack(side=TOP, anchor=W)
+        subframe.pack(side=LEFT)
     bar.pack(side=TOP);
     return bar
 
@@ -323,98 +462,127 @@ def helpwin(helpdict):
     helpwin.title(helpdict['title']) 
     helpwin.iconname(helpdict['title'])
     Label(helpwin, text=helpdict['banner']).pack()
-    textwin = Message(helpwin, text=helpdict['text'], width=600)
-    textwin.pack()
+    textframe = Frame(helpwin)
+    scroll = Scrollbar(textframe)
+    helpwin.textwidget = Text(textframe, setgrid=TRUE)
+    textframe.pack(side=TOP, expand=YES, fill=BOTH)
+    helpwin.textwidget.config(yscrollcommand=scroll.set)
+    helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
+    scroll.config(command=helpwin.textwidget.yview)
+    scroll.pack(side=RIGHT, fill=BOTH)
+    helpwin.textwidget.insert(END, helpdict['text']);
     Button(helpwin, text='Done', 
-          command=lambda x=helpwin: Widget.destroy(x),
-          relief=SUNKEN, bd=2).pack()
+           command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
+    textframe.pack(side=TOP)
+
+def make_icon_window(base, image):
+    try:
+        # Some older pythons will error out on this
+        icon_image = PhotoImage(data=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
+        # scope when the enclosing function returns.  Therefore
+        # we have to explicitly link them to something.
+        base.keepalive.append(icon_image)
+    except:
+        pass
 
 class ListEdit(Frame):
 # edit a list of values (duplicates not allowed) with a supplied editor hook 
-    def __init__(self, newlegend, list, editor, master, helptxt):
-       self.editor = editor
-       self.list = list
-
-       # Set up a widget to accept new elements
-       self.newval = StringVar(master)
-       newwin = LabeledEntry(master, newlegend, self.newval, '12')
-       newwin.bind('<Double-1>', self.handleNew)
-       newwin.bind('<Return>', 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')
+    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('<Double-1>', self.handleNew)
+        newwin.bind('<Return>', 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, relief=SUNKEN)
-       self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
-       scroll.config(command=self.listwidget.yview, relief=SUNKEN)
-       scroll.pack(side=RIGHT, fill=BOTH)
-       self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
-       self.listwidget.bind('<Double-1>', self.handleList);
-       self.listwidget.bind('<Return>', 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('<Double-1>', self.handleList);
+        self.listwidget.bind('<Return>', 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();
+        self.editItem();
 
     def handleNew(self, event):
-       item = self.newval.get()
-       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'))):
-           self.listwidget.insert('end', item)
-           if self.list != None: self.list.append(item)
-       self.newval.set('')
+        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'))):
+                self.listwidget.insert('end', item)
+                if self.list != None: self.list.append(item)
+                if self.editor:
+                    apply(self.editor, (item,))
+            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);
-               apply(self.editor, (label,))
+        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:
-           index = select[0]
-           if index:
-               self.listwidget.delete(index)
-               if self.list != None: del self.list[index]
+        select = self.listwidget.curselection()
+        if not select:
+            helpwin(listboxhelp)
+        else:
+            index = string.atoi(select[0])
+            label = self.listwidget.get(index);
+            self.listwidget.delete(index)
+            if self.list != None:
+                del self.list[index]
+            if self.deletor != None:
+                apply(self.deletor, (label,))
 
 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):
+def dispose_window(master, legend, help, savelegend='OK'):
     dispose = Frame(master, relief=RAISED, bd=5)
     Label(dispose, text=legend).pack(side=TOP,pady=10)
-    Button(dispose, text='Save', fg='blue',
+    Button(dispose, text=savelegend, fg='blue',
            command=master.save).pack(side=LEFT)
     Button(dispose, text='Quit', fg='blue',
            command=master.nosave).pack(side=LEFT)
@@ -425,26 +593,21 @@ def dispose_window(master, legend, help):
 
 class MyWidget:
 # Common methods for Tkinter widgets -- deals with Tkinter declaration
-    def post(self, template, field):
-       for x in template.typemap:
-           target = "self." + x[0]
-           source = "self." + field + "." + x[0]
-           if x[1] == 'Boolean':
-               exec target + " = BooleanVar(self)"
-               if eval(source):
-                   exec target + ".set(" + source + ")"
-           elif x[1] == 'String':
-               exec target + " = StringVar(self)"
-               if eval(source):
-                   exec target + ".set(" + source + ")"
-           elif x[1] == 'Int':
-               exec target + " = IntVar(self)"
-               if eval(source):
-                   exec target + ".set(" + source + ")"
-
-    def fetch(self, template, field):
-       for x in template.typemap:
-           setattr(eval("self." + field), x[0], getattr(self, x[0]).get())
+    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])
+            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())
 
 #
 # First, code to set the global fetchmail run controls.
@@ -506,7 +669,22 @@ Idfile
         If empty, store seen-message IDs in .fetchids under user's home
         directory.  If nonempty, use given file name.
 
-Invisible:
+Postmaster
+        Who to send multidrop mail to as a last resort if no address can
+        be matched.  Normally empty; in this case, fetchmail treats the
+        invoking user as the address of last resort unless that user is
+        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.
+        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).
+
+Invisible
         If false (the default) fetchmail generates a Received line into
         each message and generates a HELO from the machine it is running on.
         If true, fetchmail generates no Received line and HELOs as if it were
@@ -529,79 +707,141 @@ This will take you to a site configuration dialogue.
 
 
 class ConfigurationEdit(Frame, MyWidget):
-    def __init__(self, configuration, master=None):
-       self.configuration = configuration
+    def __init__(self, configuration, outfile, master, onexit):
+        self.subwidgets = {}
+        self.configuration = configuration
+        self.outfile = outfile
         self.container = master
+        self.onexit = onexit
         ConfigurationEdit.mode_to_help = {
             'novice':configure_novice_help, 'expert':configure_expert_help
             }
 
+    def server_edit(self, sitename):
+        self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
+
+    def server_delete(self, sitename):
+        try:
+            for user in self.subwidgets.keys():
+                user.destruct()
+            del self.configuration[sitename]
+        except:
+            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');
-       Pack.config(self)
+        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
+        make_icon_window(self, fetchmail_icon)
+        Pack.config(self)
         self.post(Configuration, 'configuration')
 
-       dispose_window(self,
+        dispose_window(self,
                        'Configurator ' + self.mode + ' Controls',
-                       ConfigurationEdit.mode_to_help[self.mode])
+                       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)
 
+        ff = Frame(df)
+        if self.mode != 'novice':
+            # Set the postmaster
+            log = LabeledEntry(ff, '     Postmaster:', self.postmaster, '14')
+            log.pack(side=RIGHT, anchor=E)
+
         # Set the poll interval
-        de = LabeledEntry(df, '     Poll interval:', self.poll_interval, '14')
+        de = LabeledEntry(ff, '     Poll interval:', self.poll_interval, '14')
         de.pack(side=RIGHT, anchor=E)
+        ff.pack()
+
         df.pack()
 
         if self.mode != 'novice':
+            pf = Frame(gf)
+            Checkbutton(pf,
+                {'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)
+            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, m=self.mode, s=self.configuration.servers:
-                 ServerEdit(site, s).edit(m, Toplevel()),
+        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():
+            self.subwidgets[sitename].destruct()        
+        self.master.destroy()
+        self.onexit()
 
     def nosave(self):
-       if ConfirmQuit(self, self.mode + " configuration editor"):
-           self.quit()
+        if ConfirmQuit(self, self.mode + " configuration editor"):
+            self.destruct()
 
-    # XXX Someday this must go to the actual config file location
     def save(self):
+        for sitename in self.subwidgets.keys():
+            self.subwidgets[sitename].save()
         self.fetch(Configuration, 'configuration')
-       sys.stdout.write("# Configuration created %s\n" % time.ctime(time.time()))
-       sys.stdout.write(`self.configuration`)
-       self.quit()
+        fm = None
+        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:
+            fm = open(self.outfile, 'w')
+        if fm:
+            fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
+            fm.write(`self.configuration`)
+            if self.outfile:
+                fm.close()
+            if fm != sys.stdout:
+                os.chmod(self.outfile, 0600)
+            self.destruct()
 
 #
 # Server editing stuff.
@@ -629,7 +869,7 @@ The server options screen controls fetchmail
 options that apply to one of your mailservers.
 
 Once you have a mailserver configuration set
-up as you like it, you can select `Save' to
+up as you like it, you can select `OK' to
 store it in the server list maintained in
 the main configuration window.
 
@@ -667,9 +907,17 @@ These options control the remote-mail protocol
 and TCP/IP service port used to query this
 server.
 
+If you click the `Probe for supported protocols'
+button, fetchmail will try to find you the most
+capable server on the selected host (this will
+only work if you're conncted to the Internet).
+The probe only checks for ordinary IMAP and POP
+protocols; fortunately these are the most
+frequently supported.
+
 The `Protocol' button bar offers you a choice of
 all the different protocols available.  The `auto'
-protocol is a special mode that probes the host
+protocol is the default mode; it probes the host
 ports for POP3 and IMAP to see if either is
 available.
 
@@ -683,23 +931,34 @@ sechelp = {
     'title' : 'Security option help',
     'banner': 'Security',
     'text' : """
-The `interface' option, if given, specifies the only
-device through which fetchmail is permitted to connect
-to servers.  Specifying this may protect you from a 
-spoofing attack if your client machine has more than
-one IP gateway address and some of the gateways are
-to insecure nets.
-
-The `monitor' option allows you to specify a range
+The `interface' option allows you to specify a range
 of IP addresses to monitor for activity.  If these
 addresses are not active, fetchmail will not poll.
-This option may be used to prevent fetchmail from
-triggering an expensive dial-out if the interface
-is not already active.
+Specifying this may protect you from a spoofing attack
+if your client machine has more than one IP gateway
+address and some of the gateways are to insecure nets.
+
+The `monitor' option, if given, specifies the only
+device through which fetchmail is permitted to connect
+to servers.  This option may be used to prevent
+fetchmail from triggering an expensive dial-out if the
+interface is not already active.
 
 The `interface' and `monitor' options are available
-only for Linux systems.  See the fetchmail manual page
-for details on these.
+only for Linux and freeBSD systems.  See the fetchmail
+manual page for details on these.
+
+The ssl option enables SSL communication with a mailserver
+supporting Secure Sockets Layer. The sslkey and sslcert options
+declare key and certificate files for use with SSL.
+The sslcertck option enables strict checking of SSL server
+certificates (and sslcertpath gives trusted certificate
+directory). With sslfingerprint, you can specify a finger-
+print the server's key is checked against.
+
+The `netsec' option will be configurable only if fetchmail
+was compiled with IPV6 support.  If you need to use it,
+you probably know what to do.
 """}
 
 multihelp = {
@@ -726,83 +985,131 @@ user's options on that site.
 """}
 
 class ServerEdit(Frame, MyWidget):
-    def __init__(self, host, servers):
-       self.server = None
-       for site in servers:
-           if site.pollname == host:
-               self.server = site
-       if (self.server == None):
-               self.server = Server()
-               self.server.pollname = host
-               self.server.via = host
-               servers.append(self.server)
+    def __init__(self, host, parent):
+        self.parent = parent
+        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)
 
     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.grab_set()
-#      self.focus_set()
-#      self.wait_window()
+        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
+
+    def destruct(self):
+        for username in self.subwidgets.keys():
+            self.subwidgets[username].destruct()        
+        del self.parent.subwidgets[self.server.pollname]
+        Widget.destroy(self.master)
 
     def nosave(self):
-       if ConfirmQuit(self, 'server option editing'):
-           Widget.destroy(self.master)
+        if ConfirmQuit(self, 'server option editing'):
+            self.destruct()
 
     def save(self):
-       self.fetch(Server, 'server')
-       Widget.destroy(self.master)
+        self.fetch(Server, 'server')
+        for username in self.subwidgets.keys():
+            self.subwidgets[username].save()        
+        self.destruct()
 
     def refreshPort(self):
-       proto = self.protocol.get()
-       self.port.set(defaultports[proto])
-       if not proto in ("POP3", "APOP", "KPOP"): self.uidl = FALSE
+        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.
+        # Now we reset unconditionally on the theory that if you're setting
+        # 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 
+
+    def user_edit(self, username, mode):
+        self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
+
+    def user_delete(self, username):
+        if self.subwidgets.has_key(username):
+            self.subwidgets[username].destruct()
+        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)
 
-       protwin = Frame(leftwin, relief=RAISED, bd=5)
-       Label(protwin, text="Protocol").pack(side=TOP)
-       pb = ButtonBar(protwin, '', self.protocol, protolist, 2, self.refreshPort) 
+        # Compute the available protocols from the compile-time options
+        protolist = ['auto']
+        if 'pop2' in feature_options:
+            protolist.append("POP2")
+        if 'pop3' in feature_options:
+            protolist = protolist + ["POP3", "APOP", "KPOP"]
+        if 'sdps' in feature_options:
+            protolist.append("SDPS")
+        if 'imap' in feature_options:
+            protolist.append("IMAP")
+        if 'etrn' in feature_options:
+            protolist.append("ETRN")
+        if 'odmr' in feature_options:
+            protolist.append("ODMR")
+
+        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='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.server: UserEdit(u,s).edit(m, Toplevel()),
+                 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);
@@ -810,36 +1117,336 @@ 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, mdropwin, None)
+            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, mdropwin, multihelp)
+                 self.server.localdomains, None, None, mdropwin, multihelp)
             mdropwin.pack(fill=X)
 
-            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)
-            if os.popen("uname").readlines()[0] == 'Linux\n':
-                LabeledEntry(secwin, 'Interface to check before poll:',
-                        self.interface, leftwidth).pack(side=TOP, fill=X)
-                LabeledEntry(secwin, 'IP range to monitor:',
-                        self.monitor, leftwidth).pack(side=TOP, fill=X)
-            Button(secwin, text='Help', fg='blue',
-              command=lambda: helpwin(sechelp)).pack(side=RIGHT)
-            secwin.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)
+                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)
+                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)
+                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:
+                    LabeledEntry(secwin, 'Principal:',
+                         self.principal, '12').pack(side=TOP, fill=X)
+                Button(secwin, text='Help', fg='blue',
+                       command=lambda: helpwin(sechelp)).pack(side=RIGHT)
+                secwin.pack(fill=X)
 
             rightwin.pack(side=LEFT, anchor=N);
 
+    def autoprobe(self):
+        # Note: this only handles case (1) near fetchmail.c:1032
+        # We're assuming people smart enough to set up ssh tunneling
+        # won't need autoprobing.
+        if self.server.via:
+            realhost = self.server.via
+        else:
+            realhost = self.server.pollname
+        greetline = None
+        for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            try:
+                sock.connect(realhost, port)
+                greetline = sock.recv(1024)
+                sock.close()
+            except:
+                pass
+            else:
+                break
+        confwin = Toplevel()
+        if greetline == None:
+            title = "Autoprobe of " + realhost + " failed"
+            confirm = """
+Fetchmailconf didn't find any mailservers active.
+This could mean the host doesn't support any,
+or that your Internet connection is down, or
+that the host is so slow that the probe timed
+out before getting a response.
+"""
+        else:
+            warnings = ''
+            # OK, now try to recognize potential problems
+
+            if protocol == "POP2":
+                warnings = warnings + """
+It appears you have somehow found a mailserver running only POP2.
+Congratulations.  Have you considered a career in archaeology?
+
+Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
+Unless the first line of your fetchmail -V output includes the string "POP2",
+you'll have to build it from sources yourself with the configure
+switch --enable-POP2.
+
+"""
+
+### POP3 servers start here
+
+            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
+on the floor if your connection is interrupted during the session).
+
+It is strongly recommended that you find a better POP3 server.  The fetchmail
+FAQ includes pointers to good ones.
+
+"""
+# Steve VanDevender <stevev@efn.org> writes:
+# The only system I have seen this happen with is cucipop-1.31
+# under SunOS 4.1.4.  cucipop-1.31 runs fine on at least Solaris
+# 2.x and probably quite a few other systems.  It appears to be a
+# bug or bad interaction with the SunOS realloc() -- it turns out
+# that internally cucipop does allocate a certain data structure in
+# multiples of 16, using realloc() to bump it up to the next
+# multiple if it needs more.
+# 
+# The distinctive symptom is that when there are 16 messages in the
+# inbox, you can RETR and DELE all 16 messages successfully, but on
+# QUIT cucipop returns something like "-ERR Error locking your
+# mailbox" and aborts without updating it.
+# 
+# The cucipop banner looks like:
+# 
+# +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
+#
+            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()
+under that version, and doesn't cope with the result gracefully.  Newer
+SunOS and Solaris machines run cucipop OK.
+
+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:
+                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.
+
+"""
+# 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:
+                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.
+
+"""
+            if string.find(greetline, "OpenMail") > 0:
+                warnings = warnings + """
+You appear to be using some version of HP OpenMail.  Many versions of
+OpenMail do not process the "TOP" command correctly; the symptom is that
+only the header and first line of each message is retrieved.  To work
+around this bug, turn on `fetchall' on all user entries associated with
+this server.  
+
+"""
+            if string.find(greetline, "Escape character is") > 0:
+                warnings = warnings + """
+Your greeting line looks like it was written by a fetid pile of
+camel dung identified to me as `popa3d written by Solar Designer'.
+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:
+                warnings = warnings + """
+The Mail Max POP3 server screws up on mail with attachments.  It
+reports the message size with attachments included, but doesn't
+download them on a RETR or TOP (this violates the IMAP RFCs).  It also
+doesn't implement TOP correctly.  You should get rid of it -- and the
+brain-dead NT server it rode in on.
+
+"""
+            if string.find(greetline, "POP3 Server Ready") > 0:
+                warnings = warnings + """
+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:
+                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
+qpopper does something odd that causes fetchmail to hang with a socket
+error on very large messages.  This is probably not a fetchmail bug, as
+it has been observed with fetchpop.  The fix is to upgrade to qpopper
+3.0beta or a more recent version.  Better yet, switch to IMAP.
+
+"""
+            if string.find(greetline, " sprynet.com") > 0:
+                warnings = warnings + """
+You appear to be using a SpryNet server.  In mid-1999 it was reported that
+the SpryNet TOP command marks messages seen.  Therefore, for proper error
+recovery in the event of a line drop, it is strongly recommended that you
+turn on `fetchall' on all user entries associated with this server.  
+
+"""
+            if string.find(greetline, "TEMS POP3") > 0:
+                warnings = warnings + """
+Your POP3 server has "TEMS" in its header line.  At least one such
+server does not process the "TOP" command correctly; the symptom is
+that fetchmail hangs when trying to retrieve mail.  To work around
+this bug, turn on `fetchall' on all user entries associated with this
+server.
+
+"""
+            if string.find(greetline, " spray.se") > 0:
+                warnings = warnings + """
+Your POP3 server has "spray.se" in its header line.  In May 2000 at
+least one such server did not process the "TOP" command correctly; the
+symptom is that messages are treated as headerless.  To work around
+this bug, turn on `fetchall' on all user entries associated with this
+server.
+
+"""
+            if string.find(greetline, " usa.net") > 0:
+                warnings = warnings + """
+You appear to be using USA.NET's free mail service.  Their POP3 servers
+(at least as of the 2.2 version in use mid-1998) are quite flaky, but
+fetchmail can compensate.  They seem to require that fetchall be switched on
+(otherwise you won't necessarily see all your mail, not even new mail).
+They also botch the TOP command the fetchmail normally uses for retrieval
+(it only retrieves about 10 lines rather than the number specified).
+Turning on fetchall will disable the use of TOP.
+
+Therefore, it is strongly recommended that you turn on `fetchall' on all
+user entries associated with this server.  
+
+"""
+            if string.find(greetline, " Novonyx POP3") > 0:
+                warnings = warnings + """
+Your mailserver is running Novonyx POP3.  This server, at least as of
+version 2.17, seems to have problems handling and reporting seen bits.
+You may have to use the fetchall option.
+
+"""
+
+### IMAP servers start here
+
+            if string.find(greetline, "GroupWise") > 0:
+                warnings = warnings + """
+The Novell GroupWise IMAP server would be better named GroupFoolish;
+it is (according to the designer of IMAP) unusably broken.  Among
+other things, it doesn't include a required content length in its
+BODY[TEXT] response.<p>
+
+Fetchmail works around this problem, but we strongly recommend voting
+with your dollars for a server that isn't brain-dead.  If you stick
+with code as shoddy as GroupWise seems to be, you will probably pay
+for it with other problems.<p>
+
+"""
+            if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
+                warnings = warnings + """
+This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
+set the messages's Seen flag.  As a result, if you use the keep option the
+same messages will be downloaded over and over.
+
+"""
+            if string.find(greetline, "InterChange") > 0:
+                warnings = warnings + """
+
+The InterChange IMAP server at release levels below 3.61.08 screws up
+on mail with attachments.  It doesn't fetch them if you give it a
+BODY[TEXT] request, though it does if you request RFC822.TEXT.
+According to the IMAP RFCs and their maintainer these should be
+equivalent -- and we can't drop the BODY[TEXT] form because M$
+Exchange (quite legally under RFC2062) rejectsit.  The InterChange
+folks claim to have fixed this bug in 3.61.08.
+
+"""
+            if string.find(greetline, "Imail") > 0:
+                warnings = warnings + """
+We've seen a bug report indicating that this IMAP server (at least as of
+version 5.0.7) returns an invalid body size for messages with MIME
+attachments; the effect is to drop the attachments on the floor.  We
+recommend you upgrade to a non-broken IMAP server.
+
+"""
+            if string.find(greetline, "Domino IMAP4") > 0:
+                warnings = warnings + """
+Your IMAP server appears to be Lotus Domino.  This server, at least up
+to version 4.6.2a, has a bug in its generation of MIME boundaries (see
+the details in the fetchmail FAQ).  As a result, even MIME aware MUAs
+will see attachments as part of the message text.  If your Domino server's
+POP3 facility is enabled, we recommend you fall back on it.
+
+"""
+
+### Checks for protocol variants start here
+
+            closebrak = string.find(greetline, ">")
+            if  closebrak > 0 and greetline[closebrak+1] == "\r":
+                warnings = warnings + """
+It looks like you could use APOP on this server and avoid sending it your
+password in clear.  You should talk to the mailserver administrator about
+this.
+
+"""
+            if string.find(greetline, "IMAP2bis") > 0:
+                warnings = warnings + """
+IMAP2bis servers have a minor problem; they can't peek at messages without
+marking them seen.  If you take a line hit during the retrieval, the 
+interrupted message may get left on the server, marked seen.
+
+To work around this, it is recommended that you set the `fetchall'
+option on all user entries associated with this server, so any stuck
+mail will be retrieved next time around.
+
+To fix this bug, upgrade to an IMAP4 server.  The fetchmail FAQ includes
+a pointer to an open-source implementation.
+
+"""
+            if string.find(greetline, "IMAP4rev1") > 0:
+                warnings = warnings + """
+I see an IMAP4rev1 server.  Excellent.  This is (a) the best kind of
+remote-mail server, and (b) the one the fetchmail author uses.  Fetchmail
+has therefore been extremely well tested with this class of server.
+
+"""
+            if warnings == '':
+                warnings = warnings + """
+Fetchmail doesn't know anything special about this server type.
+
+"""
+
+            # Display success window with warnings
+            title = "Autoprobe of " + realhost + " succeeded"
+            confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
+            self.protocol.set(protocol)
+        confwin.title(title) 
+        confwin.iconname(title)
+        Label(confwin, text=title).pack()
+        Message(confwin, text=confirm, width=600).pack()
+        Button(confwin, text='Done', 
+                   command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
+        
 #
 # User editing stuff
 #
@@ -853,9 +1460,9 @@ that may differ between individual
 users on your site.
 
 Once you have a user configuration set
-up as you like it, you can select `Save' to
-store it in the server list maintained in
-the main configuration window.
+up as you like it, you can select `OK' to
+store it in the user list maintained in
+the site configuration window.
 
 If you wish to discard the changes you have
 made to user options, select `Quit'.
@@ -874,45 +1481,70 @@ local name, messages will be retrieved
 in multidrop mode.  This complicates
 the configuration issues; see the manual
 page section on multidrop mode.
+
+Warning: Be careful with local names
+such as foo@bar.com, as that can cause
+the mail to be sent to foo@bar.com instead
+of sending it to your local system.
 """}
 
 class UserEdit(Frame, MyWidget):
-    def __init__(self, username, server):
-        self.server = server
-       self.user = None
-       for user in server.users:
-           if user.remote == username:
-               self.user = user
-       if self.user == None:
-           self.user = User()
-           self.user.remote = user
-           self.user.localnames = [user]
-           server.users.append(self.user)
+    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)
 
     def edit(self, mode, master=None):
-       Frame.__init__(self, master)
-       Pack.config(self)
-       self.master.title('Fetchmail user ' + self.user.remote
-                          + ' querying ' + self.server.pollname);
-       self.master.iconname('Fetchmail user ' + self.user.remote);
-       self.post(User, 'user')
-       self.makeWidgets(mode, self.server.pollname)
-#      self.grab_set()
-#      self.focus_set()
-#      self.wait_window()
+        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
+        make_icon_window(self, fetchmail_icon)
+#       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.
+        if self.parent.subwidgets.has_key(self.user.remote):
+            del self.parent.subwidgets[self.user.remote]
+        Widget.destroy(self.master)
 
     def nosave(self):
-       if ConfirmQuit(self, 'user option editing'):
-           Widget.destroy(self.master)
+        if ConfirmQuit(self, 'user option editing'):
+            self.destruct()
 
     def save(self):
-       self.fetch(User, 'user')
-       Widget.destroy(self.master)
+        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?",
+            bitmap = 'question',
+            strings = ('Yes', 'No'),
+            default = 1).num == 0:
+                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);
@@ -922,30 +1554,58 @@ 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:
+            sslwin = Frame(leftwin, relief=RAISED, bd=5)
+            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)
+            LabeledEntry(sslwin, 'SSL certificate:',
+                         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)
+            LabeledEntry(sslwin, 'SSL key fingerprint:',
+                         self.sslfingerprint, '14').pack(side=TOP, fill=X)
+            sslwin.pack(fill=X, anchor=N)
+
         names = Frame(leftwin, relief=RAISED, bd=5)
         Label(names, text="Local names").pack(side=TOP)
         ListEdit("New name: ",
-                     self.user.localnames, None, names, localhelp)
+                     self.user.localnames, None, None, names, localhelp)
         names.pack(fill=X, anchor=N)
 
         if mode != 'novice':
             targwin = Frame(leftwin, relief=RAISED, bd=5)
             Label(targwin, text="Forwarding Options").pack(side=TOP)
             Label(targwin, text="Listeners to forward to").pack(side=TOP)
-            ListEdit("New listener:", self.user.smtphunt, None, targwin, None)
+            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)
             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)
-            LabeledEntry(targwin, 'Listener spam-block code:',
-                    self.antispam, '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)
+            LabeledEntry(targwin, 'Listener spam-block codes:',
+                     self.antispam, '26').pack(side=TOP, fill=X)
+            LabeledEntry(targwin, 'Pass-through properties:',
+                     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)
 
         if mode != 'novice':
@@ -954,46 +1614,55 @@ 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)
-            Checkbutton(optwin, text="Strip CR from end of eacgh line",
-                   variable=self.stripcr).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)
             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)
-       optwin.pack(fill=X)
+                    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)
 
         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)
             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)
-            LabeledEntry(limwin, 'Interval between expunges (IMAP):',
-                     self.expunge, '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)
             limwin.pack(fill=X)
 
-            foldwin = Frame(rightwin, relief=RAISED, bd=5)
-            Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
-            ListEdit("New folder:", self.user.mailboxes, None, foldwin, None)
-            foldwin.pack(fill=X, anchor=N)
+            if self.parent.server.protocol == 'IMAP':
+                foldwin = Frame(rightwin, relief=RAISED, bd=5)
+                Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
+                ListEdit("New folder:", self.user.mailboxes,
+                         None, None, foldwin, None)
+                foldwin.pack(fill=X, anchor=N)
 
         if mode != 'novice':
             rightwin.pack(side=LEFT)
@@ -1006,120 +1675,352 @@ class UserEdit(Frame, MyWidget):
 # (but not both at once; it disappears when one is selected).
 #
 
-class MainWindow(Frame):
-    def __init__(self, master=None):
-       Frame.__init__(self, master)
-       self.master.title('fetchmail configurator main');
-       self.master.iconname('fetchmail configurator main');
-       Pack.config(self)
-       Label(self,
-               text='Configurator Main Window', 
-               bd=2).pack(side=TOP, pady=10)
-
-       Message(self, text="""
+class Configurator(Frame):
+    def __init__(self, outfile, master, onexit, parent):
+        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
+        make_icon_window(self, fetchmail_icon)
+
+        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="""
-Or you can just select `Quit' to leave the configurator now.
+        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.destroy()
-       ConfigurationEdit(Fetchmailrc).edit('novice')
+        self.master.destroy()
+        ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
 
     def expert(self):
-       self.destroy()
-       ConfigurationEdit(Fetchmailrc).edit('expert')
+        self.master.destroy()
+        ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
+
+    def leave(self):
+        self.master.destroy()
+        self.onexit()
+
+# Run a command in a scrolling text widget, displaying its output
+
+class RunWindow(Frame):
+    def __init__(self, command, master, parent):
+        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
+        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.pack(side=TOP)
+
+        Button(self, text='Quit', fg='blue', command=self.leave).pack()
+
+        self.update()   # Draw widget before executing fetchmail
+
+        child_stdout = os.popen(command + " 2>&1", "r")
+        while 1:
+            ch = child_stdout.read(1)
+            if not ch:
+                break
+            self.textwidget.insert(END, ch)
+        self.textwidget.insert(END, "Done.")
+        self.textwidget.see(END);
+
+    def leave(self):
+        Widget.destroy(self.master)
+
+# Here's where we choose either configuration or launching
+
+class MainWindow(Frame):
+    def __init__(self, outfile, master=None):
+        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
+        make_icon_window(self, fetchmail_icon)
+        self.debug = 0
+
+        ## Test icon display with the following:
+        # icon_image = PhotoImage(data=fetchmail_icon)
+        # Label(self, image=icon_image).pack(side=TOP, pady=10)
+        # self.keepalive.append(icon_image)
+
+        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.pack()
+
+        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()
+
+        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()
+
+        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()
+
+    def configure(self):
+        self.configbutton.configure(state=DISABLED)
+        Configurator(self.outfile, Toplevel(),
+                     lambda self=self: self.configbutton.configure(state=NORMAL),
+                     self)
+    def test(self):
+        RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
+
+    def run(self):
+        RunWindow("fetchmail -d0", Toplevel(), self)
 
     def leave(self):
         self.quit()
 
-#
-# And this is the main sequence
-#
+# Functions for turning a dictionary into an instantiated object tree.
 
 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):
 # Initialize a class object of given type from a conformant dictionary.
-    class_sig = toclass.__dict__.keys(); class_sig.sort()
-    dict_keys = fromdict.keys(); dict_keys.sort()
+    for fld in fromdict.keys():
+        if not fld in dictmembers:
+            dictmembers.append(fld)
+# The `optional' fields are the ones we can ignore for purposes of
+# conformability checking; they'll still get copied if they are
+# present in the dictionary.
+    optional = ('interface', 'monitor',
+                'netsec',
+                'ssl', 'sslkey', 'sslcert', 'sslproto', 'sslcertck',
+                '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 "Conformability error"
-#      print "Class signature: " + `class_sig`
-#      print "Dictionary keys: " + `dict_keys`
-       print "Not matched in class signature: " + `setdiff(class_sig, common)`
-       print "Not matched in dictionary keys: " + `setdiff(dict_keys, common)`
-       sys.exit(1)
+        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)
     else:
-       for x in dict_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:  
+#
+# First, call `fetchmail --configdump' and trap the output in a tempfile.
+# This should fill it with a Python initializer for a variable `fetchmailrc'.
+# Run execfile on the file to pull fetchmailrc into Python global space.
+# You don't want static data, though; you want, instead, a tree of objects
+# with the same data members and added appropriate methods.
+#
+# This is what the copy_instance function() is for.  It tries to copy a
+# dictionary field by field into a class, aborting if the class and dictionary
+# have different data members (except for any typemap member in the class;
+# that one is strictly for use by the MyWidget supperclass).
+#
+# Once the object tree is set up, require user to choose novice or expert
+# mode and instantiate an edit object for the configuration.  Class methods
+# will take it all from there.
+#
+# Options (not documented because they're for fetchmailconf debuggers only):
+# -d: Read the configuration and dump it to stdout before editing.  Dump
+#     the edited result to stdout as well.
+# -f: specify the run control file to read.
 
 if __name__ == '__main__': 
 
+    if not os.environ.has_key("DISPLAY"):
+        print "fetchmailconf must be run under X"
+        sys.exit(1)
+
+    fetchmail_icon = """
+R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
+GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
+xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
+CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
+jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
+czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
+5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
+Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
+/8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
+OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
+1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
+jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
+3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
+zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
+sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
+mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
+zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
+ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
+FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
+aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
+3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
+VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
+deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
+a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
+dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
+Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
+Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
+CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
+YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
+OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
+xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
+gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
+"""
+# The base64 data in the string above was generated by the following procedure:
+#
+# import base64
+# print base64.encodestring(open("fetchmail.gif", "rb").read())
+#
+
+    # Process options
+    (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
+    dump = rcfile = None;
+    for (switch, val) in options:
+        if (switch == '-d'):
+            dump = TRUE
+        elif (switch == '-f'):
+            rcfile = val
+
+    # Get client host's FQDN
+    hostname = socket.gethostbyaddr(socket.gethostname())[0]
+
     # Compute defaults
     ConfigurationDefaults = Configuration()
     ServerDefaults = Server()
     UserDefaults = User()
 
-    # Read the existing configuration
-    tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
-    os.system("fetchmail --configdump >" + tmpfile)
-    execfile(tmpfile)
+    # Read the existing configuration.  We set the umask to 077 to make sure
+    # that group & other read/write permissions are shut off -- we wouldn't
+    # want crackers to snoop password information out of the tempfile.
+    tmpfile = tempfile.mktemp()
+    if rcfile:
+        cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
+    else:
+        cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
+        
+    try:
+        s = os.system(cmd)
+        if s != 0:
+            print "`" + cmd + "' run failure, status " + `s`
+            raise SystemExit
+    except:
+        print "Unknown error while running fetchmail --configdump"
+        os.remove(tmpfile)
+        sys.exit(1)
+
+    try:
+        execfile(tmpfile)
+    except:
+        print "Can't read configuration output of fetchmail --configdump."
+        os.remove(tmpfile)
+        sys.exit(1)
+        
     os.remove(tmpfile)
 
     # The tricky part -- initializing objects from the configuration global
-    # `Configuration' is the top level of the object tree we're going to mung 
+    # `Configuration' is the top level of the object tree we're going to mung.
+    # The dictmembers list is used to track the set of fields the dictionary
+    # contains; in particular, we can use it to tell whether things like the
+    # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
+    dictmembers = []
     Fetchmailrc = Configuration()
     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 len(sys.argv) > 1 and sys.argv[1] == '-d':
+    if dump:
         print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
-        sys.exit(0)
+
+    # The theory here is that -f alone sets the rcfile location,
+    # but -d and -f together mean the new configuration should go to stdout.
+    if not rcfile and not dump:
+        rcfile = os.environ["HOME"] + "/.fetchmailrc"
 
     # OK, now run the configuration edit
-    MainWindow().mainloop()
+    root = MainWindow(rcfile)
+    root.mainloop()
 
 # The following sets edit modes for GNU EMACS
 # Local Variables: