3 # A GUI configurator for generating fetchmail configuration files.
4 # by Eric S. Raymond, <esr@snark.thyrsus.com>.
5 # Requires Python with Tkinter, and the following OS-dependent services:
6 # posix, posixpath, socket
11 import sys, time, os, string, socket, getopt
14 # Define the data structures the GUIs will be tossing around
18 self.poll_interval = 0 # Normally, run in foreground
19 self.logfile = None # No logfile, initially
20 self.idfile = os.environ["HOME"] + "/.fetchids" # Default idfile, initially
21 self.postmaster = None # No last-resort address, initially
22 self.bouncemail = TRUE # Bounce errors to users
23 self.properties = None # No exiguous properties
24 self.invisible = FALSE # Suppress Received line & spoof?
25 self.syslog = FALSE # Use syslogd for logging?
26 self.servers = [] # List of included sites
27 Configuration.typemap = (
28 ('poll_interval', 'Int'),
29 ('logfile', 'String'),
31 ('postmaster', 'String'),
32 ('bouncemail', 'Boolean'),
33 ('properties', 'String'),
34 ('syslog', 'Boolean'),
35 ('invisible', 'Boolean'))
39 if self.syslog != ConfigurationDefaults.syslog:
40 str = str + ("set syslog\n")
42 str = str + ("set logfile \"%s\"\n" % (self.logfile,));
43 if self.idfile != ConfigurationDefaults.idfile:
44 str = str + ("set idfile \"%s\"\n" % (self.idfile,));
45 if self.postmaster != ConfigurationDefaults.postmaster:
46 str = str + ("set postmaster \"%s\"\n" % (self.postmaster,));
48 str = str + ("set bouncemail\n")
50 str = str + ("set nobouncemail\n")
51 if self.properties != ConfigurationDefaults.properties:
52 str = str + ("set properties \"%s\"\n" % (self.properties,));
53 if self.poll_interval > 0:
54 str = str + "set daemon " + `self.poll_interval` + "\n"
55 for site in self.servers:
56 str = str + repr(site)
59 def __delitem__(self, name):
60 for si in range(len(self.servers)):
61 if self.servers[si].pollname == name:
66 return "[Configuration: " + repr(self) + "]"
70 self.pollname = None # Poll label
71 self.via = None # True name of host
72 self.active = TRUE # Poll status
73 self.interval = 0 # Skip interval
74 self.protocol = 'auto' # Default to auto protocol
75 self.port = 0 # Port number to use
76 self.uidl = FALSE # Don't use RFC1725 UIDLs by default
77 self.preauth = 'password' # Default to password authentication
78 self.timeout = 300 # 5-minute timeout
79 self.envelope = 'Received' # Envelope-address header
80 self.envskip = 0 # Number of envelope headers to skip
81 self.qvirtual = None # Name prefix to strip
82 self.aka = [] # List of DNS aka names
83 self.dns = TRUE # Enable DNS lookup on multidrop
84 self.localdomains = [] # Domains to be considered local
85 self.interface = None # IP address and range
86 self.monitor = None # IP address and range
87 self.plugin = None # Plugin command for going to server
88 self.plugout = None # Plugin command for going to listener
89 self.netsec = None # IPV6 security options
90 self.users = [] # List of user entries for site
92 ('pollname', 'String'),
94 ('active', 'Boolean'),
96 ('protocol', 'String'),
99 ('preauth', 'String'),
101 ('envelope', 'String'),
103 ('qvirtual', 'String'),
106 # leave localdomains out
107 ('interface', 'String'),
108 ('monitor', 'String'),
109 ('plugin', 'String'),
110 ('plugout', 'String'),
111 ('netsec', 'String'))
113 def dump(self, folded):
115 if self.active: str = str + "poll"
116 else: str = str + "skip"
117 str = str + (" " + self.pollname)
119 str = str + (" via \"%s\"\n" % (self.via,));
120 if self.protocol != ServerDefaults.protocol:
121 str = str + " with proto " + self.protocol
122 if self.port != defaultports[self.protocol] and self.port != 0:
123 str = str + " port " + `self.port`
124 if self.timeout != ServerDefaults.timeout:
125 str = str + " timeout " + `self.timeout`
126 if self.interval != ServerDefaults.interval:
127 str = str + " interval " + `self.interval`
128 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
130 str = str + " envelope " + self.envskip + " " + self.envelope
132 str = str + " envelope " + self.envelope
134 str = str + (" qvirtual \"%s\"\n" % (self.qvirtual,));
135 if self.preauth != ServerDefaults.preauth:
136 str = str + " preauth " + self.preauth
137 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
138 str = str + " and options"
139 if self.dns != ServerDefaults.dns:
140 str = str + flag2str(self.dns, 'dns')
141 if self.uidl != ServerDefaults.uidl:
142 str = str + flag2str(self.uidl, 'uidl')
143 if folded: str = str + "\n "
144 else: str = str + " "
150 if self.aka and self.localdomains: str = str + " "
151 if self.localdomains:
152 str = str + ("localdomains")
153 for x in self.localdomains:
155 if (self.aka or self.localdomains):
162 str = str + "interface \"" + self.interface + "\""
164 str = str + "monitor \"" + self.monitor + "\""
166 str = str + "netsec \"" + self.netsec + "\""
167 if self.interface or self.monitor or self.netsec:
171 if str[-1] == " ": str = str[0:-1]
173 for user in self.users:
174 str = str + repr(user)
178 def __delitem__(self, name):
179 for ui in range(len(self.users)):
180 if self.users[ui].remote == name:
185 return self.dump(TRUE)
188 return "[Server: " + self.dump(FALSE) + "]"
192 if os.environ.has_key("USER"):
193 self.remote = os.environ["USER"] # Remote username
194 elif os.environ.has_key("LOGNAME"):
195 self.remote = os.environ["LOGNAME"]
197 print "Can't get your username!"
199 self.localnames = [self.remote,]# Local names
200 self.password = None # Password for mail account access
201 self.mailboxes = [] # Remote folders to retrieve from
202 self.smtphunt = [] # Hosts to forward to
203 self.smtpaddress = None # Append this to MAIL FROM line
204 self.preconnect = None # Connection setup
205 self.postconnect = None # Connection wrapup
206 self.mda = None # Mail Delivery Agent
207 self.bsmtp = None # BSMTP output file
208 self.lmtp = FALSE # Use LMTP rather than SMTP?
209 self.antispam = "571 550 501" # Listener's spam-block code
210 self.keep = FALSE # Keep messages
211 self.flush = FALSE # Flush messages
212 self.fetchall = FALSE # Fetch old messages
213 self.rewrite = TRUE # Rewrite message headers
214 self.forcecr = FALSE # Force LF -> CR/LF
215 self.stripcr = FALSE # Strip CR
216 self.pass8bits = FALSE # Force BODY=7BIT
217 self.mimedecode = FALSE # Undo MIME armoring
218 self.dropstatus = FALSE # Drop incoming Status lines
219 self.limit = 0 # Message size limit
220 self.warnings = 0 # Size warning interval
221 self.fetchlimit = 0 # Max messages fetched per batch
222 self.batchlimit = 0 # Max message forwarded per batch
223 self.expunge = 0 # Interval between expunges (IMAP)
224 self.ssl = 0 # Enable Seccure Socket Layer
225 self.sslkey = None # SSL key filename
226 self.sslcert = None # SSL certificate filename
227 self.properties = None # Extension properties
229 ('remote', 'String'),
230 # leave out mailboxes and localnames
231 ('password', 'String'),
233 ('smtpaddress', 'String'),
234 ('preconnect', 'String'),
235 ('postconnect', 'String'),
239 ('antispam', 'String'),
241 ('flush', 'Boolean'),
242 ('fetchall', 'Boolean'),
243 ('rewrite', 'Boolean'),
244 ('forcecr', 'Boolean'),
245 ('stripcr', 'Boolean'),
246 ('pass8bits', 'Boolean'),
247 ('mimedecode', 'Boolean'),
248 ('dropstatus', 'Boolean'),
251 ('fetchlimit', 'Int'),
252 ('batchlimit', 'Int'),
255 ('sslkey', 'String'),
256 ('sslcert', 'String'),
257 ('properties', 'String'))
261 str = str + "user \"" + self.remote + "\" there ";
263 str = str + "with password \"" + self.password + '" '
266 for x in self.localnames:
269 if (self.keep != UserDefaults.keep
270 or self.flush != UserDefaults.flush
271 or self.fetchall != UserDefaults.fetchall
272 or self.rewrite != UserDefaults.rewrite
273 or self.forcecr != UserDefaults.forcecr
274 or self.stripcr != UserDefaults.stripcr
275 or self.pass8bits != UserDefaults.pass8bits
276 or self.mimedecode != UserDefaults.mimedecode
277 or self.dropstatus != UserDefaults.dropstatus):
278 str = str + " options"
279 if self.keep != UserDefaults.keep:
280 str = str + flag2str(self.keep, 'keep')
281 if self.flush != UserDefaults.flush:
282 str = str + flag2str(self.flush, 'flush')
283 if self.fetchall != UserDefaults.fetchall:
284 str = str + flag2str(self.fetchall, 'fetchall')
285 if self.rewrite != UserDefaults.rewrite:
286 str = str + flag2str(self.rewrite, 'rewrite')
287 if self.forcecr != UserDefaults.forcecr:
288 str = str + flag2str(self.forcecr, 'forcecr')
289 if self.stripcr != UserDefaults.stripcr:
290 str = str + flag2str(self.stripcr, 'stripcr')
291 if self.pass8bits != UserDefaults.pass8bits:
292 str = str + flag2str(self.pass8bits, 'pass8bits')
293 if self.mimedecode != UserDefaults.mimedecode:
294 str = str + flag2str(self.mimedecode, 'mimedecode')
295 if self.dropstatus != UserDefaults.dropstatus:
296 str = str + flag2str(self.dropstatus, 'dropstatus')
297 if self.limit != UserDefaults.limit:
298 str = str + " limit " + `self.limit`
299 if self.warnings != UserDefaults.warnings:
300 str = str + " warnings " + `self.warnings`
301 if self.fetchlimit != UserDefaults.fetchlimit:
302 str = str + " fetchlimit " + `self.fetchlimit`
303 if self.batchlimit != UserDefaults.batchlimit:
304 str = str + " batchlimit " + `self.batchlimit`
305 if self.ssl != UserDefaults.ssl:
306 str = str + flag2str(self.ssl, 'ssl')
307 if self.sslkey != UserDefaults.sslkey:
308 str = str + " sslkey " + `self.sslkey`
309 if self.sslcert != UserDefaults.sslcert:
310 str = str + " ssl " + `self.sslcert`
311 if self.expunge != UserDefaults.expunge:
312 str = str + " expunge " + `self.expunge`
314 trimmed = self.smtphunt;
315 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
316 trimmed = trimmed[0:len(trimmed) - 1]
317 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
318 trimmed = trimmed[0:len(trimmed) - 1]
320 str = str + " smtphost "
325 str = str + " folder"
326 for x in self.mailboxes:
329 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
330 if getattr(self, fld):
331 str = str + " %s %s\n" % (fld, `getattr(self, fld)`)
332 if self.lmtp != UserDefaults.lmtp:
333 str = str + flag2str(self.lmtp, 'lmtp')
334 if self.antispam != UserDefaults.antispam:
335 str = str + " antispam " + self.antispam + "\n"
339 return "[User: " + repr(self) + "]"
345 defaultports = {"auto":0,
355 preauthlist = ("password", "kerberos")
358 'title' : 'List Selection Help',
359 'banner': 'List Selection',
361 You must select an item in the list box (by clicking on it).
364 def flag2str(value, string):
365 # make a string representation of a .fetchmailrc flag or negated flag
369 if value == FALSE: str = str + ("no ")
373 class LabeledEntry(Frame):
374 # widget consisting of entry field with caption to left
375 def bind(self, key, action):
376 self.E.bind(key, action)
379 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
380 Frame.__init__(self, Master)
381 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
382 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
383 self.L.pack({'side':'left'})
384 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
386 def ButtonBar(frame, legend, ref, alternatives, depth, command):
387 # array of radio buttons, caption to left, picking from a string list
389 width = len(alternatives) / depth;
390 Label(bar, text=legend).pack(side=LEFT)
391 for column in range(width):
392 subframe = Frame(bar)
393 for row in range(depth):
394 ind = width * row + column
395 Radiobutton(subframe,
396 {'text':alternatives[ind],
398 'value':alternatives[ind],
399 'command':command}).pack(side=TOP, anchor=W)
400 subframe.pack(side=LEFT)
404 def helpwin(helpdict):
405 # help message window with a self-destruct button
407 helpwin.title(helpdict['title'])
408 helpwin.iconname(helpdict['title'])
409 Label(helpwin, text=helpdict['banner']).pack()
410 textwin = Message(helpwin, text=helpdict['text'], width=600)
412 Button(helpwin, text='Done',
413 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
415 def make_icon_window(base, image):
417 # Some older pythons will error out on this
418 icon_image = PhotoImage(data=image)
419 icon_window = Toplevel()
420 Label(icon_window, image=icon_image, bg='black').pack()
421 base.master.iconwindow(icon_window)
422 # Avoid TkInter brain death. PhotoImage objects go out of
423 # scope when the enclosing function returns. Therefore
424 # we have to explicitly link them to something.
425 base.keepalive.append(icon_image)
429 class ListEdit(Frame):
430 # edit a list of values (duplicates not allowed) with a supplied editor hook
431 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
433 self.deletor = deletor
436 # Set up a widget to accept new elements
437 self.newval = StringVar(master)
438 newwin = LabeledEntry(master, newlegend, self.newval, '12')
439 newwin.bind('<Double-1>', self.handleNew)
440 newwin.bind('<Return>', self.handleNew)
441 newwin.pack(side=TOP, fill=X, anchor=E)
443 # Edit the existing list
444 listframe = Frame(master)
445 scroll = Scrollbar(listframe)
446 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
449 self.listwidget.insert(END, x)
450 listframe.pack(side=TOP, expand=YES, fill=BOTH)
451 self.listwidget.config(yscrollcommand=scroll.set)
452 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
453 scroll.config(command=self.listwidget.yview)
454 scroll.pack(side=RIGHT, fill=BOTH)
455 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
456 self.listwidget.bind('<Double-1>', self.handleList);
457 self.listwidget.bind('<Return>', self.handleList);
461 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
462 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
464 self.helptxt = helptxt
465 Button(bf, text='Help', fg='blue',
466 command=self.help).pack(side=RIGHT)
470 helpwin(self.helptxt)
472 def handleList(self, event):
475 def handleNew(self, event):
476 item = self.newval.get()
477 entire = self.listwidget.get(0, self.listwidget.index('end'));
478 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
479 self.listwidget.insert('end', item)
480 if self.list != None: self.list.append(item)
484 select = self.listwidget.curselection()
489 if index and self.editor:
490 label = self.listwidget.get(index);
491 apply(self.editor, (label,))
493 def deleteItem(self):
494 select = self.listwidget.curselection()
498 index = string.atoi(select[0])
499 label = self.listwidget.get(index);
500 self.listwidget.delete(index)
501 if self.list != None:
503 if self.deletor != None:
504 apply(self.deletor, (label,))
506 def ConfirmQuit(frame, context):
509 text = 'Really quit ' + context + ' without saving?',
511 strings = ('Yes', 'No'),
515 def dispose_window(master, legend, help, savelegend='OK'):
516 dispose = Frame(master, relief=RAISED, bd=5)
517 Label(dispose, text=legend).pack(side=TOP,pady=10)
518 Button(dispose, text=savelegend, fg='blue',
519 command=master.save).pack(side=LEFT)
520 Button(dispose, text='Quit', fg='blue',
521 command=master.nosave).pack(side=LEFT)
522 Button(dispose, text='Help', fg='blue',
523 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
528 # Common methods for Tkinter widgets -- deals with Tkinter declaration
529 def post(self, widgetclass, field):
530 for x in widgetclass.typemap:
531 if x[1] == 'Boolean':
532 setattr(self, x[0], BooleanVar(self))
533 elif x[1] == 'String':
534 setattr(self, x[0], StringVar(self))
536 setattr(self, x[0], IntVar(self))
537 source = getattr(getattr(self, field), x[0])
539 getattr(self, x[0]).set(source)
541 def fetch(self, widgetclass, field):
542 for x in widgetclass.typemap:
543 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
546 # First, code to set the global fetchmail run controls.
549 configure_novice_help = {
550 'title' : 'Fetchmail novice configurator help',
551 'banner': 'Novice configurator help',
553 In the `Novice Configurator Controls' panel, you can:
555 Press `Save' to save the new fetchmail configuration you have created.
556 Press `Quit' to exit without saving.
557 Press `Help' to bring up this help message.
559 In the `Novice Configuration' panels, you will set up the basic data
560 needed to create a simple fetchmail setup. These include:
562 1. The name of the remote site you want to query.
564 2. Your login name on that site.
566 3. Your password on that site.
568 4. A protocol to use (POP, IMAP, ETRN, etc.)
570 5. A polling interval.
572 6. Options to fetch old messages as well as new, uor to suppress
573 deletion of fetched message.
575 The novice-configuration code will assume that you want to forward mail
576 to a local sendmail listener with no special options.
579 configure_expert_help = {
580 'title' : 'Fetchmail expert configurator help',
581 'banner': 'Expert configurator help',
583 In the `Expert Configurator Controls' panel, you can:
585 Press `Save' to save the new fetchmail configuration you have edited.
586 Press `Quit' to exit without saving.
587 Press `Help' to bring up this help message.
589 In the `Run Controls' panel, you can set the following options that
590 control how fetchmail runs:
593 Number of seconds to wait between polls in the background.
594 If zero, fetchmail will run in foreground.
597 If empty, emit progress and error messages to stderr.
598 Otherwise this gives the name of the files to write to.
599 This field is ignored if the "Log to syslog?" option is on.
602 If empty, store seen-message IDs in .fetchids under user's home
603 directory. If nonempty, use given file name.
606 Who to send multidrop mail to as a last resort if no address can
607 be matched. Normally empty; in this case, fetchmail treats the
608 invoking user as the address of last resort unless that user is
609 root. If that user is root, fetchmail sends to `postmaster'.
612 If this option is on (the default) error mail goes to the sender.
613 Otherwise it goes to the postmaster.
616 If false (the default) fetchmail generates a Received line into
617 each message and generates a HELO from the machine it is running on.
618 If true, fetchmail generates no Received line and HELOs as if it were
621 In the `Remote Mail Configurations' panel, you can:
623 1. Enter the name of a new remote mail server you want fetchmail to query.
625 To do this, simply enter a label for the poll configuration in the
626 `New Server:' box. The label should be a DNS name of the server (unless
627 you are using ssh or some other tunneling method and will fill in the `via'
628 option on the site configuration screen).
630 2. Change the configuration of an existing site.
632 To do this, find the site's label in the listbox and double-click it.
633 This will take you to a site configuration dialogue.
637 class ConfigurationEdit(Frame, MyWidget):
638 def __init__(self, configuration, outfile, master, onexit):
640 self.configuration = configuration
641 self.outfile = outfile
642 self.container = master
644 ConfigurationEdit.mode_to_help = {
645 'novice':configure_novice_help, 'expert':configure_expert_help
648 def server_edit(self, sitename):
649 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
651 def server_delete(self, sitename):
653 del self.configuration[sitename]
657 def edit(self, mode):
659 Frame.__init__(self, self.container)
660 self.master.title('fetchmail ' + self.mode + ' configurator');
661 self.master.iconname('fetchmail ' + self.mode + ' configurator');
662 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
663 self.keepalive = [] # Use this to anchor the PhotoImage object
664 make_icon_window(self, fetchmail_gif)
666 self.post(Configuration, 'configuration')
669 'Configurator ' + self.mode + ' Controls',
670 ConfigurationEdit.mode_to_help[self.mode],
673 gf = Frame(self, relief=RAISED, bd = 5)
675 text='Fetchmail Run Controls',
676 bd=2).pack(side=TOP, pady=10)
681 if self.mode != 'novice':
683 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
684 log.pack(side=RIGHT, anchor=E)
686 # Set the poll interval
687 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
688 de.pack(side=RIGHT, anchor=E)
693 if self.mode != 'novice':
696 {'text':'Bounces to sender?',
697 'variable':self.bouncemail,
698 'relief':GROOVE}).pack(side=LEFT, anchor=W)
703 {'text':'Log to syslog?',
704 'variable':self.syslog,
705 'relief':GROOVE}).pack(side=LEFT, anchor=W)
706 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
707 log.pack(side=RIGHT, anchor=E)
711 {'text':'Invisible mode?',
712 'variable':self.invisible,
713 'relief':GROOVE}).pack(side=LEFT, anchor=W)
715 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
716 log.pack(side=RIGHT, anchor=E)
720 # Expert mode allows us to edit multiple sites
721 lf = Frame(self, relief=RAISED, bd=5)
723 text='Remote Mail Server Configurations',
724 bd=2).pack(side=TOP, pady=10)
725 ListEdit('New Server:',
726 map(lambda x: x.pollname, self.configuration.servers),
727 lambda site, self=self: self.server_edit(site),
728 lambda site, self=self: self.server_delete(site),
733 for sitename in self.subwidgets.keys():
734 self.subwidgets[sitename].destruct()
735 self.master.destroy()
739 if ConfirmQuit(self, self.mode + " configuration editor"):
743 for sitename in self.subwidgets.keys():
744 self.subwidgets[sitename].save()
745 self.fetch(Configuration, 'configuration')
749 elif not os.path.isfile(self.outfile) or Dialog(self,
750 title = 'Overwrite existing run control file?',
751 text = 'Really overwrite existing run control file?',
753 strings = ('Yes', 'No'),
754 default = 1).num == 0:
755 fm = open(self.outfile, 'w')
757 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
758 fm.write(`self.configuration`)
762 os.chmod(self.outfile, 0600)
766 # Server editing stuff.
769 'title' : 'Remote site help',
770 'banner': 'Remote sites',
772 When you add a site name to the list here,
773 you initialize an entry telling fetchmail
774 how to poll a new site.
776 When you select a sitename (by double-
777 clicking it, or by single-clicking to
778 select and then clicking the Edit button),
779 you will open a window to configure that
784 'title' : 'Server options help',
785 'banner': 'Server Options',
787 The server options screen controls fetchmail
788 options that apply to one of your mailservers.
790 Once you have a mailserver configuration set
791 up as you like it, you can select `OK' to
792 store it in the server list maintained in
793 the main configuration window.
795 If you wish to discard changes to a server
796 configuration, select `Quit'.
800 'title' : 'Run Control help',
801 'banner': 'Run Controls',
803 If the `Poll normally' checkbox is on, the host is polled as part of
804 the normal operation of fetchmail when it is run with no arguments.
805 If it is off, fetchmail will only query this host when it is given as
806 a command-line argument.
808 The `True name of server' box should specify the actual DNS name
809 to query. By default this is the same as the poll name.
811 Normally each host described in the file is queried once each
812 poll cycle. If `Cycles to skip between polls' is greater than 0,
813 that's the number of poll cycles that are skipped between the
814 times this post is actually polled.
816 The `Server timeout' is the number of seconds fetchmail will wait
817 for a reply from the mailserver before concluding it is hung and
822 'title' : 'Protocol and Port help',
823 'banner': 'Protocol and Port',
825 These options control the remote-mail protocol
826 and TCP/IP service port used to query this
829 If you click the `Probe for supported protocols'
830 button, fetchmail will try to find you the most
831 capable server on the selected host (this will
832 only work if you're conncted to the Internet).
833 The probe only checks for ordinary IMAP and POP
834 protocols; fortunately these are the most
835 frequently supported.
837 The `Protocol' button bar offers you a choice of
838 all the different protocols available. The `auto'
839 protocol is the default mode; it probes the host
840 ports for POP3 and IMAP to see if either is
843 Normally the TCP/IP service port to use is
844 dictated by the protocol choice. The `Port'
845 field (only present in expert mode) lets you
846 set a non-standard port.
850 'title' : 'Security option help',
851 'banner': 'Security',
853 The `interface' option allows you to specify a range
854 of IP addresses to monitor for activity. If these
855 addresses are not active, fetchmail will not poll.
856 Specifying this may protect you from a spoofing attack
857 if your client machine has more than one IP gateway
858 address and some of the gateways are to insecure nets.
860 The `monitor' option, if given, specifies the only
861 device through which fetchmail is permitted to connect
862 to servers. This option may be used to prevent
863 fetchmail from triggering an expensive dial-out if the
864 interface is not already active.
866 The `interface' and `monitor' options are available
867 only for Linux and freeBSD systems. See the fetchmail
868 manual page for details on these.
870 The ssl option enables SSL communication with a maolserver
871 supporting Secure Sockets Layer. The sslkey and sslcert options
872 declare key and certificate files for use with SSL.
874 The `netsec' option will be configurable only if fetchmail
875 was compiled with IPV6 support. If you need to use it,
876 you probably know what to do.
880 'title' : 'Multidrop option help',
881 'banner': 'Multidrop',
883 These options are only useful with multidrop mode.
884 See the manual page for extended discussion.
888 'title' : 'User list help',
889 'banner': 'User list',
891 When you add a user name to the list here,
892 you initialize an entry telling fetchmail
893 to poll the site on behalf of the new user.
895 When you select a username (by double-
896 clicking it, or by single-clicking to
897 select and then clicking the Edit button),
898 you will open a window to configure the
899 user's options on that site.
902 class ServerEdit(Frame, MyWidget):
903 def __init__(self, host, parent):
907 for site in parent.configuration.servers:
908 if site.pollname == host:
910 if (self.server == None):
911 self.server = Server()
912 self.server.pollname = host
913 self.server.via = None
914 parent.configuration.servers.append(self.server)
916 def edit(self, mode, master=None):
917 Frame.__init__(self, master)
919 self.master.title('Fetchmail host ' + self.server.pollname);
920 self.master.iconname('Fetchmail host ' + self.server.pollname);
921 self.post(Server, 'server')
922 self.makeWidgets(self.server.pollname, mode)
923 self.keepalive = [] # Use this to anchor the PhotoImage object
924 make_icon_window(self, fetchmail_gif)
931 for username in self.subwidgets.keys():
932 self.subwidgets[username].destruct()
933 del self.parent.subwidgets[self.server.pollname]
934 Widget.destroy(self.master)
937 if ConfirmQuit(self, 'server option editing'):
941 self.fetch(Server, 'server')
942 for username in self.subwidgets.keys():
943 self.subwidgets[username].save()
946 def refreshPort(self):
947 proto = self.protocol.get()
948 if self.port.get() == 0:
949 self.port.set(defaultports[proto])
950 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
952 def user_edit(self, username, mode):
953 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
955 def user_delete(self, username):
956 if self.subwidgets.has_key(username):
957 del self.subwidgets[username]
958 del self.server[username]
960 def makeWidgets(self, host, mode):
961 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
963 leftwin = Frame(self);
967 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
968 Label(ctlwin, text="Run Controls").pack(side=TOP)
969 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
970 LabeledEntry(ctlwin, 'True name of ' + host + ':',
971 self.via, leftwidth).pack(side=TOP, fill=X)
972 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
973 self.interval, leftwidth).pack(side=TOP, fill=X)
974 LabeledEntry(ctlwin, 'Server timeout (seconds):',
975 self.timeout, leftwidth).pack(side=TOP, fill=X)
976 Button(ctlwin, text='Help', fg='blue',
977 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
980 # Compute the available protocols from the compile-time options
982 if 'pop2' in feature_options:
983 protolist.append("POP2")
984 if 'pop3' in feature_options:
985 protolist = protolist + ["POP3", "APOP", "KPOP"]
986 if 'sdps' in feature_options:
987 protolist.append("SDPS")
988 if 'imap' in feature_options:
989 protolist.append("IMAP")
990 if 'imap-gss' in feature_options:
991 protolist.append("IMAP-GSS")
992 if 'imap-k4' in feature_options:
993 protolist.append("IMAP-K4")
994 if 'etrn' in feature_options:
995 protolist.append("ETRN")
997 protwin = Frame(leftwin, relief=RAISED, bd=5)
998 Label(protwin, text="Protocol").pack(side=TOP)
999 ButtonBar(protwin, '',
1000 self.protocol, protolist, 2,
1002 if mode != 'novice':
1003 LabeledEntry(protwin, 'On server TCP/IP port:',
1004 self.port, leftwidth).pack(side=TOP, fill=X)
1006 Checkbutton(protwin,
1007 text="POP3: track `seen' with client-side UIDLs?",
1008 variable=self.uidl).pack(side=TOP)
1009 Button(protwin, text='Probe for supported protocols', fg='blue',
1010 command=self.autoprobe).pack(side=LEFT)
1011 Button(protwin, text='Help', fg='blue',
1012 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1013 protwin.pack(fill=X)
1015 userwin = Frame(leftwin, relief=RAISED, bd=5)
1016 Label(userwin, text="User entries for " + host).pack(side=TOP)
1017 ListEdit("New user: ",
1018 map(lambda x: x.remote, self.server.users),
1019 lambda u, m=mode, s=self: s.user_edit(u, m),
1020 lambda u, s=self: s.user_delete(u),
1022 userwin.pack(fill=X)
1024 leftwin.pack(side=LEFT, anchor=N, fill=X);
1026 if mode != 'novice':
1027 rightwin = Frame(self);
1029 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1030 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1031 LabeledEntry(mdropwin, 'Envelope address header:',
1032 self.envelope, '22').pack(side=TOP, fill=X)
1033 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1034 self.envskip, '22').pack(side=TOP, fill=X)
1035 LabeledEntry(mdropwin, 'Name prefix to strip:',
1036 self.qvirtual, '22').pack(side=TOP, fill=X)
1037 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1038 variable=self.dns).pack(side=TOP)
1039 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1040 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1041 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1042 ListEdit("New domain: ",
1043 self.server.localdomains, None, None, mdropwin, multihelp)
1044 mdropwin.pack(fill=X)
1046 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1047 secwin = Frame(rightwin, relief=RAISED, bd=5)
1048 Label(secwin, text="Security").pack(side=TOP)
1049 # Don't actually let users set this. KPOP sets it implicitly
1050 # ButtonBar(secwin, 'Preauthorization mode:',
1051 # self.preauth, preauthlist, 1, None).pack(side=TOP)
1052 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1053 LabeledEntry(secwin, 'IP range to check before poll:',
1054 self.interface, leftwidth).pack(side=TOP, fill=X)
1055 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1056 LabeledEntry(secwin, 'Interface to monitor:',
1057 self.monitor, leftwidth).pack(side=TOP, fill=X)
1058 if 'netsec' in feature_options or 'netsec' in dictmembers:
1059 LabeledEntry(secwin, 'IPV6 security options:',
1060 self.netsec, leftwidth).pack(side=TOP, fill=X)
1061 Button(secwin, text='Help', fg='blue',
1062 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1065 rightwin.pack(side=LEFT, anchor=N);
1067 def autoprobe(self):
1068 # Note: this only handles case (1) near fetchmail.c:1032
1069 # We're assuming people smart enough to set up ssh tunneling
1070 # won't need autoprobing.
1072 realhost = self.server.via
1074 realhost = self.server.pollname
1076 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1077 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1079 sock.connect(realhost, port)
1080 greetline = sock.recv(1024)
1086 confwin = Toplevel()
1087 if greetline == None:
1088 title = "Autoprobe of " + realhost + " failed"
1090 Fetchmailconf didn't find any mailservers active.
1091 This could mean the host doesn't support any,
1092 or that your Internet connection is down, or
1093 that the host is so slow that the probe timed
1094 out before getting a response.
1098 # OK, now try to recognize potential problems
1100 if protocol == "POP2":
1101 warnings = warnings + """
1102 It appears you have somehow found a mailserver running only POP2.
1103 Congratulations. Have you considered a career in archaeology?
1105 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1106 Unless the first line of your fetchmail -V output includes the string "POP2",
1107 you'll have to build it from sources yourself with the configure
1108 switch --enable-POP2.
1112 # The greeting line on the server known to be buggy is:
1113 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1115 if string.find(greetline, "FTGate") > 0:
1116 warnings = warnings + """
1117 This POP server has a weird bug; it says OK twice in response to TOP.
1118 Its response to RETR is normal, so use the `fetchall'.
1122 if string.find(greetline, "POP-Max") > 0:
1123 warnings = warnings + """
1124 The Mail Max server screws up on mail with attachments. It reports the
1125 message size with attachments included, but doesn't downloasd them on a
1126 RETR or TOP. You should get rid of it -- and the brain-dead NT server
1131 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1132 warnings = warnings + """
1133 This appears to be an old version of the UC Davis POP server. These are
1134 dangerously unreliable (among other problems, they may drop your mailbox
1135 on the floor if your connection is interrupted during the session).
1137 It is strongly recommended that you find a better POP3 server. The fetchmail
1138 FAQ includes pointers to good ones.
1141 if string.find(greetline, "usa.net") > 0:
1142 warnings = warnings + """
1143 You appear to be using USA.NET's free mail service. Their POP3 servers
1144 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1145 fetchmail can compensate. They seem to require that fetchall be switched on
1146 (otherwise you won't necessarily see all your mail, not even new mail).
1147 They also botch the TOP command the fetchmail normally uses for retrieval
1148 (it only retrieves about 10 lines rather than the number specified).
1149 Turning on fetchall will disable the use of TOP.
1151 Therefore, it is strongly recommended that you turn on `fetchall' on all
1152 user entries associated with this server.
1155 if string.find(greetline, "OpenMail") > 0:
1156 warnings = warnings + """
1157 You appear to be using some version of HP OpenMail. Many versions of
1158 OpenMail do not process the "TOP" command correctly; the symptom is that
1159 only the header and first line of each message is retrieved. To work
1160 around this bug, turn on `fetchall' on all user entries associated with
1164 if string.find(greetline, "TEMS POP3") > 0:
1165 warnings = warnings + """
1166 Your POP3 server has "TEMS" in its header line. At least one such
1167 server does not process the "TOP" command correctly; the symptom is
1168 that fetchmail hangs when trying to retrieve mail. To work around
1169 this bug, turn on `fetchall' on all user entries associated with this
1173 if string.find(greetline, "GroupWise") > 0:
1174 warnings = warnings + """
1175 The Novell GroupWise IMAP server would be better named GroupFoolish;
1176 it is (according to the designer of IMAP) unusably broken. Among
1177 other things, it doesn't include a required content length in its
1178 BODY[TEXT] response.<p>
1180 Fetchmail works around this problem, but we strongly recommend voting
1181 with your dollars for a server that isn't brain-dead. If you stick
1182 with code as shoddy as GroupWise seems to be, you will probably pay
1183 for it with other problems.<p>
1186 if string.find(greetline, "sprynet.com") > 0:
1187 warnings = warnings + """
1188 You appear to be using a SpryNet server. In mid-1999 it was reported that
1189 the SpryNet TOP command marks messages seen. Therefore, for proper error
1190 recovery in the event of a line drop, it is strongly recommended that you
1191 turn on `fetchall' on all user entries associated with this server.
1195 # Steve VanDevender <stevev@efn.org> writes:
1196 # The only system I have seen this happen with is cucipop-1.31
1197 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1198 # 2.x and probably quite a few other systems. It appears to be a
1199 # bug or bad interaction with the SunOS realloc() -- it turns out
1200 # that internally cucipop does allocate a certain data structure in
1201 # multiples of 16, using realloc() to bump it up to the next
1202 # multiple if it needs more.
1204 # The distinctive symptom is that when there are 16 messages in the
1205 # inbox, you can RETR and DELE all 16 messages successfully, but on
1206 # QUIT cucipop returns something like "-ERR Error locking your
1207 # mailbox" and aborts without updating it.
1209 # The cucipop banner looks like:
1211 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1213 if string.find(greetline, "Cubic Circle") > 0:
1214 warnings = warnings + """
1215 I see your server is running cucipop. Better make sure the server box
1216 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1217 under that version, and doesn't cope with the result gracefully. Newer
1218 SunOS and Solaris machines run cucipop OK.
1221 if string.find(greetline, "QPOP") > 0:
1222 warnings = warnings + """
1223 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1224 knows all about qpopper. However, be aware that the 2.53 version of
1225 qpopper does something odd that causes fetchmail to hang with a socket
1226 error on very large messages. This is probably not a fetchmail bug, as
1227 it has been observed with fetchpop. The fix is to upgrade to qpopper
1228 3.0beta or a more recent version. Better yet, switch to IMAP.
1231 if string.find(greetline, "Imail") > 0:
1232 warnings = warnings + """
1233 We've seen a bug report indicating that this IMAP server (at least as of
1234 version 5.0.7) returns an invalid body size for messages with MIME
1235 attachments; the effect is to drop the attachments on the floor. We
1236 recommend you upgrade to a non-broken IMAP server.
1239 closebrak = string.find(greetline, ">")
1240 if closebrak > 0 and greetline[closebrak+1] == "\r":
1241 warnings = warnings + """
1242 It looks like you could use APOP on this server and avoid sending it your
1243 password in clear. You should talk to the mailserver administrator about
1247 if string.find(greetline, "IMAP2bis") > 0:
1248 warnings = warnings + """
1249 IMAP2bis servers have a minor problem; they can't peek at messages without
1250 marking them seen. If you take a line hit during the retrieval, the
1251 interrupted message may get left on the server, marked seen.
1253 To work around this, it is recommended that you set the `fetchall'
1254 option on all user entries associated with this server, so any stuck
1255 mail will be retrieved next time around.
1258 if string.find(greetline, "POP3 Server Ready") > 0:
1259 warnings = warnings + """
1260 Some server that uses this greeting line has been observed to choke on
1261 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1264 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1265 warnings = warnings + """
1266 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1267 set the messages's Seen flag. As a result, if you use the keep option the
1268 same messages will be downloaded over and over.
1271 if string.find(greetline, "IMAP4rev1") > 0:
1272 warnings = warnings + """
1273 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1274 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1275 has therefore been extremely well tested with this class of server.
1278 warnings = warnings + """
1279 Fetchmail doesn't know anything special about this server type.
1282 # Display success window with warnings
1283 title = "Autoprobe of " + realhost + " succeeded"
1284 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1285 self.protocol.set(protocol)
1286 confwin.title(title)
1287 confwin.iconname(title)
1288 Label(confwin, text=title).pack()
1289 Message(confwin, text=confirm, width=600).pack()
1290 Button(confwin, text='Done',
1291 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1294 # User editing stuff
1298 'title' : 'User option help',
1299 'banner': 'User options',
1301 You may use this panel to set options
1302 that may differ between individual
1305 Once you have a user configuration set
1306 up as you like it, you can select `OK' to
1307 store it in the user list maintained in
1308 the site configuration window.
1310 If you wish to discard the changes you have
1311 made to user options, select `Quit'.
1315 'title' : 'Local name help',
1316 'banner': 'Local names',
1318 The local name(s) in a user entry are the
1319 people on the client machine who should
1320 receive mail from the poll described.
1322 Note: if a user entry has more than one
1323 local name, messages will be retrieved
1324 in multidrop mode. This complicates
1325 the configuration issues; see the manual
1326 page section on multidrop mode.
1329 class UserEdit(Frame, MyWidget):
1330 def __init__(self, username, parent):
1331 self.parent = parent
1333 for user in parent.server.users:
1334 if user.remote == username:
1336 if self.user == None:
1338 self.user.remote = username
1339 self.user.localnames = [username]
1340 parent.server.users.append(self.user)
1342 def edit(self, mode, master=None):
1343 Frame.__init__(self, master)
1345 self.master.title('Fetchmail user ' + self.user.remote
1346 + ' querying ' + self.parent.server.pollname);
1347 self.master.iconname('Fetchmail user ' + self.user.remote);
1348 self.post(User, 'user')
1349 self.makeWidgets(mode, self.parent.server.pollname)
1350 self.keepalive = [] # Use this to anchor the PhotoImage object
1351 make_icon_window(self, fetchmail_gif)
1354 # self.wait_window()
1358 del self.parent.subwidgets[self.user.remote]
1359 Widget.destroy(self.master)
1362 if ConfirmQuit(self, 'user option editing'):
1366 self.fetch(User, 'user')
1369 def makeWidgets(self, mode, servername):
1370 dispose_window(self,
1371 "User options for " + self.user.remote + " querying " + servername,
1374 if mode != 'novice':
1375 leftwin = Frame(self);
1379 secwin = Frame(leftwin, relief=RAISED, bd=5)
1380 Label(secwin, text="Authentication").pack(side=TOP)
1381 LabeledEntry(secwin, 'Password:',
1382 self.password, '12').pack(side=TOP, fill=X)
1383 secwin.pack(fill=X, anchor=N)
1385 if 'ssl' in feature_options or 'ssl' in dictmembers:
1386 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1387 Checkbutton(sslwin, text="Use SSL?",
1388 variable=self.ssl).pack(side=TOP, fill=X)
1389 LabeledEntry(sslwin, 'SSL key:',
1390 self.sslkey, '14').pack(side=TOP, fill=X)
1391 LabeledEntry(sslwin, 'SSL certificate:',
1392 self.sslcert, '14').pack(side=TOP, fill=X)
1393 sslwin.pack(fill=X, anchor=N)
1395 names = Frame(leftwin, relief=RAISED, bd=5)
1396 Label(names, text="Local names").pack(side=TOP)
1397 ListEdit("New name: ",
1398 self.user.localnames, None, None, names, localhelp)
1399 names.pack(fill=X, anchor=N)
1401 if mode != 'novice':
1402 targwin = Frame(leftwin, relief=RAISED, bd=5)
1403 Label(targwin, text="Forwarding Options").pack(side=TOP)
1404 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1405 ListEdit("New listener:",
1406 self.user.smtphunt, None, None, targwin, None)
1407 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1408 self.smtpaddress, '26').pack(side=TOP, fill=X)
1409 LabeledEntry(targwin, 'Connection setup command:',
1410 self.preconnect, '26').pack(side=TOP, fill=X)
1411 LabeledEntry(targwin, 'Connection wrapup command:',
1412 self.postconnect, '26').pack(side=TOP, fill=X)
1413 LabeledEntry(targwin, 'Local delivery agent:',
1414 self.mda, '26').pack(side=TOP, fill=X)
1415 LabeledEntry(targwin, 'BSMTP output file:',
1416 self.bsmtp, '26').pack(side=TOP, fill=X)
1417 LabeledEntry(targwin, 'Listener spam-block codes:',
1418 self.antispam, '26').pack(side=TOP, fill=X)
1419 LabeledEntry(targwin, 'Pass-through properties:',
1420 self.properties, '26').pack(side=TOP, fill=X)
1421 Checkbutton(targwin, text="Use LMTP?",
1422 variable=self.lmtp).pack(side=TOP, fill=X)
1423 targwin.pack(fill=X, anchor=N)
1425 if mode != 'novice':
1426 leftwin.pack(side=LEFT, fill=X, anchor=N)
1427 rightwin = Frame(self)
1431 optwin = Frame(rightwin, relief=RAISED, bd=5)
1432 Label(optwin, text="Processing Options").pack(side=TOP)
1433 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1434 variable=self.keep).pack(side=TOP, anchor=W)
1435 Checkbutton(optwin, text="Fetch old messages as well as new",
1436 variable=self.fetchall).pack(side=TOP, anchor=W)
1437 if mode != 'novice':
1438 Checkbutton(optwin, text="Flush seen messages before retrieval",
1439 variable=self.flush).pack(side=TOP, anchor=W)
1440 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1441 variable=self.rewrite).pack(side=TOP, anchor=W)
1442 Checkbutton(optwin, text="Force CR/LF at end of each line",
1443 variable=self.forcecr).pack(side=TOP, anchor=W)
1444 Checkbutton(optwin, text="Strip CR from end of each line",
1445 variable=self.stripcr).pack(side=TOP, anchor=W)
1446 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1447 variable=self.pass8bits).pack(side=TOP, anchor=W)
1448 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1449 variable=self.mimedecode).pack(side=TOP, anchor=W)
1450 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1451 variable=self.dropstatus).pack(side=TOP, anchor=W)
1454 if mode != 'novice':
1455 limwin = Frame(rightwin, relief=RAISED, bd=5)
1456 Label(limwin, text="Resource Limits").pack(side=TOP)
1457 LabeledEntry(limwin, 'Message size limit:',
1458 self.limit, '30').pack(side=TOP, fill=X)
1459 LabeledEntry(limwin, 'Size warning interval:',
1460 self.warnings, '30').pack(side=TOP, fill=X)
1461 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1462 self.fetchlimit, '30').pack(side=TOP, fill=X)
1463 LabeledEntry(limwin, 'Max messages to forward per poll:',
1464 self.batchlimit, '30').pack(side=TOP, fill=X)
1465 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1466 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1467 self.expunge, '30').pack(side=TOP, fill=X)
1470 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1471 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1472 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1473 ListEdit("New folder:", self.user.mailboxes,
1474 None, None, foldwin, None)
1475 foldwin.pack(fill=X, anchor=N)
1477 if mode != 'novice':
1478 rightwin.pack(side=LEFT)
1484 # Top-level window that offers either novice or expert mode
1485 # (but not both at once; it disappears when one is selected).
1488 class Configurator(Frame):
1489 def __init__(self, outfile, master, onexit, parent):
1490 Frame.__init__(self, master)
1491 self.outfile = outfile
1492 self.onexit = onexit
1493 self.parent = parent
1494 self.master.title('fetchmail configurator');
1495 self.master.iconname('fetchmail configurator');
1497 self.keepalive = [] # Use this to anchor the PhotoImage object
1498 make_icon_window(self, fetchmail_gif)
1500 Message(self, text="""
1501 Use `Novice Configuration' for basic fetchmail setup;
1502 with this, you can easily set up a single-drop connection
1503 to one remote mail server.
1504 """, width=600).pack(side=TOP)
1505 Button(self, text='Novice Configuration',
1506 fg='blue', command=self.novice).pack()
1508 Message(self, text="""
1509 Use `Expert Configuration' for advanced fetchmail setup,
1510 including multiple-site or multidrop connections.
1511 """, width=600).pack(side=TOP)
1512 Button(self, text='Expert Configuration',
1513 fg='blue', command=self.expert).pack()
1515 Message(self, text="""
1516 Or you can just select `Quit' to leave the configurator now and
1517 return to the main panel.
1518 """, width=600).pack(side=TOP)
1519 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1520 master.protocol("WM_DELETE_WINDOW", self.leave)
1523 self.master.destroy()
1524 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1527 self.master.destroy()
1528 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1531 self.master.destroy()
1534 # Run a command an a scrolling text widget, displaying its output
1536 class RunWindow(Frame):
1537 def __init__(self, command, master, parent):
1538 Frame.__init__(self, master)
1539 self.master = master
1540 self.master.title('fetchmail run window');
1541 self.master.iconname('fetchmail run window');
1544 text="Running "+command,
1545 bd=2).pack(side=TOP, pady=10)
1546 self.keepalive = [] # Use this to anchor the PhotoImage object
1547 make_icon_window(self, fetchmail_gif)
1549 # This is a scrolling text window
1550 textframe = Frame(self)
1551 scroll = Scrollbar(textframe)
1552 self.textwidget = Text(textframe, setgrid=TRUE)
1553 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1554 self.textwidget.config(yscrollcommand=scroll.set)
1555 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1556 scroll.config(command=self.textwidget.yview)
1557 scroll.pack(side=RIGHT, fill=BOTH)
1558 textframe.pack(side=TOP)
1560 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1562 self.update() # Draw widget before executing fetchmail
1564 child_stdout = os.popen(command + " 2>&1", "r")
1566 ch = child_stdout.read(1)
1569 self.textwidget.insert(END, ch)
1570 self.textwidget.insert(END, "Done.")
1571 self.textwidget.see(END);
1574 Widget.destroy(self.master)
1576 # Here's where we choose either configuration or launching
1578 class MainWindow(Frame):
1579 def __init__(self, outfile, master=None):
1580 Frame.__init__(self, master)
1581 self.outfile = outfile
1582 self.master.title('fetchmail launcher');
1583 self.master.iconname('fetchmail launcher');
1586 text='Fetchmailconf ' + version,
1587 bd=2).pack(side=TOP, pady=10)
1588 self.keepalive = [] # Use this to anchor the PhotoImage object
1589 make_icon_window(self, fetchmail_gif)
1592 Message(self, text="""
1593 Use `Configure fetchmail' to tell fetchmail about the remote
1594 servers it should poll (the host name, your username there,
1595 whether to use POP or IMAP, and so forth).
1596 """, width=600).pack(side=TOP)
1597 self.configbutton = Button(self, text='Configure fetchmail',
1598 fg='blue', command=self.configure)
1599 self.configbutton.pack()
1601 Message(self, text="""
1602 Use `Test fetchmail' to run fetchmail with debugging enabled.
1603 This is a good way to test out a new configuration.
1604 """, width=600).pack(side=TOP)
1605 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1607 Message(self, text="""
1608 Use `Run fetchmail' to run fetchmail in foreground.
1609 Progress messages will be shown, but not debug messages.
1610 """, width=600).pack(side=TOP)
1611 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1613 Message(self, text="""
1614 Or you can just select `Quit' to exit the launcher now.
1615 """, width=600).pack(side=TOP)
1616 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1618 def configure(self):
1619 self.configbutton.configure(state=DISABLED)
1620 Configurator(self.outfile, Toplevel(),
1621 lambda self=self: self.configbutton.configure(state=NORMAL),
1625 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1628 RunWindow("fetchmail -d0", Toplevel(), self)
1633 # Functions for turning a dictionary into an instantiated object tree.
1635 def intersect(list1, list2):
1636 # Compute set intersection of lists
1643 def setdiff(list1, list2):
1644 # Compute set difference of lists
1651 def copy_instance(toclass, fromdict):
1652 # Initialize a class object of given type from a conformant dictionary.
1653 for fld in fromdict.keys():
1654 if not fld in dictmembers:
1655 dictmembers.append(fld)
1656 # The `optional' fields are the ones we can ignore for purposes of
1657 # conformability checking; they'll still get copied if they are
1658 # present in the dictionary.
1659 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1660 class_sig = setdiff(toclass.__dict__.keys(), optional)
1662 dict_keys = setdiff(fromdict.keys(), optional)
1664 common = intersect(class_sig, dict_keys)
1665 if 'typemap' in class_sig:
1666 class_sig.remove('typemap')
1667 if tuple(class_sig) != tuple(dict_keys):
1668 print "Fields don't match what fetchmailconf expected:"
1669 # print "Class signature: " + `class_sig`
1670 # print "Dictionary keys: " + `dict_keys`
1671 diff = setdiff(class_sig, common)
1673 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1674 diff = setdiff(dict_keys, common)
1676 print "Not matched in dictionary keys: " + `diff`
1679 for x in fromdict.keys():
1680 setattr(toclass, x, fromdict[x])
1683 # And this is the main sequence. How it works:
1685 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1686 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1687 # Run execfile on the file to pull fetchmailrc into Python global space.
1688 # You don't want static data, though; you want, instead, a tree of objects
1689 # with the same data members and added appropriate methods.
1691 # This is what the copy_instance function() is for. It tries to copy a
1692 # dictionary field by field into a class, aborting if the class and dictionary
1693 # have different data members (except for any typemap member in the class;
1694 # that one is strictly for use by the MyWidget supperclass).
1696 # Once the object tree is set up, require user to choose novice or expert
1697 # mode and instantiate an edit object for the configuration. Class methods
1698 # will take it all from there.
1700 # Options (not documented because they're for fetchmailconf debuggers only):
1701 # -d: Read the configuration and dump it to stdout before editing. Dump
1702 # the edited result to stdout as well.
1703 # -f: specify the run control file to read.
1705 if __name__ == '__main__':
1707 if not os.environ.has_key("DISPLAY"):
1708 print "fetchmailconf must be run under X"
1712 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1713 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1714 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1715 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1716 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1717 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1718 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1719 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1720 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1721 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1722 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1723 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1724 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1725 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1726 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1727 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1728 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1729 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1730 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1731 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1732 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1733 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1734 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1735 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1736 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1737 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1738 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1739 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1740 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1741 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1742 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1743 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1745 # Note on making icons: the above was generated by the following procedure:
1748 # data = open("fetchmail.gif", "rb").read()
1749 # print "fetchmail_gif =\\"
1750 # print repr(base64.encodestring(data))
1754 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1755 dump = rcfile = None;
1756 for (switch, val) in options:
1757 if (switch == '-d'):
1759 elif (switch == '-f'):
1762 # Get client host's FQDN
1763 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1766 ConfigurationDefaults = Configuration()
1767 ServerDefaults = Server()
1768 UserDefaults = User()
1770 # Read the existing configuration
1771 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1773 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1775 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1780 print "`" + cmd + "' run failure, status " + `s`
1783 print "Unknown error while running fetchmail --configdump"
1790 print "Can't read configuration output of fetchmail --configdump."
1796 # The tricky part -- initializing objects from the configuration global
1797 # `Configuration' is the top level of the object tree we're going to mung.
1798 # The dictmembers list is used to track the set of fields the dictionary
1799 # contains; in particular, we can use it to tell whether things like the
1800 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1802 Fetchmailrc = Configuration()
1803 copy_instance(Fetchmailrc, fetchmailrc)
1804 Fetchmailrc.servers = [];
1805 for server in fetchmailrc['servers']:
1807 copy_instance(Newsite, server)
1808 Fetchmailrc.servers.append(Newsite)
1810 for user in server['users']:
1812 copy_instance(Newuser, user)
1813 Newsite.users.append(Newuser)
1815 # We may want to display the configuration and quit
1817 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1819 # The theory here is that -f alone sets the rcfile location,
1820 # but -d and -f together mean the new configuration should go to stdout.
1821 if not rcfile and not dump:
1822 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1824 # OK, now run the configuration edit
1825 root = MainWindow(rcfile)
1828 # The following sets edit modes for GNU EMACS