]> Pileus Git - ~andy/fetchmail/blobdiff - fetchmailconf
Bug fixes and internationalization improvements.
[~andy/fetchmail] / fetchmailconf
index 50eddd4267baf83731ebad8e32e50de9885a25bb..15ccc2fbf02234e060d0a15ecbd43983ae4d6a71 100755 (executable)
@@ -4,10 +4,7 @@
 # by Eric S. Raymond, <esr@snark.thyrsus.com>.
 # Requires Python with Tkinter, and the following OS-dependent services:
 #      posix, posixpath, socket
-#
-# TO DO: Arrange for save and quit buttons to clean up all frames dependent
-# on the current ones.
-version = "1.1"
+version = "1.28"
 
 from Tkinter import *
 from Dialog import *
@@ -19,18 +16,22 @@ import sys, time, os, string, socket, getopt
 class Configuration:
     def __init__(self):
        self.poll_interval = 0          # Normally, run in foreground
-       self.syslog = FALSE             # Use syslogd for logging?
        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.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'),
-           ('syslog',          'Boolean'),
            ('logfile',         'String'),
            ('idfile',          'String'),
            ('postmaster',      'String'),
+           ('bouncemail',      'Boolean'),
+           ('properties',      'String'),
+           ('syslog',          'Boolean'),
            ('invisible',       'Boolean'))
 
     def __repr__(self):
@@ -43,12 +44,24 @@ class Configuration:
            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.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) + "]"
 
@@ -61,7 +74,7 @@ class Server:
        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.preauth = '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
@@ -71,6 +84,8 @@ class Server:
        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.users = []                 # List of user entries for site
        Server.typemap = (
@@ -79,10 +94,9 @@ class Server:
            ('active',    'Boolean'),
            ('interval',  'Int'),
            ('protocol',  'String'),
-           ('interval',  'Int'),
            ('port',      'Int'),
            ('uidl',      'Boolean'),
-           ('auth',      'String'),
+           ('preauth',      'String'),
            ('timeout',   'Int'),
            ('envelope',  'String'),
            ('envskip',   'Int'),
@@ -92,72 +106,80 @@ class Server:
            # leave localdomains out
            ('interface', 'String'),
            ('monitor',   'String'),
+           ('plugin',   'String'),
+           ('plugout',  'String'),
            ('netsec',   'String'))
 
     def dump(self, folded):
-       str = ""
-       if self.active:   str = str + "poll"
-       else:             str = str + "skip"
-       str = str + (" " + self.pollname)
+       res = ""
+       if self.active:   res = res + "poll"
+       else:             res = res + "skip"
+       res = res + (" " + self.pollname)
        if self.via:
-           str = str + (" via \"%s\"\n" % (self.via,));
+           res = res + (" via " + str(self.via) + "\n");
        if self.protocol != ServerDefaults.protocol:
-           str = str + " with proto " + self.protocol 
+           res = res + " with proto " + self.protocol 
        if self.port != defaultports[self.protocol] and self.port != 0:
-           str = str + " port " + `self.port`
+           res = res + " port " + `self.port`
        if self.timeout != ServerDefaults.timeout:
-           str = str + " timeout " + `self.timeout`
+           res = res + " timeout " + `self.timeout`
        if self.interval != ServerDefaults.interval: 
-           str = str + " interval " + `self.interval`
+           res = res + " interval " + `self.interval`
        if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
            if self.envskip:
-               str = str + " envelope " + self.envskip + " " + self.envelope
+               res = res + " envelope " + `self.envskip` + " " + self.envelope
            else:
-               str = str + " envelope " + self.envelope
+               res = res + " envelope " + self.envelope
        if self.qvirtual:
-           str = str + (" qvirtual \"%s\"\n" % (self.qvirtual,));
-       if self.auth != ServerDefaults.auth:
-           str = str + " auth " + self.auth
+           res = res + (" qvirtual " + str(self.qvirtual) + "\n");
+       if self.preauth != ServerDefaults.preauth:
+           res = res + " preauth " + self.preauth
        if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
-           str = str + " and options"
+           res = res + " and options"
        if self.dns != ServerDefaults.dns:
-           str = str + flag2str(self.dns, 'dns')
+           res = res + flag2str(self.dns, 'dns')
        if self.uidl != ServerDefaults.uidl:
