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.auth = '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'),
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.auth != ServerDefaults.auth:
136 str = str + " auth " + self.auth
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 = TRUE # 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.properties = None # Extension properties
226 ('remote', 'String'),
227 # leave out mailboxes and localnames
228 ('password', 'String'),
230 ('smtpaddress', 'String'),
231 ('preconnect', 'String'),
232 ('postconnect', 'String'),
236 ('antispam', 'String'),
238 ('flush', 'Boolean'),
239 ('fetchall', 'Boolean'),
240 ('rewrite', 'Boolean'),
241 ('forcecr', 'Boolean'),
242 ('stripcr', 'Boolean'),
243 ('pass8bits', 'Boolean'),
244 ('mimedecode', 'Boolean'),
245 ('dropstatus', 'Boolean'),
248 ('fetchlimit', 'Int'),
249 ('batchlimit', 'Int'),
251 ('properties', 'String'))
255 str = str + "user \"" + self.remote + "\" there ";
257 str = str + "with password \"" + self.password + '" '
260 for x in self.localnames:
263 if (self.keep != UserDefaults.keep
264 or self.flush != UserDefaults.flush
265 or self.fetchall != UserDefaults.fetchall
266 or self.rewrite != UserDefaults.rewrite
267 or self.forcecr != UserDefaults.forcecr
268 or self.stripcr != UserDefaults.stripcr
269 or self.pass8bits != UserDefaults.pass8bits
270 or self.mimedecode != UserDefaults.mimedecode
271 or self.dropstatus != UserDefaults.dropstatus):
272 str = str + " options"
273 if self.keep != UserDefaults.keep:
274 str = str + flag2str(self.keep, 'keep')
275 if self.flush != UserDefaults.flush:
276 str = str + flag2str(self.flush, 'flush')
277 if self.fetchall != UserDefaults.fetchall:
278 str = str + flag2str(self.fetchall, 'fetchall')
279 if self.rewrite != UserDefaults.rewrite:
280 str = str + flag2str(self.rewrite, 'rewrite')
281 if self.forcecr != UserDefaults.forcecr:
282 str = str + flag2str(self.forcecr, 'forcecr')
283 if self.stripcr != UserDefaults.stripcr:
284 str = str + flag2str(self.stripcr, 'stripcr')
285 if self.pass8bits != UserDefaults.pass8bits:
286 str = str + flag2str(self.pass8bits, 'pass8bits')
287 if self.mimedecode != UserDefaults.mimedecode:
288 str = str + flag2str(self.mimedecode, 'mimedecode')
289 if self.dropstatus != UserDefaults.dropstatus:
290 str = str + flag2str(self.dropstatus, 'dropstatus')
291 if self.limit != UserDefaults.limit:
292 str = str + " limit " + `self.limit`
293 if self.warnings != UserDefaults.warnings:
294 str = str + " warnings " + `self.warnings`
295 if self.fetchlimit != UserDefaults.fetchlimit:
296 str = str + " fetchlimit " + `self.fetchlimit`
297 if self.batchlimit != UserDefaults.batchlimit:
298 str = str + " batchlimit " + `self.batchlimit`
299 if self.expunge != UserDefaults.expunge:
300 str = str + " expunge " + `self.expunge`
302 trimmed = self.smtphunt;
303 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
304 trimmed = trimmed[0:len(trimmed) - 1]
305 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
306 trimmed = trimmed[0:len(trimmed) - 1]
308 str = str + " smtphost "
313 str = str + " folder"
314 for x in self.mailboxes:
317 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
318 if getattr(self, fld):
319 str = str + " %s %s\n" % (fld, `getattr(self, fld)`)
320 if self.lmtp != UserDefaults.lmtp:
321 str = str + flag2str(self.lmtp, 'lmtp')
322 if self.antispam != UserDefaults.antispam:
323 str = str + " antispam " + self.antispam + "\n"
327 return "[User: " + repr(self) + "]"
333 defaultports = {"auto":0,
343 authlist = ("password", "kerberos")
346 'title' : 'List Selection Help',
347 'banner': 'List Selection',
349 You must select an item in the list box (by clicking on it).
352 def flag2str(value, string):
353 # make a string representation of a .fetchmailrc flag or negated flag
357 if value == FALSE: str = str + ("no ")
361 class LabeledEntry(Frame):
362 # widget consisting of entry field with caption to left
363 def bind(self, key, action):
364 self.E.bind(key, action)
367 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
368 Frame.__init__(self, Master)
369 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
370 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
371 self.L.pack({'side':'left'})
372 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
374 def ButtonBar(frame, legend, ref, alternatives, depth, command):
375 # array of radio buttons, caption to left, picking from a string list
377 width = len(alternatives) / depth;
378 Label(bar, text=legend).pack(side=LEFT)
379 for column in range(width):
380 subframe = Frame(bar)
381 for row in range(depth):
382 ind = width * row + column
383 Radiobutton(subframe,
384 {'text':alternatives[ind],
386 'value':alternatives[ind],
387 'command':command}).pack(side=TOP, anchor=W)
388 subframe.pack(side=LEFT)
392 def helpwin(helpdict):
393 # help message window with a self-destruct button
395 helpwin.title(helpdict['title'])
396 helpwin.iconname(helpdict['title'])
397 Label(helpwin, text=helpdict['banner']).pack()
398 textwin = Message(helpwin, text=helpdict['text'], width=600)
400 Button(helpwin, text='Done',
401 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
403 def make_icon_window(base, image):
405 # Some older pythons will error out on this
406 icon_image = PhotoImage(data=image)
407 icon_window = Toplevel()
408 Label(icon_window, image=icon_image, bg='black').pack()
409 base.master.iconwindow(icon_window)
410 # Avoid TkInter brain death. PhotoImage objects go out of
411 # scope when the enclosing function returns. Therefore
412 # we have to explicitly link them to something.
413 base.keepalive.append(icon_image)
417 class ListEdit(Frame):
418 # edit a list of values (duplicates not allowed) with a supplied editor hook
419 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
421 self.deletor = deletor
424 # Set up a widget to accept new elements
425 self.newval = StringVar(master)
426 newwin = LabeledEntry(master, newlegend, self.newval, '12')
427 newwin.bind('<Double-1>', self.handleNew)
428 newwin.bind('<Return>', self.handleNew)
429 newwin.pack(side=TOP, fill=X, anchor=E)
431 # Edit the existing list
432 listframe = Frame(master)
433 scroll = Scrollbar(listframe)
434 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
437 self.listwidget.insert(END, x)
438 listframe.pack(side=TOP, expand=YES, fill=BOTH)
439 self.listwidget.config(yscrollcommand=scroll.set)
440 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
441 scroll.config(command=self.listwidget.yview)
442 scroll.pack(side=RIGHT, fill=BOTH)
443 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
444 self.listwidget.bind('<Double-1>', self.handleList);
445 self.listwidget.bind('<Return>', self.handleList);
449 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
450 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
452 self.helptxt = helptxt
453 Button(bf, text='Help', fg='blue',
454 command=self.help).pack(side=RIGHT)
458 helpwin(self.helptxt)
460 def handleList(self, event):
463 def handleNew(self, event):
464 item = self.newval.get()
465 entire = self.listwidget.get(0, self.listwidget.index('end'));
466 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
467 self.listwidget.insert('end', item)
468 if self.list != None: self.list.append(item)
472 select = self.listwidget.curselection()
477 if index and self.editor:
478 label = self.listwidget.get(index);
479 apply(self.editor, (label,))
481 def deleteItem(self):
482 select = self.listwidget.curselection()
486 index = string.atoi(select[0])
487 label = self.listwidget.get(index);
488 self.listwidget.delete(index)
489 if self.list != None:
491 if self.deletor != None:
492 apply(self.deletor, (label,))
494 def ConfirmQuit(frame, context):
497 text = 'Really quit ' + context + ' without saving?',
499 strings = ('Yes', 'No'),
503 def dispose_window(master, legend, help, savelegend='OK'):
504 dispose = Frame(master, relief=RAISED, bd=5)
505 Label(dispose, text=legend).pack(side=TOP,pady=10)
506 Button(dispose, text=savelegend, fg='blue',
507 command=master.save).pack(side=LEFT)
508 Button(dispose, text='Quit', fg='blue',
509 command=master.nosave).pack(side=LEFT)
510 Button(dispose, text='Help', fg='blue',
511 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
516 # Common methods for Tkinter widgets -- deals with Tkinter declaration
517 def post(self, widgetclass, field):
518 for x in widgetclass.typemap:
519 if x[1] == 'Boolean':
520 setattr(self, x[0], BooleanVar(self))
521 elif x[1] == 'String':
522 setattr(self, x[0], StringVar(self))
524 setattr(self, x[0], IntVar(self))
525 source = getattr(getattr(self, field), x[0])
527 getattr(self, x[0]).set(source)
529 def fetch(self, widgetclass, field):
530 for x in widgetclass.typemap:
531 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
534 # First, code to set the global fetchmail run controls.
537 configure_novice_help = {
538 'title' : 'Fetchmail novice configurator help',
539 'banner': 'Novice configurator help',
541 In the `Novice Configurator Controls' panel, you can:
543 Press `Save' to save the new fetchmail configuration you have created.
544 Press `Quit' to exit without saving.
545 Press `Help' to bring up this help message.
547 In the `Novice Configuration' panels, you will set up the basic data
548 needed to create a simple fetchmail setup. These include:
550 1. The name of the remote site you want to query.
552 2. Your login name on that site.
554 3. Your password on that site.
556 4. A protocol to use (POP, IMAP, ETRN, etc.)
558 5. A polling interval.
560 6. Options to fetch old messages as well as new, uor to suppress
561 deletion of fetched message.
563 The novice-configuration code will assume that you want to forward mail
564 to a local sendmail listener with no special options.
567 configure_expert_help = {
568 'title' : 'Fetchmail expert configurator help',
569 'banner': 'Expert configurator help',
571 In the `Expert Configurator Controls' panel, you can:
573 Press `Save' to save the new fetchmail configuration you have edited.
574 Press `Quit' to exit without saving.
575 Press `Help' to bring up this help message.
577 In the `Run Controls' panel, you can set the following options that
578 control how fetchmail runs:
581 Number of seconds to wait between polls in the background.
582 If zero, fetchmail will run in foreground.
585 If empty, emit progress and error messages to stderr.
586 Otherwise this gives the name of the files to write to.
587 This field is ignored if the "Log to syslog?" option is on.
590 If empty, store seen-message IDs in .fetchids under user's home
591 directory. If nonempty, use given file name.
594 Who to send multidrop mail to as a last resort if no address can
595 be matched. Normally empty; in this case, fetchmail treats the
596 invoking user as the address of last resort unless that user is
597 root. If that user is root, fetchmail sends to `postmaster'.
600 If this option is on (the default) error mail goes to the sender.
601 Otherwise it goes to the postmaster.
604 If false (the default) fetchmail generates a Received line into
605 each message and generates a HELO from the machine it is running on.
606 If true, fetchmail generates no Received line and HELOs as if it were
609 In the `Remote Mail Configurations' panel, you can:
611 1. Enter the name of a new remote mail server you want fetchmail to query.
613 To do this, simply enter a label for the poll configuration in the
614 `New Server:' box. The label should be a DNS name of the server (unless
615 you are using ssh or some other tunneling method and will fill in the `via'
616 option on the site configuration screen).
618 2. Change the configuration of an existing site.
620 To do this, find the site's label in the listbox and double-click it.
621 This will take you to a site configuration dialogue.
625 class ConfigurationEdit(Frame, MyWidget):
626 def __init__(self, configuration, outfile, master, onexit):
628 self.configuration = configuration
629 self.outfile = outfile
630 self.container = master
632 ConfigurationEdit.mode_to_help = {
633 'novice':configure_novice_help, 'expert':configure_expert_help
636 def server_edit(self, sitename):
637 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
639 def server_delete(self, sitename):
641 del self.configuration[sitename]
645 def edit(self, mode):
647 Frame.__init__(self, self.container)
648 self.master.title('fetchmail ' + self.mode + ' configurator');
649 self.master.iconname('fetchmail ' + self.mode + ' configurator');
650 self.keepalive = [] # Use this to anchor the PhotoImage object
651 make_icon_window(self, fetchmail_gif)
653 self.post(Configuration, 'configuration')
656 'Configurator ' + self.mode + ' Controls',
657 ConfigurationEdit.mode_to_help[self.mode],
660 gf = Frame(self, relief=RAISED, bd = 5)
662 text='Fetchmail Run Controls',
663 bd=2).pack(side=TOP, pady=10)
668 if self.mode != 'novice':
670 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
671 log.pack(side=RIGHT, anchor=E)
673 # Set the poll interval
674 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
675 de.pack(side=RIGHT, anchor=E)
680 if self.mode != 'novice':
683 {'text':'Bounces to sender?',
684 'variable':self.bouncemail,
685 'relief':GROOVE}).pack(side=LEFT, anchor=W)
690 {'text':'Log to syslog?',
691 'variable':self.syslog,
692 'relief':GROOVE}).pack(side=LEFT, anchor=W)
693 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
694 log.pack(side=RIGHT, anchor=E)
698 {'text':'Invisible mode?',
699 'variable':self.invisible,
700 'relief':GROOVE}).pack(side=LEFT, anchor=W)
702 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
703 log.pack(side=RIGHT, anchor=E)
707 # Expert mode allows us to edit multiple sites
708 lf = Frame(self, relief=RAISED, bd=5)
710 text='Remote Mail Server Configurations',
711 bd=2).pack(side=TOP, pady=10)
712 ListEdit('New Server:',
713 map(lambda x: x.pollname, self.configuration.servers),
714 lambda site, self=self: self.server_edit(site),
715 lambda site, self=self: self.server_delete(site),
720 for sitename in self.subwidgets.keys():
721 self.subwidgets[sitename].destruct()
722 self.master.destroy()
726 if ConfirmQuit(self, self.mode + " configuration editor"):
730 for sitename in self.subwidgets.keys():
731 self.subwidgets[sitename].save()
732 self.fetch(Configuration, 'configuration')
736 elif not os.path.isfile(self.outfile) or Dialog(self,
737 title = 'Overwrite existing run control file?',
738 text = 'Really overwrite existing run control file?',
740 strings = ('Yes', 'No'),
741 default = 1).num == 0:
742 fm = open(self.outfile, 'w')
744 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
745 fm.write(`self.configuration`)
749 os.chmod(self.outfile, 0600)
753 # Server editing stuff.
756 'title' : 'Remote site help',
757 'banner': 'Remote sites',
759 When you add a site name to the list here,
760 you initialize an entry telling fetchmail
761 how to poll a new site.
763 When you select a sitename (by double-
764 clicking it, or by single-clicking to
765 select and then clicking the Edit button),
766 you will open a window to configure that
771 'title' : 'Server options help',
772 'banner': 'Server Options',
774 The server options screen controls fetchmail
775 options that apply to one of your mailservers.
777 Once you have a mailserver configuration set
778 up as you like it, you can select `OK' to
779 store it in the server list maintained in
780 the main configuration window.
782 If you wish to discard changes to a server
783 configuration, select `Quit'.
787 'title' : 'Run Control help',
788 'banner': 'Run Controls',
790 If the `Poll normally' checkbox is on, the host is polled as part of
791 the normal operation of fetchmail when it is run with no arguments.
792 If it is off, fetchmail will only query this host when it is given as
793 a command-line argument.
795 The `True name of server' box should specify the actual DNS name
796 to query. By default this is the same as the poll name.
798 Normally each host described in the file is queried once each
799 poll cycle. If `Cycles to skip between polls' is greater than 0,
800 that's the number of poll cycles that are skipped between the
801 times this post is actually polled.
803 The `Server timeout' is the number of seconds fetchmail will wait
804 for a reply from the mailserver before concluding it is hung and
809 'title' : 'Protocol and Port help',
810 'banner': 'Protocol and Port',
812 These options control the remote-mail protocol
813 and TCP/IP service port used to query this
816 If you click the `Probe for supported protocols'
817 button, fetchmail will try to find you the most
818 capable server on the selected host (this will
819 only work if you're conncted to the Internet).
820 The probe only checks for ordinary IMAP and POP
821 protocols; fortunately these are the most
822 frequently supported.
824 The `Protocol' button bar offers you a choice of
825 all the different protocols available. The `auto'
826 protocol is the default mode; it probes the host
827 ports for POP3 and IMAP to see if either is
830 Normally the TCP/IP service port to use is
831 dictated by the protocol choice. The `Port'
832 field (only present in expert mode) lets you
833 set a non-standard port.
837 'title' : 'Security option help',
838 'banner': 'Security',
840 The `interface' option allows you to specify a range
841 of IP addresses to monitor for activity. If these
842 addresses are not active, fetchmail will not poll.
843 Specifying this may protect you from a spoofing attack
844 if your client machine has more than one IP gateway
845 address and some of the gateways are to insecure nets.
847 The `monitor' option, if given, specifies the only
848 device through which fetchmail is permitted to connect
849 to servers. This option may be used to prevent
850 fetchmail from triggering an expensive dial-out if the
851 interface is not already active.
853 The `interface' and `monitor' options are available
854 only for Linux and freeBSD systems. See the fetchmail
855 manual page for details on these.
857 The `netsec' option will be configurable only if fetchmail
858 was compiled with IPV6 support. If you need to use it,
859 you probably know what to do.
863 'title' : 'Multidrop option help',
864 'banner': 'Multidrop',
866 These options are only useful with multidrop mode.
867 See the manual page for extended discussion.
871 'title' : 'User list help',
872 'banner': 'User list',
874 When you add a user name to the list here,
875 you initialize an entry telling fetchmail
876 to poll the site on behalf of the new user.
878 When you select a username (by double-
879 clicking it, or by single-clicking to
880 select and then clicking the Edit button),
881 you will open a window to configure the
882 user's options on that site.
885 class ServerEdit(Frame, MyWidget):
886 def __init__(self, host, parent):
890 for site in parent.configuration.servers:
891 if site.pollname == host:
893 if (self.server == None):
894 self.server = Server()
895 self.server.pollname = host
896 self.server.via = None
897 parent.configuration.servers.append(self.server)
899 def edit(self, mode, master=None):
900 Frame.__init__(self, master)
902 self.master.title('Fetchmail host ' + self.server.pollname);
903 self.master.iconname('Fetchmail host ' + self.server.pollname);
904 self.post(Server, 'server')
905 self.makeWidgets(self.server.pollname, mode)
906 self.keepalive = [] # Use this to anchor the PhotoImage object
907 make_icon_window(self, fetchmail_gif)
914 for username in self.subwidgets.keys():
915 self.subwidgets[username].destruct()
916 del self.parent.subwidgets[self.server.pollname]
917 Widget.destroy(self.master)
920 if ConfirmQuit(self, 'server option editing'):
924 self.fetch(Server, 'server')
925 for username in self.subwidgets.keys():
926 self.subwidgets[username].save()
929 def refreshPort(self):
930 proto = self.protocol.get()
931 if self.port.get() == 0:
932 self.port.set(defaultports[proto])
933 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
935 def user_edit(self, username, mode):
936 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
938 def user_delete(self, username):
939 if self.subwidgets.has_key(username):
940 del self.subwidgets[username]
941 del self.server[username]
943 def makeWidgets(self, host, mode):
944 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
946 leftwin = Frame(self);
950 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
951 Label(ctlwin, text="Run Controls").pack(side=TOP)
952 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
953 LabeledEntry(ctlwin, 'True name of ' + host + ':',
954 self.via, leftwidth).pack(side=TOP, fill=X)
955 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
956 self.interval, leftwidth).pack(side=TOP, fill=X)
957 LabeledEntry(ctlwin, 'Server timeout (seconds):',
958 self.timeout, leftwidth).pack(side=TOP, fill=X)
959 Button(ctlwin, text='Help', fg='blue',
960 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
963 # Compute the available protocols from the compile-time options
965 if 'pop2' in feature_options:
966 protolist.append("POP2")
967 if 'pop3' in feature_options:
968 protolist = protolist + ["POP3", "APOP", "KPOP"]
969 if 'sdps' in feature_options:
970 protolist.append("SDPS")
971 if 'imap' in feature_options:
972 protolist.append("IMAP")
973 if 'imap-gss' in feature_options:
974 protolist.append("IMAP-GSS")
975 if 'imap-k4' in feature_options:
976 protolist.append("IMAP-K4")
977 if 'etrn' in feature_options:
978 protolist.append("ETRN")
980 protwin = Frame(leftwin, relief=RAISED, bd=5)
981 Label(protwin, text="Protocol").pack(side=TOP)
982 ButtonBar(protwin, '',
983 self.protocol, protolist, 2,
986 LabeledEntry(protwin, 'On server TCP/IP port:',
987 self.port, leftwidth).pack(side=TOP, fill=X)
990 text="POP3: track `seen' with client-side UIDLs?",
991 variable=self.uidl).pack(side=TOP)
992 Button(protwin, text='Probe for supported protocols', fg='blue',
993 command=self.autoprobe).pack(side=LEFT)
994 Button(protwin, text='Help', fg='blue',
995 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
998 userwin = Frame(leftwin, relief=RAISED, bd=5)
999 Label(userwin, text="User entries for " + host).pack(side=TOP)
1000 ListEdit("New user: ",
1001 map(lambda x: x.remote, self.server.users),
1002 lambda u, m=mode, s=self: s.user_edit(u, m),
1003 lambda u, s=self: s.user_delete(u),
1005 userwin.pack(fill=X)
1007 leftwin.pack(side=LEFT, anchor=N, fill=X);
1009 if mode != 'novice':
1010 rightwin = Frame(self);
1012 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1013 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1014 LabeledEntry(mdropwin, 'Envelope address header:',
1015 self.envelope, '22').pack(side=TOP, fill=X)
1016 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1017 self.envskip, '22').pack(side=TOP, fill=X)
1018 LabeledEntry(mdropwin, 'Name prefix to strip:',
1019 self.qvirtual, '22').pack(side=TOP, fill=X)
1020 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1021 variable=self.dns).pack(side=TOP)
1022 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1023 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1024 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1025 ListEdit("New domain: ",
1026 self.server.localdomains, None, None, mdropwin, multihelp)
1027 mdropwin.pack(fill=X)
1029 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1030 secwin = Frame(rightwin, relief=RAISED, bd=5)
1031 Label(secwin, text="Security").pack(side=TOP)
1032 # Don't actually let users set this. KPOP sets it implicitly
1033 # ButtonBar(secwin, 'Authorization mode:',
1034 # self.auth, authlist, 1, None).pack(side=TOP)
1035 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1036 LabeledEntry(secwin, 'IP range to check before poll:',
1037 self.interface, leftwidth).pack(side=TOP, fill=X)
1038 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1039 LabeledEntry(secwin, 'Interface to monitor:',
1040 self.monitor, leftwidth).pack(side=TOP, fill=X)
1041 if 'netsec' in feature_options or 'netsec' in dictmembers:
1042 LabeledEntry(secwin, 'IPV6 security options:',
1043 self.netsec, leftwidth).pack(side=TOP, fill=X)
1044 Button(secwin, text='Help', fg='blue',
1045 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1048 rightwin.pack(side=LEFT, anchor=N);
1050 def autoprobe(self):
1051 # Note: this only handles case (1) near fetchmail.c:1032
1052 # We're assuming people smart enough to set up ssh tunneling
1053 # won't need autoprobing.
1055 realhost = self.server.via
1057 realhost = self.server.pollname
1059 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1060 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1062 sock.connect(realhost, port)
1063 greetline = sock.recv(1024)
1069 confwin = Toplevel()
1070 if greetline == None:
1071 title = "Autoprobe of " + realhost + " failed"
1073 Fetchmailconf didn't find any mailservers active.
1074 This could mean the host doesn't support any,
1075 or that your Internet connection is down, or
1076 that the host is so slow that the probe timed
1077 out before getting a response.
1081 # OK, now try to recognize potential problems
1083 if protocol == "POP2":
1084 warnings = warnings + """
1085 It appears you have somehow found a mailserver running only POP2.
1086 Congratulations. Have you considered a career in archaeology?
1088 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1089 Unless the first line of your fetchmail -V output includes the string "POP2",
1090 you'll have to build it from sources yourself with the configure
1091 switch --enable-POP2.
1094 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1095 warnings = warnings + """
1096 This appears to be an old version of the UC Davis POP server. These are
1097 dangerously unreliable (among other problems, they may drop your mailbox
1098 on the floor if your connection is interrupted during the session).
1100 It is strongly recommended that you find a better POP3 server. The fetchmail
1101 FAQ includes pointers to good ones.
1104 if string.find(greetline, "usa.net") > 0:
1105 warnings = warnings + """
1106 You appear to be using USA.NET's free mail service. Their POP3 servers
1107 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1108 fetchmail can compensate. They seem to require that fetchall be switched on
1109 (otherwise you won't necessarily see all your mail, not even new mail).
1110 They also botch the TOP command the fetchmail normally uses for retrieval
1111 (it only retrieves about 10 lines rather than the number specified).
1112 Turning on fetchall will disable the use of TOP.
1114 Therefore, it is strongly recommended that you turn on `fetchall' on all
1115 user entries associated with this server.
1118 if string.find(greetline, "sprynet.com") > 0:
1119 warnings = warnings + """
1120 You appear to be using a SpryNet server. In mid-1999 it was reported that
1121 the SpryNet TOP command marks messages seen. Therefore, for proper error
1122 recovery in the event of a line drop, it is strongly recommended that you
1123 turn on `fetchall' on all user entries associated with this server.
1127 # Steve VanDevender <stevev@efn.org> writes:
1128 # The only system I have seen this happen with is cucipop-1.31
1129 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1130 # 2.x and probably quite a few other systems. It appears to be a
1131 # bug or bad interaction with the SunOS realloc() -- it turns out
1132 # that internally cucipop does allocate a certain data structure in
1133 # multiples of 16, using realloc() to bump it up to the next
1134 # multiple if it needs more.
1136 # The distinctive symptom is that when there are 16 messages in the
1137 # inbox, you can RETR and DELE all 16 messages successfully, but on
1138 # QUIT cucipop returns something like "-ERR Error locking your
1139 # mailbox" and aborts without updating it.
1141 # The cucipop banner looks like:
1143 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1145 if string.find(greetline, "Cubic Circle") > 0:
1146 warnings = warnings + """
1147 I see your server is running cucipop. Better make sure the server box
1148 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1149 under that version, and doesn't cope with the result gracefully. Newer
1150 SunOS and Solaris machines run cucipop OK.
1152 if string.find(greetline, "QPOP") > 0:
1153 warnings = warnings + """
1154 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1155 knows all about qpopper.
1158 closebrak = string.find(greetline, ">")
1159 if closebrak > 0 and greetline[closebrak+1] == "\r":
1160 warnings = warnings + """
1161 It looks like you could use APOP on this server and avoid sending it your
1162 password in clear. You should talk to the mailserver administrator about
1166 if string.find(greetline, "csi.com") > 0:
1167 warnings = warnings + """
1168 It appears you're talking to CompuServe. You can use their special RPA
1169 service for authentication, but only if your fetchmail -V output's first
1170 line contains the string "RPA". This is not included in stock fetchmail
1171 binaries; to compile it in, rebuild from sources with the configure
1172 option --enable-RPA.
1174 if string.find(greetline, "IMAP2bis") > 0:
1175 warnings = warnings + """
1176 IMAP2bis servers have a minor problem; they can't peek at messages without
1177 marking them seen. If you take a line hit during the retrieval, the
1178 interrupted message may get left on the server, marked seen.
1180 To work around this, it is recommended that you set the `fetchall'
1181 option on all user entries associated with this server, so any stuck
1182 mail will be retrieved next time around.
1185 if string.find(greetline, "POP3 Server Ready") > 0:
1186 warnings = warnings + """
1187 Some server that uses this greeting line has been observed to choke on
1188 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1190 if string.find(greetline, "IMAP4rev1") > 0:
1191 warnings = warnings + """
1192 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1193 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1194 has therefore been extremely well tested with this class of server.
1197 warnings = warnings + """
1198 Fetchmail doesn't know anything special about this server type.
1201 # Display success window with warnings
1202 title = "Autoprobe of " + realhost + " succeeded"
1203 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1204 self.protocol.set(protocol)
1205 confwin.title(title)
1206 confwin.iconname(title)
1207 Label(confwin, text=title).pack()
1208 Message(confwin, text=confirm, width=600).pack()
1209 Button(confwin, text='Done',
1210 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1213 # User editing stuff
1217 'title' : 'User option help',
1218 'banner': 'User options',
1220 You may use this panel to set options
1221 that may differ between individual
1224 Once you have a user configuration set
1225 up as you like it, you can select `OK' to
1226 store it in the user list maintained in
1227 the site configuration window.
1229 If you wish to discard the changes you have
1230 made to user options, select `Quit'.
1234 'title' : 'Local name help',
1235 'banner': 'Local names',
1237 The local name(s) in a user entry are the
1238 people on the client machine who should
1239 receive mail from the poll described.
1241 Note: if a user entry has more than one
1242 local name, messages will be retrieved
1243 in multidrop mode. This complicates
1244 the configuration issues; see the manual
1245 page section on multidrop mode.
1248 class UserEdit(Frame, MyWidget):
1249 def __init__(self, username, parent):
1250 self.parent = parent
1252 for user in parent.server.users:
1253 if user.remote == username:
1255 if self.user == None:
1257 self.user.remote = username
1258 self.user.localnames = [username]
1259 parent.server.users.append(self.user)
1261 def edit(self, mode, master=None):
1262 Frame.__init__(self, master)
1264 self.master.title('Fetchmail user ' + self.user.remote
1265 + ' querying ' + self.parent.server.pollname);
1266 self.master.iconname('Fetchmail user ' + self.user.remote);
1267 self.post(User, 'user')
1268 self.makeWidgets(mode, self.parent.server.pollname)
1269 self.keepalive = [] # Use this to anchor the PhotoImage object
1270 make_icon_window(self, fetchmail_gif)
1273 # self.wait_window()
1277 del self.parent.subwidgets[self.user.remote]
1278 Widget.destroy(self.master)
1281 if ConfirmQuit(self, 'user option editing'):
1285 self.fetch(User, 'user')
1288 def makeWidgets(self, mode, servername):
1289 dispose_window(self,
1290 "User options for " + self.user.remote + " querying " + servername,
1293 if mode != 'novice':
1294 leftwin = Frame(self);
1298 secwin = Frame(leftwin, relief=RAISED, bd=5)
1299 Label(secwin, text="Authentication").pack(side=TOP)
1300 LabeledEntry(secwin, 'Password:',
1301 self.password, '12').pack(side=TOP, fill=X)
1302 secwin.pack(fill=X, anchor=N)
1304 names = Frame(leftwin, relief=RAISED, bd=5)
1305 Label(names, text="Local names").pack(side=TOP)
1306 ListEdit("New name: ",
1307 self.user.localnames, None, None, names, localhelp)
1308 names.pack(fill=X, anchor=N)
1310 if mode != 'novice':
1311 targwin = Frame(leftwin, relief=RAISED, bd=5)
1312 Label(targwin, text="Forwarding Options").pack(side=TOP)
1313 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1314 ListEdit("New listener:",
1315 self.user.smtphunt, None, None, targwin, None)
1316 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1317 self.smtpaddress, '26').pack(side=TOP, fill=X)
1318 LabeledEntry(targwin, 'Connection setup command:',
1319 self.preconnect, '26').pack(side=TOP, fill=X)
1320 LabeledEntry(targwin, 'Connection wrapup command:',
1321 self.postconnect, '26').pack(side=TOP, fill=X)
1322 LabeledEntry(targwin, 'Local delivery agent:',
1323 self.mda, '26').pack(side=TOP, fill=X)
1324 LabeledEntry(targwin, 'BSMTP output file:',
1325 self.bsmtp, '26').pack(side=TOP, fill=X)
1326 LabeledEntry(targwin, 'Listener spam-block codes:',
1327 self.antispam, '26').pack(side=TOP, fill=X)
1328 LabeledEntry(targwin, 'Pass-through properties:',
1329 self.properties, '26').pack(side=TOP, fill=X)
1330 Checkbutton(targwin, text="Use LMTP?",
1331 variable=self.lmtp).pack(side=TOP, fill=X)
1332 targwin.pack(fill=X, anchor=N)
1334 if mode != 'novice':
1335 leftwin.pack(side=LEFT, fill=X, anchor=N)
1336 rightwin = Frame(self)
1340 optwin = Frame(rightwin, relief=RAISED, bd=5)
1341 Label(optwin, text="Processing Options").pack(side=TOP)
1342 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1343 variable=self.keep).pack(side=TOP, anchor=W)
1344 Checkbutton(optwin, text="Fetch old messages as well as new",
1345 variable=self.fetchall).pack(side=TOP, anchor=W)
1346 if mode != 'novice':
1347 Checkbutton(optwin, text="Flush seen messages before retrieval",
1348 variable=self.flush).pack(side=TOP, anchor=W)
1349 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1350 variable=self.rewrite).pack(side=TOP, anchor=W)
1351 Checkbutton(optwin, text="Force CR/LF at end of each line",
1352 variable=self.forcecr).pack(side=TOP, anchor=W)
1353 Checkbutton(optwin, text="Strip CR from end of each line",
1354 variable=self.stripcr).pack(side=TOP, anchor=W)
1355 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1356 variable=self.pass8bits).pack(side=TOP, anchor=W)
1357 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1358 variable=self.mimedecode).pack(side=TOP, anchor=W)
1359 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1360 variable=self.dropstatus).pack(side=TOP, anchor=W)
1363 if mode != 'novice':
1364 limwin = Frame(rightwin, relief=RAISED, bd=5)
1365 Label(limwin, text="Resource Limits").pack(side=TOP)
1366 LabeledEntry(limwin, 'Message size limit:',
1367 self.limit, '30').pack(side=TOP, fill=X)
1368 LabeledEntry(limwin, 'Size warning interval:',
1369 self.warnings, '30').pack(side=TOP, fill=X)
1370 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1371 self.fetchlimit, '30').pack(side=TOP, fill=X)
1372 LabeledEntry(limwin, 'Max messages to forward per poll:',
1373 self.batchlimit, '30').pack(side=TOP, fill=X)
1374 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1375 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1376 self.expunge, '30').pack(side=TOP, fill=X)
1379 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1380 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1381 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1382 ListEdit("New folder:", self.user.mailboxes,
1383 None, None, foldwin, None)
1384 foldwin.pack(fill=X, anchor=N)
1386 if mode != 'novice':
1387 rightwin.pack(side=LEFT)
1393 # Top-level window that offers either novice or expert mode
1394 # (but not both at once; it disappears when one is selected).
1397 class Configurator(Frame):
1398 def __init__(self, outfile, master, onexit, parent):
1399 Frame.__init__(self, master)
1400 self.outfile = outfile
1401 self.onexit = onexit
1402 self.parent = parent
1403 self.master.title('fetchmail configurator');
1404 self.master.iconname('fetchmail configurator');
1406 self.keepalive = [] # Use this to anchor the PhotoImage object
1407 make_icon_window(self, fetchmail_gif)
1409 Message(self, text="""
1410 Use `Novice Configuration' for basic fetchmail setup;
1411 with this, you can easily set up a single-drop connection
1412 to one remote mail server.
1413 """, width=600).pack(side=TOP)
1414 Button(self, text='Novice Configuration',
1415 fg='blue', command=self.novice).pack()
1417 Message(self, text="""
1418 Use `Expert Configuration' for advanced fetchmail setup,
1419 including multiple-site or multidrop connections.
1420 """, width=600).pack(side=TOP)
1421 Button(self, text='Expert Configuration',
1422 fg='blue', command=self.expert).pack()
1424 Message(self, text="""
1425 Or you can just select `Quit' to leave the configurator now and
1426 return to the main panel.
1427 """, width=600).pack(side=TOP)
1428 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1431 self.master.destroy()
1432 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1435 self.master.destroy()
1436 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1439 self.master.destroy()
1442 # Run a command an a scrolling text widget, displaying its output
1444 class RunWindow(Frame):
1445 def __init__(self, command, master, parent):
1446 Frame.__init__(self, master)
1447 self.master = master
1448 self.master.title('fetchmail run window');
1449 self.master.iconname('fetchmail run window');
1452 text="Running "+command,
1453 bd=2).pack(side=TOP, pady=10)
1454 self.keepalive = [] # Use this to anchor the PhotoImage object
1455 make_icon_window(self, fetchmail_gif)
1457 # This is a scrolling text window
1458 textframe = Frame(self)
1459 scroll = Scrollbar(textframe)
1460 self.textwidget = Text(textframe, setgrid=TRUE)
1461 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1462 self.textwidget.config(yscrollcommand=scroll.set)
1463 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1464 scroll.config(command=self.textwidget.yview)
1465 scroll.pack(side=RIGHT, fill=BOTH)
1466 textframe.pack(side=TOP)
1468 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1470 self.update() # Draw widget before executing fetchmail
1472 child_stdout = os.popen(command + " 2>&1", "r")
1474 ch = child_stdout.read(1)
1477 self.textwidget.insert(END, ch)
1478 self.textwidget.insert(END, "Done.")
1479 self.textwidget.see(END);
1482 Widget.destroy(self.master)
1484 # Here's where we choose either configuration or launching
1486 class MainWindow(Frame):
1487 def __init__(self, outfile, master=None):
1488 Frame.__init__(self, master)
1489 self.outfile = outfile
1490 self.master.title('fetchmail launcher');
1491 self.master.iconname('fetchmail launcher');
1494 text='Fetchmailconf ' + version,
1495 bd=2).pack(side=TOP, pady=10)
1496 self.keepalive = [] # Use this to anchor the PhotoImage object
1497 make_icon_window(self, fetchmail_gif)
1500 Message(self, text="""
1501 Use `Configure fetchmail' to tell fetchmail about the remote
1502 servers it should poll (the host name, your username there,
1503 whether to use POP or IMAP, and so forth).
1504 """, width=600).pack(side=TOP)
1505 self.configbutton = Button(self, text='Configure fetchmail',
1506 fg='blue', command=self.configure)
1507 self.configbutton.pack()
1509 Message(self, text="""
1510 Use `Test fetchmail' to run fetchmail with debugging enabled.
1511 This is a good way to test out a new configuration.
1512 """, width=600).pack(side=TOP)
1513 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1515 Message(self, text="""
1516 Use `Run fetchmail' to run fetchmail in foreground.
1517 Progress messages will be shown, but not debug messages.
1518 """, width=600).pack(side=TOP)
1519 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1521 Message(self, text="""
1522 Or you can just select `Quit' to exit the launcher now.
1523 """, width=600).pack(side=TOP)
1524 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1526 def configure(self):
1527 self.configbutton.configure(state=DISABLED)
1528 Configurator(self.outfile, Toplevel(),
1529 lambda self=self: self.configbutton.configure(state=NORMAL),
1533 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1536 RunWindow("fetchmail -d0", Toplevel(), self)
1541 # Functions for turning a dictionary into an instantiated object tree.
1543 def intersect(list1, list2):
1544 # Compute set intersection of lists
1551 def setdiff(list1, list2):
1552 # Compute set difference of lists
1559 def copy_instance(toclass, fromdict):
1560 # Initialize a class object of given type from a conformant dictionary.
1561 for fld in fromdict.keys():
1562 if not fld in dictmembers:
1563 dictmembers.append(fld)
1564 # The `optional' fields are the ones we can ignore for purposes of
1565 # conformability checking; they'll still get copied if they are
1566 # present in the dictionary.
1567 optional = ('interface', 'monitor', 'netsec')
1568 class_sig = setdiff(toclass.__dict__.keys(), optional)
1570 dict_keys = setdiff(fromdict.keys(), optional)
1572 common = intersect(class_sig, dict_keys)
1573 if 'typemap' in class_sig:
1574 class_sig.remove('typemap')
1575 if tuple(class_sig) != tuple(dict_keys):
1576 print "Fields don't match what fetchmailconf expected:"
1577 # print "Class signature: " + `class_sig`
1578 # print "Dictionary keys: " + `dict_keys`
1579 diff = setdiff(class_sig, common)
1581 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1582 diff = setdiff(dict_keys, common)
1584 print "Not matched in dictionary keys: " + `diff`
1587 for x in fromdict.keys():
1588 setattr(toclass, x, fromdict[x])
1591 # And this is the main sequence. How it works:
1593 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1594 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1595 # Run execfile on the file to pull fetchmailrc into Python global space.
1596 # You don't want static data, though; you want, instead, a tree of objects
1597 # with the same data members and added appropriate methods.
1599 # This is what the copy_instance function() is for. It tries to copy a
1600 # dictionary field by field into a class, aborting if the class and dictionary
1601 # have different data members (except for any typemap member in the class;
1602 # that one is strictly for use by the MyWidget supperclass).
1604 # Once the object tree is set up, require user to choose novice or expert
1605 # mode and instantiate an edit object for the configuration. Class methods
1606 # will take it all from there.
1608 # Options (not documented because they're for fetchmailconf debuggers only):
1609 # -d: Read the configuration and dump it to stdout before editing. Dump
1610 # the edited result to stdout as well.
1611 # -f: specify the run control file to read.
1613 if __name__ == '__main__':
1616 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1617 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1618 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1619 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1620 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1621 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1622 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1623 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1624 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1625 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1626 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1627 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1628 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1629 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1630 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1631 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1632 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1633 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1634 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1635 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1636 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1637 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1638 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1639 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1640 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1641 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1642 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1643 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1644 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1645 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1646 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1647 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1649 # Note on making icons: the above was generated by the following procedure:
1652 # data = open("fetchmail.gif", "rb").read()
1653 # print "fetchmail_gif =\\"
1654 # print repr(base64.encodestring(data))
1658 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1659 dump = rcfile = None;
1660 for (switch, val) in options:
1661 if (switch == '-d'):
1663 elif (switch == '-f'):
1666 # Get client host's FQDN
1667 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1670 ConfigurationDefaults = Configuration()
1671 ServerDefaults = Server()
1672 UserDefaults = User()
1674 # Read the existing configuration
1675 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1677 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1679 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1684 print "`" + cmd + "' run failure, status " + `s`
1687 print "Unknown error while running fetchmail --configdump"
1694 print "Can't read configuration output of fetchmail --configdump."
1700 # The tricky part -- initializing objects from the configuration global
1701 # `Configuration' is the top level of the object tree we're going to mung.
1702 # The dictmembers list is used to track the set of fields the dictionary
1703 # contains; in particular, we can use it to tell whether things like the
1704 # monitor, interface, and netsec fields are present.
1706 Fetchmailrc = Configuration()
1707 copy_instance(Fetchmailrc, fetchmailrc)
1708 Fetchmailrc.servers = [];
1709 for server in fetchmailrc['servers']:
1711 copy_instance(Newsite, server)
1712 Fetchmailrc.servers.append(Newsite)
1714 for user in server['users']:
1716 copy_instance(Newuser, user)
1717 Newsite.users.append(Newuser)
1719 # We may want to display the configuration and quit
1721 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1723 # The theory here is that -f alone sets the rcfile location,
1724 # but -d and -f together mean the new configuration should go to stdout.
1725 if not rcfile and not dump:
1726 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1728 # OK, now run the configuration edit
1729 root = MainWindow(rcfile)
1732 # The following sets edit modes for GNU EMACS