]> Pileus Git - ~andy/fetchmail/blobdiff - fetchmailconf
Bug fixes and internationalization improvements.
[~andy/fetchmail] / fetchmailconf
index 98a05b9dd36d717a034742de8f758dac9f0e32ef..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.2"
+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,15 +189,23 @@ 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.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
@@ -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,6 +236,8 @@ class User:
            ('preconnect',  'String'),
            ('postconnect', 'String'),
            ('mda',         'String'),
+           ('bsmtp',       'String'),
+            ('lmtp',        'Boolean'),
            ('antispam',    'String'),
            ('keep',        'Boolean'),
            ('flush',       '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,7 +1132,10 @@ 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
@@ -983,7 +1145,108 @@ It is strongly recommended that you find a better POP3 server.  The fetchmail
 FAQ includes pointers to good ones.
 
 """
-            if string.find(greetline, "usa.net") > 0:
+# 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, "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
@@ -997,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 + """
@@ -1010,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 + """
@@ -1029,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
@@ -1044,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
@@ -1060,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'.
@@ -1084,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,
@@ -1134,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:',
@@ -1153,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, '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':
@@ -1176,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)
@@ -1184,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':
@@ -1191,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)
@@ -1215,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;
@@ -1244,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()
@@ -1285,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)
@@ -1305,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])
 
 #
@@ -1333,7 +1790,11 @@ def copy_instance(toclass, fromdict):
 
 if __name__ == '__main__': 
 
-    fetchmail_gif = """
+    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
@@ -1367,6 +1828,11 @@ 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:")
@@ -1385,12 +1851,14 @@ gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
     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)
@@ -1415,7 +1883,7 @@ gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
     # `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)