]> Pileus Git - ~andy/fetchmail/blobdiff - fetchmailconf.py
Attempt merging from 6.3.24.
[~andy/fetchmail] / fetchmailconf.py
index 571e18fbbc8ba26a9bb25c0765906c9f20e8c8b2..b4b0a1ae7facd4431a39b776e39cefb071b9ab5e 100755 (executable)
@@ -5,7 +5,7 @@
 # Matthias Andree <matthias.andree@gmx.de>
 # Requires Python with Tkinter, and the following OS-dependent services:
 #      posix, posixpath, socket
-version = "1.49"
+version = "1.58"
 
 from Tkinter import *
 from Dialog import *
@@ -22,6 +22,7 @@ class Configuration:
        self.postmaster = None          # No last-resort address, initially
        self.bouncemail = TRUE          # Bounce errors to users
        self.spambounce = FALSE         # Bounce spam errors
+       self.softbounce = TRUE          # Treat permanent error as temporary
        self.properties = None          # No exiguous properties
        self.invisible = FALSE          # Suppress Received line & spoof?
        self.syslog = FALSE             # Use syslogd for logging?
@@ -33,6 +34,7 @@ class Configuration:
            ('postmaster',      'String'),
            ('bouncemail',      'Boolean'),
            ('spambounce',      'Boolean'),
+           ('softbounce',      'Boolean'),
            ('properties',      'String'),
            ('syslog',    'Boolean'),
            ('invisible',       'Boolean'))
@@ -55,10 +57,16 @@ class Configuration:
            str = str + ("set spambounce\n")
        else:
            str = str + ("set no spambounce\n")
+       if self.softbounce:
+           str = str + ("set softbounce\n")
+       else:
+           str = str + ("set no softbounce\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"
+       if self.invisible:
+          str = str + ("set invisible\n")
        for site in self.servers:
            str = str + repr(site)
        return str
@@ -80,7 +88,6 @@ class Server:
        self.interval = 0               # Skip interval
        self.protocol = 'auto'          # Default to auto protocol
        self.service = None             # Service name to use
-       self.uidl = FALSE               # Don't use RFC1725 UIDLs by default
        self.auth = 'any'               # Default to password authentication
        self.timeout = 300              # 5-minute timeout
        self.envelope = 'Received'      # Envelope-address header
@@ -97,6 +104,8 @@ class Server:
        self.esmtpname = None           # ESMTP 2554 name
        self.esmtppassword = None       # ESMTP 2554 password
        self.tracepolls = FALSE         # Add trace-poll info to headers
+       self.badheader = FALSE          # Pass messages with bad headers on?
+       self.retrieveerror = 'abort'    # Policy when message retrieval errors encountered
        self.users = []                 # List of user entries for site
        Server.typemap = (
            ('pollname',  'String'),
@@ -105,7 +114,6 @@ class Server:
            ('interval',  'Int'),
            ('protocol',  'String'),
            ('service',   'String'),
-           ('uidl',      'Boolean'),
            ('auth',      'String'),
            ('timeout',   'Int'),
            ('envelope',  'String'),
@@ -121,7 +129,9 @@ class Server:
            ('esmtpname', 'String'),
            ('esmtppassword', 'String'),
            ('principal', 'String'),
-           ('tracepolls','Boolean'))
+           ('tracepolls','Boolean'),
+           ('badheader', 'Boolean'),
+           ('retrieveerror', 'String'))
 
     def dump(self, folded):
        res = ""
@@ -147,12 +157,9 @@ class Server:
            res = res + (" qvirtual " + str(self.qvirtual) + "\n");
        if self.auth != ServerDefaults.auth:
            res = res + " auth " + self.auth
-       if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
-           res = res + " and options"
        if self.dns != ServerDefaults.dns:
+           res = res + " and options"
            res = res + flag2str(self.dns, 'dns')
-       if self.uidl != ServerDefaults.uidl:
-           res = res + flag2str(self.uidl, 'uidl')
        if folded:      res = res + "\n    "
        else:        res = res + " "
 
@@ -189,6 +196,16 @@ class Server:
        if self.esmtppassword:
            res = res + " esmtppassword " + `self.esmtppassword`
        if self.interface or self.monitor or self.principal or self.plugin or self.plugout:
+           if folded:
+               res = res + "\n    "
+
+       if self.badheader:
+               res = res + "bad-header accept "
+       if self.retrieveerror == 'continue':
+               res = res + "retrieve-error continue "
+       if self.retrieveerror == 'markseen':
+               res = res + "retrieve-error markseen "
+       if self.badheader or self.retrieveerror != ServerDefaults.retrieveerror:
            if folded:
                res = res + "\n"
 