-           str = str + flag2str(self.uidl, 'uidl')
-       if folded:        str = str + "\n    "
-       else:             str = str + " "
+           res = res + flag2str(self.uidl, 'uidl')
+       if folded:        res = res + "\n    "
+       else:             res = res + " "
 
        if self.aka:
-            str = str + "aka"
+            res = res + "aka"
             for x in self.aka:
-               str = str + " " + x
-       if self.aka and self.localdomains: str = str + " "
+               res = res + " " + x
+       if self.aka and self.localdomains: res = res + " "
        if self.localdomains:
-            str = str + ("localdomains")
+            res = res + ("localdomains")
             for x in self.localdomains:
-               str = str + " " + x
+               res = res + " " + x
         if (self.aka or self.localdomains):
            if folded:
-               str = str + "\n    "
+               res = res + "\n    "
            else:
-               str = str + " "
+               res = res + " "
 
        if self.interface:
-            str = str + "interface \"" + self.interface + "\""
+            res = res + "interface " + str(self.interface)
        if self.monitor:
-            str = str + "monitor \"" + self.monitor + "\""
+            res = res + "monitor " + str(self.monitor)
        if self.netsec:
-            str = str + "netsec \"" + self.netsec + "\""
+            res = res + "netsec " + str(self.netsec)
        if self.interface or self.monitor or self.netsec:
            if folded:
-               str = str + "\n"
+               res = res + "\n"
 
-       if str[-1] == " ": str = str[0:-1]
+       if res[-1] == " ": res = res[0:-1]
 
        for user in self.users:
-           str = str + repr(user)
-       str = str + "\n"
-       return str;
+           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)
@@ -167,16 +189,24 @@ class Server:
 
 class User:
     def __init__(self):
-       self.remote = os.environ["USER"]# Remote username
+        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.smtpaddress = None;        # Append this to MAIL FROM line
+       self.smtpaddress = None         # Append this to MAIL FROM line
        self.preconnect = None          # Connection setup
        self.postconnect = None         # Connection wrapup
        self.mda = None                 # Mail Delivery Agent
-       self.antispam = 571;            # Listener's spam-block code
+       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
@@ -186,10 +216,17 @@ class User:
        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 = 1                # Interval between expunges (IMAP)
+       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.properties = None         # Extension properties
        User.typemap = (
            ('remote',      'String'),
            # leave out mailboxes and localnames
@@ -199,7 +236,9 @@ class User:
            ('preconnect',  'String'),
            ('postconnect', 'String'),
            ('mda',         'String'),
-           ('antispam',    'Int'),
+           ('bsmtp',       'String'),
+            ('lmtp',        'Boolean'),
+           ('antispam',    'String'),
            ('keep',        'Boolean'),
            ('flush',       'Boolean'),
            ('fetchall',    'Boolean'),
@@ -209,21 +248,28 @@ class User:
            ('pass8bits',   'Boolean'),
            ('mimedecode',  'Boolean'),
            ('dropstatus',  'Boolean'),
+            ('dropdelivered', 'Boolean'),
+           ('idle',        'Boolean'),
            ('limit',       'Int'),
+           ('warnings',    'Int'),
            ('fetchlimit',  'Int'),
            ('batchlimit',  'Int'),
-           ('expunge',     'Int'))
+           ('expunge',     'Int'),
+           ('ssl',         'Boolean'),
+           ('sslkey',      'String'),
+           ('sslcert',     'String'),
+            ('properties',  'String'))
 
     def __repr__(self):
-       str = "    "
-       str = str + "user \"" + self.remote + "\" there ";
+       res = "    "
+       res = res + "user " + `self.remote` + " there ";
        if self.password:
-            str = str + "with password \"" + self.password + '" '
+            res = res + "with password " + `self.password` + " "
        if self.localnames:
-            str = str + "is"
+            res = res + "is"
             for x in self.localnames:
-               str = str + " " + x
-            str = str + " here"
+               res = res + " " + x
+            res = res + " here"
        if (self.keep != UserDefaults.keep
                or self.flush != UserDefaults.flush
                or self.fetchall != UserDefaults.fetchall
@@ -232,56 +278,72 @@ class User:
                or self.stripcr != UserDefaults.stripcr 
                or self.pass8bits != UserDefaults.pass8bits
                or self.mimedecode != UserDefaults.mimedecode
-               or self.dropstatus != UserDefaults.dropstatus):
-           str = str + " options"
+               or self.dropstatus != UserDefaults.dropstatus
+               or self.dropdelivered != UserDefaults.dropdelivered
+               or self.idle != UserDefaults.idle):
+           res = res + " options"
        if self.keep != UserDefaults.keep:
