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.
1111 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1112 warnings = warnings + """
1113 This appears to be an old version of the UC Davis POP server. These are
1114 dangerously unreliable (among other problems, they may drop your mailbox
1115 on the floor if your connection is interrupted during the session).
1117 It is strongly recommended that you find a better POP3 server. The fetchmail
1118 FAQ includes pointers to good ones.
1121 if string.find(greetline, "usa.net") > 0:
1122 warnings = warnings + """
1123 You appear to be using USA.NET's free mail service. Their POP3 servers
1124 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1125 fetchmail can compensate. They seem to require that fetchall be switched on
1126 (otherwise you won't necessarily see all your mail, not even new mail).
1127 They also botch the TOP command the fetchmail normally uses for retrieval
1128 (it only retrieves about 10 lines rather than the number specified).
1129 Turning on fetchall will disable the use of TOP.
1131 Therefore, it is strongly recommended that you turn on `fetchall' on all
1132 user entries associated with this server.
1135 if string.find(greetline, "OpenMail") > 0:
1136 warnings = warnings + """
1137 You appear to be using some version of HP OpenMail. Many versions of
1138 OpenMail do not process the "TOP" command correctly; the symptom is that
1139 only the header and first line of each message is retrieved. To work
1140 around this bug, turn on `fetchall' on all user entries associated with
1144 if string.find(greetline, "TEMS POP3") > 0:
1145 warnings = warnings + """
1146 Your POP3 server has "TEMS" in its header line. At least one such
1147 server does not process the "TOP" command correctly; the symptom is
1148 that fetchmail hangs when trying to retrieve mail. To work around
1149 this bug, turn on `fetchall' on all user entries associated with this
1153 if string.find(greetline, "GroupWise") > 0:
1154 warnings = warnings + """
1155 The Novell GroupWise IMAP server would be better named GroupFoolish;
1156 it is (according to the designer of IMAP) unusably broken. Among
1157 other things, it doesn't include a required content length in its
1158 BODY[TEXT] response.<p>
1160 Fetchmail works around this problem, but we strongly recommend voting
1161 with your dollars for a server that isn't brain-dead. If you stick
1162 with code as shoddy as GroupWise seems to be, you will probably pay
1163 for it with other problems.<p>
1166 if string.find(greetline, "sprynet.com") > 0:
1167 warnings = warnings + """
1168 You appear to be using a SpryNet server. In mid-1999 it was reported that
1169 the SpryNet TOP command marks messages seen. Therefore, for proper error
1170 recovery in the event of a line drop, it is strongly recommended that you
1171 turn on `fetchall' on all user entries associated with this server.
1175 # Steve VanDevender <stevev@efn.org> writes:
1176 # The only system I have seen this happen with is cucipop-1.31
1177 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1178 # 2.x and probably quite a few other systems. It appears to be a
1179 # bug or bad interaction with the SunOS realloc() -- it turns out
1180 # that internally cucipop does allocate a certain data structure in
1181 # multiples of 16, using realloc() to bump it up to the next
1182 # multiple if it needs more.
1184 # The distinctive symptom is that when there are 16 messages in the
1185 # inbox, you can RETR and DELE all 16 messages successfully, but on
1186 # QUIT cucipop returns something like "-ERR Error locking your
1187 # mailbox" and aborts without updating it.
1189 # The cucipop banner looks like:
1191 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1193 if string.find(greetline, "Cubic Circle") > 0:
1194 warnings = warnings + """
1195 I see your server is running cucipop. Better make sure the server box
1196 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1197 under that version, and doesn't cope with the result gracefully. Newer
1198 SunOS and Solaris machines run cucipop OK.
1201 if string.find(greetline, "QPOP") > 0:
1202 warnings = warnings + """
1203 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1204 knows all about qpopper. However, be aware that the 2.53 version of
1205 qpopper does something odd that causes fetchmail to hang with a socket
1206 error on very large messages. This is probably not a fetchmail bug, as
1207 it has been observed with fetchpop. The fix is to upgrade to qpopper
1208 3.0beta or a more recent version. Better yet, switch to IMAP.
1211 if string.find(greetline, "Imail") > 0:
1212 warnings = warnings + """
1213 We've seen a bug report indicating that this IMAP server (at least as of
1214 version 5.0.7) returns an invalid body size for messages with MIME
1215 attachments; the effect is to drop the attachments on the floor. We
1216 recommend you upgrade to a non-broken IMAP server.
1219 closebrak = string.find(greetline, ">")
1220 if closebrak > 0 and greetline[closebrak+1] == "\r":
1221 warnings = warnings + """
1222 It looks like you could use APOP on this server and avoid sending it your
1223 password in clear. You should talk to the mailserver administrator about
1227 if string.find(greetline, "IMAP2bis") > 0:
1228 warnings = warnings + """
1229 IMAP2bis servers have a minor problem; they can't peek at messages without
1230 marking them seen. If you take a line hit during the retrieval, the
1231 interrupted message may get left on the server, marked seen.
1233 To work around this, it is recommended that you set the `fetchall'
1234 option on all user entries associated with this server, so any stuck
1235 mail will be retrieved next time around.
1238 if string.find(greetline, "POP3 Server Ready") > 0:
1239 warnings = warnings + """
1240 Some server that uses this greeting line has been observed to choke on
1241 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1243 if string.find(greetline, "IMAP4rev1") > 0:
1244 warnings = warnings + """
1245 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1246 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1247 has therefore been extremely well tested with this class of server.
1250 warnings = warnings + """
1251 Fetchmail doesn't know anything special about this server type.
1254 # Display success window with warnings
1255 title = "Autoprobe of " + realhost + " succeeded"
1256 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1257 self.protocol.set(protocol)
1258 confwin.title(title)
1259 confwin.iconname(title)
1260 Label(confwin, text=title).pack()
1261 Message(confwin, text=confirm, width=600).pack()
1262 Button(confwin, text='Done',
1263 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1266 # User editing stuff
1270 'title' : 'User option help',
1271 'banner': 'User options',
1273 You may use this panel to set options
1274 that may differ between individual
1277 Once you have a user configuration set
1278 up as you like it, you can select `OK' to
1279 store it in the user list maintained in
1280 the site configuration window.
1282 If you wish to discard the changes you have
1283 made to user options, select `Quit'.
1287 'title' : 'Local name help',
1288 'banner': 'Local names',
1290 The local name(s) in a user entry are the
1291 people on the client machine who should
1292 receive mail from the poll described.
1294 Note: if a user entry has more than one
1295 local name, messages will be retrieved
1296 in multidrop mode. This complicates
1297 the configuration issues; see the manual
1298 page section on multidrop mode.
1301 class UserEdit(Frame, MyWidget):
1302 def __init__(self, username, parent):
1303 self.parent = parent
1305 for user in parent.server.users:
1306 if user.remote == username:
1308 if self.user == None:
1310 self.user.remote = username
1311 self.user.localnames = [username]
1312 parent.server.users.append(self.user)
1314 def edit(self, mode, master=None):
1315 Frame.__init__(self, master)
1317 self.master.title('Fetchmail user ' + self.user.remote
1318 + ' querying ' + self.parent.server.pollname);
1319 self.master.iconname('Fetchmail user ' + self.user.remote);
1320 self.post(User, 'user')
1321 self.makeWidgets(mode, self.parent.server.pollname)
1322 self.keepalive = [] # Use this to anchor the PhotoImage object
1323 make_icon_window(self, fetchmail_gif)
1326 # self.wait_window()
1330 del self.parent.subwidgets[self.user.remote]
1331 Widget.destroy(self.master)
1334 if ConfirmQuit(self, 'user option editing'):
1338 self.fetch(User, 'user')
1341 def makeWidgets(self, mode, servername):
1342 dispose_window(self,
1343 "User options for " + self.user.remote + " querying " + servername,
1346 if mode != 'novice':
1347 leftwin = Frame(self);
1351 secwin = Frame(leftwin, relief=RAISED, bd=5)
1352 Label(secwin, text="Authentication").pack(side=TOP)
1353 LabeledEntry(secwin, 'Password:',
1354 self.password, '12').pack(side=TOP, fill=X)
1355 secwin.pack(fill=X, anchor=N)
1357 if 'ssl' in feature_options or 'ssl' in dictmembers:
1358 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1359 Checkbutton(sslwin, text="Use SSL?",
1360 variable=self.ssl).pack(side=TOP, fill=X)
1361 LabeledEntry(sslwin, 'SSL key:',
1362 self.sslkey, '14').pack(side=TOP, fill=X)
1363 LabeledEntry(sslwin, 'SSL certificate:',
1364 self.sslcert, '14').pack(side=TOP, fill=X)
1365 sslwin.pack(fill=X, anchor=N)
1367 names = Frame(leftwin, relief=RAISED, bd=5)
1368 Label(names, text="Local names").pack(side=TOP)
1369 ListEdit("New name: ",
1370 self.user.localnames, None, None, names, localhelp)
1371 names.pack(fill=X, anchor=N)
1373 if mode != 'novice':
1374 targwin = Frame(leftwin, relief=RAISED, bd=5)
1375 Label(targwin, text="Forwarding Options").pack(side=TOP)
1376 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1377 ListEdit("New listener:",
1378 self.user.smtphunt, None, None, targwin, None)
1379 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1380 self.smtpaddress, '26').pack(side=TOP, fill=X)
1381 LabeledEntry(targwin, 'Connection setup command:',
1382 self.preconnect, '26').pack(side=TOP, fill=X)
1383 LabeledEntry(targwin, 'Connection wrapup command:',
1384 self.postconnect, '26').pack(side=TOP, fill=X)
1385 LabeledEntry(targwin, 'Local delivery agent:',
1386 self.mda, '26').pack(side=TOP, fill=X)
1387 LabeledEntry(targwin, 'BSMTP output file:',
1388 self.bsmtp, '26').pack(side=TOP, fill=X)
1389 LabeledEntry(targwin, 'Listener spam-block codes:',
1390 self.antispam, '26').pack(side=TOP, fill=X)
1391 LabeledEntry(targwin, 'Pass-through properties:',
1392 self.properties, '26').pack(side=TOP, fill=X)
1393 Checkbutton(targwin, text="Use LMTP?",
1394 variable=self.lmtp).pack(side=TOP, fill=X)
1395 targwin.pack(fill=X, anchor=N)
1397 if mode != 'novice':
1398 leftwin.pack(side=LEFT, fill=X, anchor=N)
1399 rightwin = Frame(self)
1403 optwin = Frame(rightwin, relief=RAISED, bd=5)
1404 Label(optwin, text="Processing Options").pack(side=TOP)
1405 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1406 variable=self.keep).pack(side=TOP, anchor=W)
1407 Checkbutton(optwin, text="Fetch old messages as well as new",
1408 variable=self.fetchall).pack(side=TOP, anchor=W)
1409 if mode != 'novice':
1410 Checkbutton(optwin, text="Flush seen messages before retrieval",
1411 variable=self.flush).pack(side=TOP, anchor=W)
1412 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1413 variable=self.rewrite).pack(side=TOP, anchor=W)
1414 Checkbutton(optwin, text="Force CR/LF at end of each line",
1415 variable=self.forcecr).pack(side=TOP, anchor=W)
1416 Checkbutton(optwin, text="Strip CR from end of each line",
1417 variable=self.stripcr).pack(side=TOP, anchor=W)
1418 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1419 variable=self.pass8bits).pack(side=TOP, anchor=W)
1420 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1421 variable=self.mimedecode).pack(side=TOP, anchor=W)
1422 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1423 variable=self.dropstatus).pack(side=TOP, anchor=W)
1426 if mode != 'novice':
1427 limwin = Frame(rightwin, relief=RAISED, bd=5)
1428 Label(limwin, text="Resource Limits").pack(side=TOP)
1429 LabeledEntry(limwin, 'Message size limit:',
1430 self.limit, '30').pack(side=TOP, fill=X)
1431 LabeledEntry(limwin, 'Size warning interval:',
1432 self.warnings, '30').pack(side=TOP, fill=X)
1433 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1434 self.fetchlimit, '30').pack(side=TOP, fill=X)
1435 LabeledEntry(limwin, 'Max messages to forward per poll:',
1436 self.batchlimit, '30').pack(side=TOP, fill=X)
1437 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1438 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1439 self.expunge, '30').pack(side=TOP, fill=X)
1442 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1443 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1444 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1445 ListEdit("New folder:", self.user.mailboxes,
1446 None, None, foldwin, None)
1447 foldwin.pack(fill=X, anchor=N)
1449 if mode != 'novice':
1450 rightwin.pack(side=LEFT)
1456 # Top-level window that offers either novice or expert mode
1457 # (but not both at once; it disappears when one is selected).
1460 class Configurator(Frame):
1461 def __init__(self, outfile, master, onexit, parent):
1462 Frame.__init__(self, master)
1463 self.outfile = outfile
1464 self.onexit = onexit
1465 self.parent = parent
1466 self.master.title('fetchmail configurator');
1467 self.master.iconname('fetchmail configurator');
1469 self.keepalive = [] # Use this to anchor the PhotoImage object
1470 make_icon_window(self, fetchmail_gif)
1472 Message(self, text="""
1473 Use `Novice Configuration' for basic fetchmail setup;
1474 with this, you can easily set up a single-drop connection
1475 to one remote mail server.
1476 """, width=600).pack(side=TOP)
1477 Button(self, text='Novice Configuration',
1478 fg='blue', command=self.novice).pack()
1480 Message(self, text="""
1481 Use `Expert Configuration' for advanced fetchmail setup,
1482 including multiple-site or multidrop connections.
1483 """, width=600).pack(side=TOP)
1484 Button(self, text='Expert Configuration',
1485 fg='blue', command=self.expert).pack()
1487 Message(self, text="""
1488 Or you can just select `Quit' to leave the configurator now and
1489 return to the main panel.
1490 """, width=600).pack(side=TOP)
1491 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1492 master.protocol("WM_DELETE_WINDOW", self.leave)
1495 self.master.destroy()
1496 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1499 self.master.destroy()
1500 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1503 self.master.destroy()
1506 # Run a command an a scrolling text widget, displaying its output
1508 class RunWindow(Frame):
1509 def __init__(self, command, master, parent):
1510 Frame.__init__(self, master)
1511 self.master = master
1512 self.master.title('fetchmail run window');
1513 self.master.iconname('fetchmail run window');
1516 text="Running "+command,
1517 bd=2).pack(side=TOP, pady=10)
1518 self.keepalive = [] # Use this to anchor the PhotoImage object
1519 make_icon_window(self, fetchmail_gif)
1521 # This is a scrolling text window
1522 textframe = Frame(self)
1523 scroll = Scrollbar(textframe)
1524 self.textwidget = Text(textframe, setgrid=TRUE)
1525 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1526 self.textwidget.config(yscrollcommand=scroll.set)
1527 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1528 scroll.config(command=self.textwidget.yview)
1529 scroll.pack(side=RIGHT, fill=BOTH)
1530 textframe.pack(side=TOP)
1532 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1534 self.update() # Draw widget before executing fetchmail
1536 child_stdout = os.popen(command + " 2>&1", "r")
1538 ch = child_stdout.read(1)
1541 self.textwidget.insert(END, ch)
1542 self.textwidget.insert(END, "Done.")
1543 self.textwidget.see(END);
1546 Widget.destroy(self.master)
1548 # Here's where we choose either configuration or launching
1550 class MainWindow(Frame):
1551 def __init__(self, outfile, master=None):
1552 Frame.__init__(self, master)
1553 self.outfile = outfile
1554 self.master.title('fetchmail launcher');
1555 self.master.iconname('fetchmail launcher');
1558 text='Fetchmailconf ' + version,
1559 bd=2).pack(side=TOP, pady=10)
1560 self.keepalive = [] # Use this to anchor the PhotoImage object
1561 make_icon_window(self, fetchmail_gif)
1564 Message(self, text="""
1565 Use `Configure fetchmail' to tell fetchmail about the remote
1566 servers it should poll (the host name, your username there,
1567 whether to use POP or IMAP, and so forth).
1568 """, width=600).pack(side=TOP)
1569 self.configbutton = Button(self, text='Configure fetchmail',
1570 fg='blue', command=self.configure)
1571 self.configbutton.pack()
1573 Message(self, text="""
1574 Use `Test fetchmail' to run fetchmail with debugging enabled.
1575 This is a good way to test out a new configuration.
1576 """, width=600).pack(side=TOP)
1577 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1579 Message(self, text="""
1580 Use `Run fetchmail' to run fetchmail in foreground.
1581 Progress messages will be shown, but not debug messages.
1582 """, width=600).pack(side=TOP)
1583 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1585 Message(self, text="""
1586 Or you can just select `Quit' to exit the launcher now.
1587 """, width=600).pack(side=TOP)
1588 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1590 def configure(self):
1591 self.configbutton.configure(state=DISABLED)
1592 Configurator(self.outfile, Toplevel(),
1593 lambda self=self: self.configbutton.configure(state=NORMAL),
1597 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1600 RunWindow("fetchmail -d0", Toplevel(), self)
1605 # Functions for turning a dictionary into an instantiated object tree.
1607 def intersect(list1, list2):
1608 # Compute set intersection of lists
1615 def setdiff(list1, list2):
1616 # Compute set difference of lists
1623 def copy_instance(toclass, fromdict):
1624 # Initialize a class object of given type from a conformant dictionary.
1625 for fld in fromdict.keys():
1626 if not fld in dictmembers:
1627 dictmembers.append(fld)
1628 # The `optional' fields are the ones we can ignore for purposes of
1629 # conformability checking; they'll still get copied if they are
1630 # present in the dictionary.
1631 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1632 class_sig = setdiff(toclass.__dict__.keys(), optional)
1634 dict_keys = setdiff(fromdict.keys(), optional)
1636 common = intersect(class_sig, dict_keys)
1637 if 'typemap' in class_sig:
1638 class_sig.remove('typemap')
1639 if tuple(class_sig) != tuple(dict_keys):
1640 print "Fields don't match what fetchmailconf expected:"
1641 # print "Class signature: " + `class_sig`
1642 # print "Dictionary keys: " + `dict_keys`
1643 diff = setdiff(class_sig, common)
1645 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1646 diff = setdiff(dict_keys, common)
1648 print "Not matched in dictionary keys: " + `diff`
1651 for x in fromdict.keys():
1652 setattr(toclass, x, fromdict[x])
1655 # And this is the main sequence. How it works:
1657 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1658 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1659 # Run execfile on the file to pull fetchmailrc into Python global space.
1660 # You don't want static data, though; you want, instead, a tree of objects
1661 # with the same data members and added appropriate methods.
1663 # This is what the copy_instance function() is for. It tries to copy a
1664 # dictionary field by field into a class, aborting if the class and dictionary
1665 # have different data members (except for any typemap member in the class;
1666 # that one is strictly for use by the MyWidget supperclass).
1668 # Once the object tree is set up, require user to choose novice or expert
1669 # mode and instantiate an edit object for the configuration. Class methods
1670 # will take it all from there.
1672 # Options (not documented because they're for fetchmailconf debuggers only):
1673 # -d: Read the configuration and dump it to stdout before editing. Dump
1674 # the edited result to stdout as well.
1675 # -f: specify the run control file to read.
1677 if __name__ == '__main__':
1680 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1681 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1682 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1683 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1684 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1685 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1686 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1687 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1688 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1689 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1690 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1691 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1692 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1693 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1694 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1695 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1696 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1697 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1698 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1699 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1700 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1701 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1702 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1703 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1704 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1705 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1706 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1707 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1708 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1709 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1710 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1711 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1713 # Note on making icons: the above was generated by the following procedure:
1716 # data = open("fetchmail.gif", "rb").read()
1717 # print "fetchmail_gif =\\"
1718 # print repr(base64.encodestring(data))
1722 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1723 dump = rcfile = None;
1724 for (switch, val) in options:
1725 if (switch == '-d'):
1727 elif (switch == '-f'):
1730 # Get client host's FQDN
1731 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1734 ConfigurationDefaults = Configuration()
1735 ServerDefaults = Server()
1736 UserDefaults = User()
1738 # Read the existing configuration
1739 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1741 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1743 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1748 print "`" + cmd + "' run failure, status " + `s`
1751 print "Unknown error while running fetchmail --configdump"
1758 print "Can't read configuration output of fetchmail --configdump."
1764 # The tricky part -- initializing objects from the configuration global
1765 # `Configuration' is the top level of the object tree we're going to mung.
1766 # The dictmembers list is used to track the set of fields the dictionary
1767 # contains; in particular, we can use it to tell whether things like the
1768 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1770 Fetchmailrc = Configuration()
1771 copy_instance(Fetchmailrc, fetchmailrc)
1772 Fetchmailrc.servers = [];
1773 for server in fetchmailrc['servers']:
1775 copy_instance(Newsite, server)
1776 Fetchmailrc.servers.append(Newsite)
1778 for user in server['users']:
1780 copy_instance(Newuser, user)
1781 Newsite.users.append(Newuser)
1783 # We may want to display the configuration and quit
1785 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1787 # The theory here is that -f alone sets the rcfile location,
1788 # but -d and -f together mean the new configuration should go to stdout.
1789 if not rcfile and not dump:
1790 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1792 # OK, now run the configuration edit
1793 root = MainWindow(rcfile)
1796 # The following sets edit modes for GNU EMACS