@@ -249,7 +266,7 @@ class User:
        self.warnings = 3600    # Size warning interval (see tunable.h)
        self.fetchlimit = 0     # Max messages fetched per batch
        self.fetchsizelimit = 100       # Max message sizes fetched per transaction
-       self.fastuidl = 10      # Do fast uidl 9 out of 10 times
+       self.fastuidl = 4       # Do fast uidl 3 out of 4 times
        self.batchlimit = 0     # Max message forwarded per batch
        self.expunge = 0        # Interval between expunges (IMAP)
        self.ssl = 0            # Enable Seccure Socket Layer
@@ -258,6 +275,7 @@ class User:
        self.sslproto = None    # Force SSL?
        self.sslcertck = 0      # Enable strict SSL cert checking
        self.sslcertpath = None # Path to trusted certificates
+       self.sslcommonname = None       # SSL CommonName to expect
        self.sslfingerprint = None      # SSL key fingerprint to check
        self.properties = None  # Extension properties
        User.typemap = (
@@ -297,6 +315,7 @@ class User:
            ('sslcert',     'String'),
            ('sslcertck',   'Boolean'),
            ('sslcertpath', 'String'),
+           ('sslcommonname', 'String'),
            ('sslfingerprint', 'String'),
            ('properties',  'String'))
 
@@ -371,6 +390,8 @@ class User:
            res = res +  flag2str(self.sslcertck, 'sslcertck')
        if self.sslcertpath and self.sslcertpath != UserDefaults.sslcertpath:
            res = res + " sslcertpath " + `self.sslcertpath`
+       if self.sslcommonname and self.sslcommonname != UserDefaults.sslcommonname:
+           res = res + " sslcommonname " + `self.sslcommonname`
        if self.sslfingerprint and self.sslfingerprint != UserDefaults.sslfingerprint:
            res = res + " sslfingerprint " + `self.sslfingerprint`
        if self.expunge != UserDefaults.expunge:
@@ -397,7 +418,7 @@ class User:
        if self.mailboxes:
             res = res + "    folder"
             for x in self.mailboxes:
-               res = res + " " + x
+               res = res + ' "%s"' % x
             res = res + "\n"
        for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
            if getattr(self, fld):
@@ -416,8 +437,7 @@ class User:
 #
 
 # IANA port assignments and bogus 1109 entry