-           str = str + flag2str(self.keep, 'keep')
+           res = res + flag2str(self.keep, 'keep')
        if self.flush != UserDefaults.flush:
-           str = str + flag2str(self.flush, 'flush')
+           res = res + flag2str(self.flush, 'flush')
        if self.fetchall != UserDefaults.fetchall:
-           str = str + flag2str(self.fetchall, 'fetchall')
+           res = res + flag2str(self.fetchall, 'fetchall')
        if self.rewrite != UserDefaults.rewrite:
-           str = str + flag2str(self.rewrite, 'rewrite')
+           res = res + flag2str(self.rewrite, 'rewrite')
        if self.forcecr != UserDefaults.forcecr:
-           str = str + flag2str(self.forcecr, 'forcecr')
+           res = res + flag2str(self.forcecr, 'forcecr')
        if self.stripcr != UserDefaults.stripcr:
-           str = str + flag2str(self.stripcr, 'stripcr')
+           res = res + flag2str(self.stripcr, 'stripcr')
        if self.pass8bits != UserDefaults.pass8bits:
-           str = str + flag2str(self.pass8bits, 'pass8bits')
+           res = res + flag2str(self.pass8bits, 'pass8bits')
        if self.mimedecode != UserDefaults.mimedecode:
-           str = str + flag2str(self.mimedecode, 'mimedecode')
+           res = res + flag2str(self.mimedecode, 'mimedecode')
        if self.dropstatus != UserDefaults.dropstatus:
-           str = str + flag2str(self.dropstatus, '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:
-           str = str + " limit " + `self.limit`
+           res = res + " limit " + `self.limit`
+       if self.warnings != UserDefaults.warnings:
+           res = res + " warnings " + `self.warnings`
        if self.fetchlimit != UserDefaults.fetchlimit:
-           str = str + " fetchlimit " + `self.fetchlimit`
+           res = res + " fetchlimit " + `self.fetchlimit`
        if self.batchlimit != UserDefaults.batchlimit:
-           str = str + " batchlimit " + `self.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.expunge != UserDefaults.expunge:
-           str = str + " expunge " + `self.expunge`
-        str = str + "\n"
+           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 != []:
-            str = str + "    smtphost "
+            res = res + "    smtphost "
             for x in trimmed:
-                str = str + " " + x
-                str = str + "\n"
+                res = res + " " + x
+                res = res + "\n"
        if self.mailboxes:
-            str = str + "    folder"
+            res = res + "    folder"
             for x in self.mailboxes:
-               str = str + " " + x
-            str = str + "\n"
-        for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda'):
+               res = res + " " + x
+            res = res + "\n"
+        for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
             if getattr(self, fld):
-                str = str + " %s \"%s\"\n" % (fld, `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:
-            str = str + "    antispam " + `self.antispam` + "\n"
-       return str;
+            res = res + "    antispam " + self.antispam + "\n"
+       return res;
 
     def __str__(self):
        return "[User: " + repr(self) + "]"
@@ -296,12 +358,11 @@ defaultports = {"auto":0,
                 "APOP":110,
                 "KPOP":1109,
                 "IMAP":143,
+               "IMAP-GSS":143,
                "IMAP-K4":143,
                "ETRN":25}
 
-protolist = ("auto", "POP2", "POP3", "APOP", "KPOP", "IMAP", "IMAP-K4", "ETRN")
-
-authlist = ("password", "kerberos")
+preauthlist = ("password", "kerberos", "ssh")
 
 listboxhelp = {
     'title' : 'List Selection Help',
@@ -356,11 +417,18 @@ 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:
@@ -378,8 +446,9 @@ def make_icon_window(base, image):
 
 class ListEdit(Frame):
 # edit a list of values (duplicates not allowed) with a supplied editor hook 
-    def __init__(self, newlegend, list, editor, master, helptxt):
+    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
@@ -397,9 +466,9 @@ class ListEdit(Frame):
             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.config(yscrollcommand=scroll.set)
        self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
-       scroll.config(command=self.listwidget.yview, relief=SUNKEN)
+       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);
@@ -419,15 +488,18 @@ class ListEdit(Frame):
        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('')
+        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()
@@ -437,17 +509,21 @@ class ListEdit(Frame):
            index = select[0]
            if index and self.editor:
                label = self.listwidget.get(index);
-               apply(self.editor, (label,))
+                if self.editor:
+                    apply(self.editor, (label,))
 
     def deleteItem(self):
        select = self.listwidget.curselection()
        if not select:
            helpwin(listboxhelp)
        else:
-           index = string.atoi(select[0])
-           if index:
-               self.listwidget.delete(index)
-               if self.list != None: del self.list[index]
+            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, 
@@ -458,10 +534,10 @@ def ConfirmQuit(frame, context):
                 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)