-ianaservices = {"pop2":109,
-               "pop3":110,
+ianaservices = {"pop3":110,
                "1109":1109,
                "imap":143,
                "smtp":25,
@@ -425,16 +445,14 @@ ianaservices = {"pop2":109,
 
 # fetchmail protocol to IANA service name
 defaultports = {"auto":None,
-               "POP2":"pop2",
                "POP3":"pop3",
-               "APOP":"pop3",
                "KPOP":"1109",
                "IMAP":"imap",
                "ETRN":"smtp",
                "ODMR":"odmr"}
 
 authlist = ("any", "password", "gssapi", "kerberos", "ssh", "otp",
-           "msn", "ntlm")
+           "msn", "ntlm", "apop", "cram-md5")
 
 listboxhelp = {
     'title' : 'List Selection Help',
@@ -666,9 +684,12 @@ needed to create a simple fetchmail setup.  These include:
 
 4. A protocol to use (POP, IMAP, ETRN, etc.)
 
-5. A polling interval.
+5. A poll interval in seconds.
+   If 0, fetchmail will run in the foreground once when started.
+   If > 0, fetchmail will run in the background and start a new poll
+   cycle after the interval has elapsed.
 
-6. Options to fetch old messages as well as new, uor to suppress
+6. Options to fetch old messages as well as new, or to suppress
    deletion of fetched message.
 
 The novice-configuration code will assume that you want to forward mail
@@ -716,6 +737,14 @@ Send spam bounces?
        postmaster (depending on the "Bounces to sender?" option.  Otherwise,
        spam bounces are not sent (the default).
 
+Use soft bounces?
+       If this option is on, permanent delivery errors are treated as
+       temporary, i. e. mail is kept on the upstream server. Useful
+       during testing and after configuration changes, and on by
+       default.
+         If this option is off, permanent delivery errors delete
+       undeliverable mail from the upstream.
+
 Invisible
        If false (the default) fetchmail generates a Received line into
        each message and generates a HELO from the machine it is running on.
@@ -806,11 +835,18 @@ class ConfigurationEdit(Frame, MyWidget):
 
            sb = Frame(gf)
            Checkbutton(sb,
-               {'text':'send spam bounces?',
+               {'text':'Send spam bounces?',
                'variable':self.spambounce,
                'relief':GROOVE}).pack(side=LEFT, anchor=W)
            sb.pack(fill=X)
 
+           sb = Frame(gf)
+           Checkbutton(sb,
+               {'text':'Treat permanent errors as temporary?',
+               'variable':self.softbounce,
+               'relief':GROOVE}).pack(side=LEFT, anchor=W)
+           sb.pack(fill=X)
+
            sf = Frame(gf)
            Checkbutton(sf,
                {'text':'Log to syslog?',
@@ -926,6 +962,13 @@ the normal operation of fetchmail when it is run with no arguments.
 If it is off, fetchmail will only query this host when it is given as
 a command-line argument.
 
+The `Retrieve Error Policy' specifies how server errors during
+message retrieval are handled.  The default behaviour is to abort the
+current session.  Both the continue and markseen options will skip
+the message with the error, but continue the session allowing for 
+downloading of subsequent messages.  Additionally, the markseen
+option will mark the skipped message as seen.
 The `True name of server' box should specify the actual DNS name
 to query. By default this is the same as the poll name.
 
@@ -997,8 +1040,10 @@ The ssl option enables SSL communication with a mailserver
 supporting Secure Sockets Layer. The sslkey and sslcert options
 declare key and certificate files for use with SSL.
 The sslcertck option enables strict checking of SSL server
-certificates (and sslcertpath gives trusted certificate
-directory). With sslfingerprint, you can specify a finger-
+certificates (and sslcertpath gives the trusted certificate
+directory). The sslcommonname option helps if the server is
+misconfigured and returning "Server CommonName mismatch"
+warnings. With sslfingerprint, you can specify a finger-
 print the server's key is checked against.
 """}
 
@@ -1079,7 +1124,6 @@ class ServerEdit(Frame, MyWidget):
        # a custom port number you should be in expert mode and playing
        # close enough attention to notice this...
        self.service.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())
@@ -1099,6 +1143,11 @@ class ServerEdit(Frame, MyWidget):
            ctlwin = Frame(leftwin, relief=RAISED, bd=5)
            Label(ctlwin, text="Run Controls").pack(side=TOP)
            Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
+           Checkbutton(ctlwin, text='Pass messages with bad headers?',
+                   variable=self.badheader).pack(side=TOP)
+            retrieveerrorlist = ['abort', 'continue', 'markseen']
+            Label(ctlwin, text="Retrieve Error Policy").pack(side=TOP)
+            ButtonBar(ctlwin, '', self.retrieveerror, retrieveerrorlist, 1, None)
            LabeledEntry(ctlwin, 'True name of ' + host + ':',
                      self.via, leftwidth).pack(side=TOP, fill=X)
            LabeledEntry(ctlwin, 'Cycles to skip between polls:',
@@ -1111,10 +1160,8 @@ class ServerEdit(Frame, MyWidget):
 
        # 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"]
+           protolist = protolist + ["POP3", "KPOP"]
        if 'sdps' in feature_options:
            protolist.append("SDPS")
        if 'imap' in feature_options:
@@ -1133,9 +1180,6 @@ class ServerEdit(Frame, MyWidget):
            LabeledEntry(protwin, 'On server TCP/IP service:',
                      self.service, leftwidth).pack(side=TOP, fill=X)
            self.defaultPort()
-           Checkbutton(protwin,
-               text="POP3: track `seen' with client-side UIDLs?",
-               variable=self.uidl).pack(side=TOP)
        Button(protwin, text='Probe for supported protocols', fg='blue',
               command=self.autoprobe).pack(side=LEFT)
        Button(protwin, text='Help', fg='blue',
@@ -1209,7 +1253,7 @@ class ServerEdit(Frame, MyWidget):
        else:
            realhost = self.server.pollname
        greetline = None
-       for protocol in ("IMAP","POP3","POP2"):
+       for protocol in ("IMAP","POP3"):
            service = defaultports[protocol]
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
@@ -1234,18 +1278,6 @@ out before getting a response.
            warnings = ''
            # OK, now try to recognize potential problems
 
-           if protocol == "POP2":
-               warnings = warnings + """
-It appears you have somehow found a mailserver running only POP2.
-Congratulations.  Have you considered a career in archaeology?
-
-Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
-Unless the first line of your fetchmail -V output includes the string "POP2",
-you'll have to build it from sources yourself with the configure
-switch --enable-POP2.
-
-"""
-
 ### POP3 servers start here
 
            if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
@@ -1436,13 +1468,6 @@ 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 + """
@@ -1467,7 +1492,7 @@ 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
+to version 5.0.2, 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.
@@ -1483,20 +1508,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, "IMAP2bis") > 0:
-               warnings = warnings + """
-IMAP2bis servers have a minor problem; they can't peek at messages without
-marking them seen.  If you take a line hit during the retrieval, the
-interrupted message may get left on the server, marked seen.
-
-To work around this, it is recommended that you set the `fetchall'
-option on all user entries associated with this server, so any stuck
-mail will be retrieved next time around.
-
-To fix this bug, upgrade to an IMAP4 server.  The fetchmail FAQ includes
-a pointer to an open-source implementation.
-
 """
            if string.find(greetline, "IMAP4rev1") > 0:
                warnings = warnings + """
@@ -1515,6 +1526,7 @@ Fetchmail doesn't know anything special about this server type.
            title = "Autoprobe of " + realhost + " succeeded"
            confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
            self.protocol.set(protocol)
+           self.service.set(defaultports[protocol])
        confwin.title(title)
        confwin.iconname(title)
        Label(confwin, text=title).pack()
@@ -1644,6 +1656,8 @@ class UserEdit(Frame, MyWidget):
                        variable=self.sslcertck).pack(side=TOP, fill=X)
            LabeledEntry(sslwin, 'SSL trusted certificate directory:',
                         self.sslcertpath, '14').pack(side=TOP, fill=X)
+           LabeledEntry(sslwin, 'SSL CommonName:',
+                        self.sslcommonname, '14').pack(side=TOP, fill=X)
            LabeledEntry(sslwin, 'SSL key fingerprint:',
                         self.sslfingerprint, '14').pack(side=TOP, fill=X)
            sslwin.pack(fill=X, anchor=N)
@@ -1663,9 +1677,9 @@ class UserEdit(Frame, MyWidget):
            Label(targwin, text="Domains to fetch from (ODMR/ETRN only)").pack(side=TOP)
            ListEdit("Domains:",
                     self.user.fetchdomains, None, None, targwin, None)
-           LabeledEntry(targwin, 'Append to MAIL FROM line:',
+           LabeledEntry(targwin, 'Use domain on RCPT TO line:',
                     self.smtpaddress, '26').pack(side=TOP, fill=X)
-           LabeledEntry(targwin, 'Set RCPT To address:',
+           LabeledEntry(targwin, 'Set fixed RCPT TO address:',
                     self.smtpname, '26').pack(side=TOP, fill=X)
            LabeledEntry(targwin, 'Connection setup command:',
                     self.preconnect, '26').pack(side=TOP, fill=X)
@@ -1945,7 +1959,7 @@ def copy_instance(toclass, fromdict):
     optional = ('interface', 'monitor',
                'esmtpname', 'esmtppassword',
                'ssl', 'sslkey', 'sslcert', 'sslproto', 'sslcertck',
-               'sslcertpath', 'sslfingerprint', 'showdots')
+               'sslcertpath', 'sslcommonname', 'sslfingerprint', 'showdots')
     class_sig = setdiff(toclass.__dict__.keys(), optional)
     class_sig.sort()
     dict_keys = setdiff(fromdict.keys(), optional)
@@ -2038,20 +2052,32 @@ gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
 #
 
     # Process options
-    (options, arguments) = getopt.getopt(sys.argv[1:], "df:h")
+    (options, arguments) = getopt.getopt(sys.argv[1:], "df:hV", ["help",
+           "version"])
     dump = rcfile = None;
     for (switch, val) in options:
        if (switch == '-d'):
            dump = TRUE
        elif (switch == '-f'):
            rcfile = val
-       elif (switch == '-h'):
+       elif (switch == '-h' or switch == '--help'):
            print """
-Usage: fetchmailconf [-d] [-f fetchmailrc]
--d       - dump configuration (for debugging)
--f fmrc  - read alternate fetchmailrc file
+Usage: fetchmailconf {[-d] [-f fetchmailrc]|-h|--help|-V|--version}
+           -d      - dump configuration (for debugging)
+           -f fmrc - read alternate fetchmailrc file
+--help,    -h      - print this help text and quit
+--version, -V      - print fetchmailconf version and quit
 """
            sys.exit(0)
+       elif (switch == '-V' or switch == '--version'):
+           print "fetchmailconf %s" % version
+           print """
+Copyright (C) 1997 - 2003 Eric S. Raymond
+Copyright (C) 2005, 2006, 2008, 2009 Matthias Andree
+fetchmailconf comes with ABSOLUTELY NO WARRANTY.  This is free software, you are
+welcome to redistribute it under certain conditions.  Please see the file
+COPYING in the source or documentation directory for details."""
+           sys.exit(0)
 
     # Get client host's FQDN
     hostname = socket.gethostbyaddr(socket.gethostname())[0]