@@ -554,6 +630,10 @@ Postmaster
         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.
+
 Invisible
         If false (the default) fetchmail generates a Received line into
         each message and generates a HELO from the machine it is running on.
@@ -577,27 +657,42 @@ This will take you to a site configuration dialogue.
 
 
 class ConfigurationEdit(Frame, MyWidget):
-    def __init__(self, configuration, outfile, master=None):
+    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');
+        self.master.protocol('WM_DELETE_WINDOW', self.nosave)
         self.keepalive = []    # Use this to anchor the PhotoImage object
-        make_icon_window(self, fetchmail_gif)
+        make_icon_window(self, fetchmail_icon)
        Pack.config(self)
         self.post(Configuration, 'configuration')
 
        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,
@@ -606,12 +701,27 @@ class ConfigurationEdit(Frame, MyWidget):
 
         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)
+
             sf = Frame(gf)
             Checkbutton(sf,
                {'text':'Log to syslog?',
@@ -628,9 +738,6 @@ class ConfigurationEdit(Frame, MyWidget):
             # Set the idfile
             log = LabeledEntry(gf, '     Idfile:', self.idfile, '14')
             log.pack(side=RIGHT, anchor=E)
-            # Set the postmaster
-            log = LabeledEntry(gf, '     Postmaster:', self.postmaster, '14')
-            log.pack(side=RIGHT, anchor=E)
 
        gf.pack(fill=X)
 
@@ -641,16 +748,24 @@ class ConfigurationEdit(Frame, MyWidget):
              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()),
+               lambda site, self=self: self.server_edit(site),
+               lambda site, self=self: self.server_delete(site),
                 lf, remotehelp)
        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()
+           self.destruct()
 
     def save(self):
+        for sitename in self.subwidgets.keys():
+            self.subwidgets[sitename].save()
         self.fetch(Configuration, 'configuration')
         fm = None
         if not self.outfile:
@@ -665,8 +780,11 @@ class ConfigurationEdit(Frame, MyWidget):
         if fm:
             fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
             fm.write(`self.configuration`)
-            os.chmod(self.outfile, 0600)
-            self.quit()
+            if self.outfile:
+                fm.close()
+            if fm != sys.stdout:
+                os.chmod(self.outfile, 0600)
+            self.destruct()
 
 #
 # Server editing stuff.
@@ -694,7 +812,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.
 
@@ -756,23 +874,26 @@ 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 `netsec' option will be configurable only if fetchmail
 was compiled with IPV6 support.  If you need to use it,
@@ -803,16 +924,18 @@ user's options on that site.
 """}
 
 class ServerEdit(Frame, MyWidget):
-    def __init__(self, host, servers):
+    def __init__(self, host, parent):
+        self.parent = parent
        self.server = None
-       for site in servers:
+        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
-               servers.append(self.server)
+               parent.configuration.servers.append(self.server)
 
     def edit(self, mode, master=None):
        Frame.__init__(self, master)
@@ -822,24 +945,42 @@ class ServerEdit(Frame, MyWidget):
        self.post(Server, 'server')
        self.makeWidgets(self.server.pollname, mode)
         self.keepalive = []    # Use this to anchor the PhotoImage object
-        make_icon_window(self, fetchmail_gif)
+        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)
+           self.destruct()
 
     def save(self):
        self.fetch(Server, 'server')
-       Widget.destroy(self.master)
+        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 self.port.get() == 0:
+            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)
 
@@ -860,6 +1001,23 @@ class ServerEdit(Frame, MyWidget):
               command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
             ctlwin.pack(fill=X)
 
+        # 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 'imap-gss' in feature_options:
+            protolist.append("IMAP-GSS")
+        if 'imap-k4' in feature_options:
+            protolist.append("IMAP-K4")
+        if 'etrn' in feature_options:
+            protolist.append("ETRN")
+
        protwin = Frame(leftwin, relief=RAISED, bd=5)
        Label(protwin, text="Protocol").pack(side=TOP)
        ButtonBar(protwin, '',
@@ -882,7 +1040,8 @@ class ServerEdit(Frame, MyWidget):
        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)
 
@@ -902,38 +1061,38 @@ class ServerEdit(Frame, MyWidget):
             Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
                    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)
 
-            if 'interface' in dictmembers or 'monitor' in dictmembers or 'netsec' in dictmembers:
+            if os_type == 'linux' or os_type == '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 'interface' in dictmembers:
-                    LabeledEntry(secwin, 'Interface to check before poll:',
+                #      ButtonBar(secwin, 'Preauthorization mode:',
+                #                self.preauth, preauthlist, 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 'monitor' in dictmembers:
-                    LabeledEntry(secwin, 'IP range to monitor:',
+                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 dictmembers:
+                if 'netsec' in feature_options or 'netsec' in dictmembers:
                     LabeledEntry(secwin, 'IPV6 security options:',
                         self.netsec, leftwidth).pack(side=TOP, fill=X)
-                    Button(secwin, text='Help', fg='blue',
-                           command=lambda: helpwin(sechelp)).pack(side=RIGHT)
+                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:892
+        # 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 != None:
+        if self.server.via:
             realhost = self.server.via
         else:
             realhost = self.server.pollname
@@ -973,16 +1132,121 @@ you'll have to build it from sources yourself with the configure
 switch --enable-POP2.
 
 """
-            if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.003") > 0:
+
+### 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.
+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.
 
 """
-            if string.find(greetline, "usa.net") > 0:
+           if string.find(greetline, "David POP3 Server") > 0:
+                warnings = warnings + """
+This POP3 serrver is badly broken.  You should get rid of it -- and the
+brain-dead NT 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, "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
@@ -996,12 +1260,66 @@ Therefore, it is strongly recommended that you turn on `fetchall' on all
 user entries associated with this server.  
 
 """
-            if string.find(greetline, "QPOP") > 0:
+            if string.find(greetline, " Novonyx POP3") > 0:
                 warnings = warnings + """
-This appears to be a version of Eudora qpopper.  That's good.  Fetchmail
-knows all about qpopper.
+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 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)
+rejects it.
+
+"""
+            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 + """
@@ -1009,14 +1327,6 @@ 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, "csi.com") > 0:
-                warnings = warnings + """
-It appears you're talking to CompuServe.  You can use their special RPA
-service for authentication, but only if your fetchmail -V output's first
-line contains the string "RPA".  This is not included in stock fetchmail
-binaries; to compile it in, rebuild from sources with the configure
-option --enable-RPA.
 """
             if string.find(greetline, "IMAP2bis") > 0:
                 warnings = warnings + """
@@ -1028,10 +1338,21 @@ 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
@@ -1043,8 +1364,7 @@ Fetchmail doesn't know anything special about this server type.
         Label(confwin, text=title).pack()
         Message(confwin, text=confirm, width=600).pack()
         Button(confwin, text='Done', 
-                   command=lambda x=confwin: Widget.destroy(x),
-                   relief=SUNKEN, bd=2).pack()
+                   command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
         
 #
 # User editing stuff
@@ -1059,9 +1379,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'.
@@ -1083,39 +1403,46 @@ page section on multidrop mode.
 """}
 
 class UserEdit(Frame, MyWidget):
-    def __init__(self, username, server):
-        self.server = server
+    def __init__(self, username, parent):
+        self.parent = parent
        self.user = None
-       for user in server.users:
+       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]
-           server.users.append(self.user)
+           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);
+                          + ' querying ' + self.parent.server.pollname);
        self.master.iconname('Fetchmail user ' + self.user.remote);
        self.post(User, 'user')
-       self.makeWidgets(mode, self.server.pollname)
+       self.makeWidgets(mode, self.parent.server.pollname)
         self.keepalive = []    # Use this to anchor the PhotoImage object
-        make_icon_window(self, fetchmail_gif)
+        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)
+            self.destruct()
 
     def save(self):
        self.fetch(User, 'user')
-       Widget.destroy(self.master)
+       self.destruct()
 
     def makeWidgets(self, mode, servername):
        dispose_window(self,
@@ -1133,17 +1460,28 @@ class UserEdit(Frame, MyWidget):
                      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)
+            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)
             LabeledEntry(targwin, 'Append to MAIL FROM line:',
                     self.smtpaddress, '26').pack(side=TOP, fill=X)
             LabeledEntry(targwin, 'Connection setup command:',
@@ -1152,8 +1490,14 @@ class UserEdit(Frame, MyWidget):
                     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:',
+            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':
@@ -1175,7 +1519,7 @@ class UserEdit(Frame, MyWidget):
                    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",
+            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)
@@ -1183,6 +1527,8 @@ class UserEdit(Frame, MyWidget):
                    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)
+            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':
@@ -1190,18 +1536,25 @@ class UserEdit(Frame, MyWidget):
             Label(limwin, text="Resource Limits").pack(side=TOP)
             LabeledEntry(limwin, 'Message size limit:',
                      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)
             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)
+            if self.parent.server.protocol != 'ETRN':
+                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 in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
+                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)
@@ -1214,18 +1567,17 @@ class UserEdit(Frame, MyWidget):
 # (but not both at once; it disappears when one is selected).
 #
 
-class MainWindow(Frame):
-    def __init__(self, outfile, master=None):
+class Configurator(Frame):
+    def __init__(self, outfile, master, onexit, parent):
        Frame.__init__(self, master)
         self.outfile = outfile
-       self.master.title('fetchmail configurator main');
-       self.master.iconname('fetchmail configurator main');
+        self.onexit = onexit
+        self.parent = parent
+       self.master.title('fetchmail configurator');
+       self.master.iconname('fetchmail configurator');
        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_gif)
+        make_icon_window(self, fetchmail_icon)
 
        Message(self, text="""
 Use `Novice Configuration' for basic fetchmail setup;
@@ -1243,17 +1595,123 @@ including multiple-site or multidrop connections.
                                fg='blue', command=self.expert).pack()
 
        Message(self, text="""
-Or you can just select `Quit' to leave the configurator now.
+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()
+        master.protocol("WM_DELETE_WINDOW", self.leave)
 
     def novice(self):
-       self.destroy()
-       ConfigurationEdit(Fetchmailrc, self.outfile).edit('novice')
+       self.master.destroy()
+       ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
 
     def expert(self):
-       self.destroy()
-       ConfigurationEdit(Fetchmailrc, self.outfile).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()
@@ -1284,7 +1742,7 @@ def copy_instance(toclass, fromdict):
 # 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');
+    optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
     class_sig = setdiff(toclass.__dict__.keys(), optional)
     class_sig.sort()
     dict_keys = setdiff(fromdict.keys(), optional)
@@ -1304,7 +1762,7 @@ def copy_instance(toclass, fromdict):
             print "Not matched in dictionary keys: " + `diff`
        sys.exit(1)
     else:
-       for x in dict_keys:
+       for x in fromdict.keys():
            setattr(toclass, x, fromdict[x])
 
 #
@@ -1332,8 +1790,49 @@ def copy_instance(toclass, fromdict):
 
 if __name__ == '__main__': 
 
-    fetchmail_gif =\
-'R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY\012GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e\012xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC\012CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1\012jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l\012czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//\0125/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG\012Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3\012/8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx\012OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc\0121lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp\012jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl\0123qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA\012zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI\012sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH\012mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs\012zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS\012ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU\012FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA\012aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH\0123FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW\012VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe\012deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai\012a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd\012dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E\012Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI\012Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg\012CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24\012YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO\012OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY\012xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw\012gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7\012'
+    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:")
@@ -1352,12 +1851,14 @@ if __name__ == '__main__':
     ServerDefaults = Server()
     UserDefaults = User()
 
-    # Read the existing configuration
+    # 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 = "/tmp/fetchmailconf." + `os.getpid()`
     if rcfile:
-        cmd = "fetchmail -f " + rcfile + " --configdump >" + tmpfile
+        cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
     else:
-        cmd = "fetchmail --configdump >" + tmpfile
+        cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
         
     try:
         s = os.system(cmd)
@@ -1382,7 +1883,7 @@ if __name__ == '__main__':
     # `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, and netsec fields are present.
+    # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
     dictmembers = []
     Fetchmailrc = Configuration()
     copy_instance(Fetchmailrc, fetchmailrc)