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 = 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 preauthlist = ("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.master.protocol('WM_DELETE_WINDOW', self.nosave)
651 self.keepalive = [] # Use this to anchor the PhotoImage object
652 make_icon_window(self, fetchmail_gif)
654 self.post(Configuration, 'configuration')
657 'Configurator ' + self.mode + ' Controls',
658 ConfigurationEdit.mode_to_help[self.mode],
661 gf = Frame(self, relief=RAISED, bd = 5)
663 text='Fetchmail Run Controls',
664 bd=2).pack(side=TOP, pady=10)
669 if self.mode != 'novice':
671 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
672 log.pack(side=RIGHT, anchor=E)
674 # Set the poll interval
675 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
676 de.pack(side=RIGHT, anchor=E)
681 if self.mode != 'novice':
684 {'text':'Bounces to sender?',
685 'variable':self.bouncemail,
686 'relief':GROOVE}).pack(side=LEFT, anchor=W)
691 {'text':'Log to syslog?',
692 'variable':self.syslog,
693 'relief':GROOVE}).pack(side=LEFT, anchor=W)
694 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
695 log.pack(side=RIGHT, anchor=E)
699 {'text':'Invisible mode?',
700 'variable':self.invisible,
701 'relief':GROOVE}).pack(side=LEFT, anchor=W)
703 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
704 log.pack(side=RIGHT, anchor=E)
708 # Expert mode allows us to edit multiple sites
709 lf = Frame(self, relief=RAISED, bd=5)
711 text='Remote Mail Server Configurations',
712 bd=2).pack(side=TOP, pady=10)
713 ListEdit('New Server:',
714 map(lambda x: x.pollname, self.configuration.servers),
715 lambda site, self=self: self.server_edit(site),
716 lambda site, self=self: self.server_delete(site),
721 for sitename in self.subwidgets.keys():
722 self.subwidgets[sitename].destruct()
723 self.master.destroy()
727 if ConfirmQuit(self, self.mode + " configuration editor"):
731 for sitename in self.subwidgets.keys():
732 self.subwidgets[sitename].save()
733 self.fetch(Configuration, 'configuration')
737 elif not os.path.isfile(self.outfile) or Dialog(self,
738 title = 'Overwrite existing run control file?',
739 text = 'Really overwrite existing run control file?',
741 strings = ('Yes', 'No'),
742 default = 1).num == 0:
743 fm = open(self.outfile, 'w')
745 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
746 fm.write(`self.configuration`)
750 os.chmod(self.outfile, 0600)
754 # Server editing stuff.
757 'title' : 'Remote site help',
758 'banner': 'Remote sites',
760 When you add a site name to the list here,
761 you initialize an entry telling fetchmail
762 how to poll a new site.
764 When you select a sitename (by double-
765 clicking it, or by single-clicking to
766 select and then clicking the Edit button),
767 you will open a window to configure that
772 'title' : 'Server options help',
773 'banner': 'Server Options',
775 The server options screen controls fetchmail
776 options that apply to one of your mailservers.
778 Once you have a mailserver configuration set
779 up as you like it, you can select `OK' to
780 store it in the server list maintained in
781 the main configuration window.
783 If you wish to discard changes to a server
784 configuration, select `Quit'.
788 'title' : 'Run Control help',
789 'banner': 'Run Controls',
791 If the `Poll normally' checkbox is on, the host is polled as part of
792 the normal operation of fetchmail when it is run with no arguments.
793 If it is off, fetchmail will only query this host when it is given as
794 a command-line argument.
796 The `True name of server' box should specify the actual DNS name
797 to query. By default this is the same as the poll name.
799 Normally each host described in the file is queried once each
800 poll cycle. If `Cycles to skip between polls' is greater than 0,
801 that's the number of poll cycles that are skipped between the
802 times this post is actually polled.
804 The `Server timeout' is the number of seconds fetchmail will wait
805 for a reply from the mailserver before concluding it is hung and
810 'title' : 'Protocol and Port help',
811 'banner': 'Protocol and Port',
813 These options control the remote-mail protocol
814 and TCP/IP service port used to query this
817 If you click the `Probe for supported protocols'
818 button, fetchmail will try to find you the most
819 capable server on the selected host (this will
820 only work if you're conncted to the Internet).
821 The probe only checks for ordinary IMAP and POP
822 protocols; fortunately these are the most
823 frequently supported.
825 The `Protocol' button bar offers you a choice of
826 all the different protocols available. The `auto'
827 protocol is the default mode; it probes the host
828 ports for POP3 and IMAP to see if either is
831 Normally the TCP/IP service port to use is
832 dictated by the protocol choice. The `Port'
833 field (only present in expert mode) lets you
834 set a non-standard port.
838 'title' : 'Security option help',
839 'banner': 'Security',
841 The `interface' option allows you to specify a range
842 of IP addresses to monitor for activity. If these
843 addresses are not active, fetchmail will not poll.
844 Specifying this may protect you from a spoofing attack
845 if your client machine has more than one IP gateway
846 address and some of the gateways are to insecure nets.
848 The `monitor' option, if given, specifies the only
849 device through which fetchmail is permitted to connect
850 to servers. This option may be used to prevent
851 fetchmail from triggering an expensive dial-out if the
852 interface is not already active.
854 The `interface' and `monitor' options are available
855 only for Linux and freeBSD systems. See the fetchmail
856 manual page for details on these.
858 The `netsec' option will be configurable only if fetchmail
859 was compiled with IPV6 support. If you need to use it,
860 you probably know what to do.
864 'title' : 'Multidrop option help',
865 'banner': 'Multidrop',
867 These options are only useful with multidrop mode.
868 See the manual page for extended discussion.
872 'title' : 'User list help',
873 'banner': 'User list',
875 When you add a user name to the list here,
876 you initialize an entry telling fetchmail
877 to poll the site on behalf of the new user.
879 When you select a username (by double-
880 clicking it, or by single-clicking to
881 select and then clicking the Edit button),
882 you will open a window to configure the
883 user's options on that site.
886 class ServerEdit(Frame, MyWidget):
887 def __init__(self, host, parent):
891 for site in parent.configuration.servers:
892 if site.pollname == host:
894 if (self.server == None):
895 self.server = Server()
896 self.server.pollname = host
897 self.server.via = None
898 parent.configuration.servers.append(self.server)
900 def edit(self, mode, master=None):
901 Frame.__init__(self, master)
903 self.master.title('Fetchmail host ' + self.server.pollname);
904 self.master.iconname('Fetchmail host ' + self.server.pollname);
905 self.post(Server, 'server')
906 self.makeWidgets(self.server.pollname, mode)
907 self.keepalive = [] # Use this to anchor the PhotoImage object
908 make_icon_window(self, fetchmail_gif)
915 for username in self.subwidgets.keys():
916 self.subwidgets[username].destruct()
917 del self.parent.subwidgets[self.server.pollname]
918 Widget.destroy(self.master)
921 if ConfirmQuit(self, 'server option editing'):
925 self.fetch(Server, 'server')
926 for username in self.subwidgets.keys():
927 self.subwidgets[username].save()
930 def refreshPort(self):
931 proto = self.protocol.get()
932 if self.port.get() == 0:
933 self.port.set(defaultports[proto])
934 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
936 def user_edit(self, username, mode):
937 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
939 def user_delete(self, username):
940 if self.subwidgets.has_key(username):
941 del self.subwidgets[username]
942 del self.server[username]
944 def makeWidgets(self, host, mode):
945 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
947 leftwin = Frame(self);
951 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
952 Label(ctlwin, text="Run Controls").pack(side=TOP)
953 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
954 LabeledEntry(ctlwin, 'True name of ' + host + ':',
955 self.via, leftwidth).pack(side=TOP, fill=X)
956 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
957 self.interval, leftwidth).pack(side=TOP, fill=X)
958 LabeledEntry(ctlwin, 'Server timeout (seconds):',
959 self.timeout, leftwidth).pack(side=TOP, fill=X)
960 Button(ctlwin, text='Help', fg='blue',
961 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
964 # Compute the available protocols from the compile-time options
966 if 'pop2' in feature_options:
967 protolist.append("POP2")
968 if 'pop3' in feature_options:
969 protolist = protolist + ["POP3", "APOP", "KPOP"]
970 if 'sdps' in feature_options:
971 protolist.append("SDPS")
972 if 'imap' in feature_options:
973 protolist.append("IMAP")
974 if 'imap-gss' in feature_options:
975 protolist.append("IMAP-GSS")
976 if 'imap-k4' in feature_options:
977 protolist.append("IMAP-K4")
978 if 'etrn' in feature_options:
979 protolist.append("ETRN")
981 protwin = Frame(leftwin, relief=RAISED, bd=5)
982 Label(protwin, text="Protocol").pack(side=TOP)
983 ButtonBar(protwin, '',
984 self.protocol, protolist, 2,
987 LabeledEntry(protwin, 'On server TCP/IP port:',
988 self.port, leftwidth).pack(side=TOP, fill=X)
991 text="POP3: track `seen' with client-side UIDLs?",
992 variable=self.uidl).pack(side=TOP)
993 Button(protwin, text='Probe for supported protocols', fg='blue',
994 command=self.autoprobe).pack(side=LEFT)
995 Button(protwin, text='Help', fg='blue',
996 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
999 userwin = Frame(leftwin, relief=RAISED, bd=5)
1000 Label(userwin, text="User entries for " + host).pack(side=TOP)
1001 ListEdit("New user: ",
1002 map(lambda x: x.remote, self.server.users),
1003 lambda u, m=mode, s=self: s.user_edit(u, m),
1004 lambda u, s=self: s.user_delete(u),
1006 userwin.pack(fill=X)
1008 leftwin.pack(side=LEFT, anchor=N, fill=X);
1010 if mode != 'novice':
1011 rightwin = Frame(self);
1013 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1014 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1015 LabeledEntry(mdropwin, 'Envelope address header:',
1016 self.envelope, '22').pack(side=TOP, fill=X)
1017 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1018 self.envskip, '22').pack(side=TOP, fill=X)
1019 LabeledEntry(mdropwin, 'Name prefix to strip:',
1020 self.qvirtual, '22').pack(side=TOP, fill=X)
1021 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1022 variable=self.dns).pack(side=TOP)
1023 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1024 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1025 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1026 ListEdit("New domain: ",
1027 self.server.localdomains, None, None, mdropwin, multihelp)
1028 mdropwin.pack(fill=X)
1030 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1031 secwin = Frame(rightwin, relief=RAISED, bd=5)
1032 Label(secwin, text="Security").pack(side=TOP)
1033 # Don't actually let users set this. KPOP sets it implicitly
1034 # ButtonBar(secwin, 'Preauthorization mode:',
1035 # self.preauth, preauthlist, 1, None).pack(side=TOP)
1036 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1037 LabeledEntry(secwin, 'IP range to check before poll:',
1038 self.interface, leftwidth).pack(side=TOP, fill=X)
1039 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1040 LabeledEntry(secwin, 'Interface to monitor:',
1041 self.monitor, leftwidth).pack(side=TOP, fill=X)
1042 if 'netsec' in feature_options or 'netsec' in dictmembers:
1043 LabeledEntry(secwin, 'IPV6 security options:',
1044 self.netsec, leftwidth).pack(side=TOP, fill=X)
1045 Button(secwin, text='Help', fg='blue',
1046 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1049 rightwin.pack(side=LEFT, anchor=N);
1051 def autoprobe(self):
1052 # Note: this only handles case (1) near fetchmail.c:1032
1053 # We're assuming people smart enough to set up ssh tunneling
1054 # won't need autoprobing.
1056 realhost = self.server.via
1058 realhost = self.server.pollname
1060 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1061 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1063 sock.connect(realhost, port)
1064 greetline = sock.recv(1024)
1070 confwin = Toplevel()
1071 if greetline == None:
1072 title = "Autoprobe of " + realhost + " failed"
1074 Fetchmailconf didn't find any mailservers active.
1075 This could mean the host doesn't support any,
1076 or that your Internet connection is down, or
1077 that the host is so slow that the probe timed
1078 out before getting a response.
1082 # OK, now try to recognize potential problems
1084 if protocol == "POP2":
1085 warnings = warnings + """
1086 It appears you have somehow found a mailserver running only POP2.
1087 Congratulations. Have you considered a career in archaeology?
1089 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1090 Unless the first line of your fetchmail -V output includes the string "POP2",
1091 you'll have to build it from sources yourself with the configure
1092 switch --enable-POP2.
1095 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1096 warnings = warnings + """
1097 This appears to be an old version of the UC Davis POP server. These are
1098 dangerously unreliable (among other problems, they may drop your mailbox
1099 on the floor if your connection is interrupted during the session).
1101 It is strongly recommended that you find a better POP3 server. The fetchmail
1102 FAQ includes pointers to good ones.
1105 if string.find(greetline, "usa.net") > 0:
1106 warnings = warnings + """
1107 You appear to be using USA.NET's free mail service. Their POP3 servers
1108 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1109 fetchmail can compensate. They seem to require that fetchall be switched on
1110 (otherwise you won't necessarily see all your mail, not even new mail).
1111 They also botch the TOP command the fetchmail normally uses for retrieval
1112 (it only retrieves about 10 lines rather than the number specified).
1113 Turning on fetchall will disable the use of TOP.
1115 Therefore, it is strongly recommended that you turn on `fetchall' on all
1116 user entries associated with this server.
1119 if string.find(greetline, "OpenMail") > 0:
1120 warnings = warnings + """
1121 You appear to be using some version of HP OpenMail. Many versions of
1122 OpenMail do not process the "TOP" command correctly; the symptom is that
1123 only the header and first line of each message is retrieved. To work
1124 around this bug, turn on `fetchall' on all user entries associated with
1128 if string.find(greetline, "sprynet.com") > 0:
1129 warnings = warnings + """
1130 You appear to be using a SpryNet server. In mid-1999 it was reported that
1131 the SpryNet TOP command marks messages seen. Therefore, for proper error
1132 recovery in the event of a line drop, it is strongly recommended that you
1133 turn on `fetchall' on all user entries associated with this server.
1137 # Steve VanDevender <stevev@efn.org> writes:
1138 # The only system I have seen this happen with is cucipop-1.31
1139 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1140 # 2.x and probably quite a few other systems. It appears to be a
1141 # bug or bad interaction with the SunOS realloc() -- it turns out
1142 # that internally cucipop does allocate a certain data structure in
1143 # multiples of 16, using realloc() to bump it up to the next
1144 # multiple if it needs more.
1146 # The distinctive symptom is that when there are 16 messages in the
1147 # inbox, you can RETR and DELE all 16 messages successfully, but on
1148 # QUIT cucipop returns something like "-ERR Error locking your
1149 # mailbox" and aborts without updating it.
1151 # The cucipop banner looks like:
1153 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1155 if string.find(greetline, "Cubic Circle") > 0:
1156 warnings = warnings + """
1157 I see your server is running cucipop. Better make sure the server box
1158 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1159 under that version, and doesn't cope with the result gracefully. Newer
1160 SunOS and Solaris machines run cucipop OK.
1163 if string.find(greetline, "QPOP") > 0:
1164 warnings = warnings + """
1165 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1166 knows all about qpopper. However, be aware that the 2.53 version of
1167 qpopper does something odd that causes fetchmail to hang with a socket
1168 error on very large messages. This is probably not a fetchmail bug, as
1169 it has been observed with fetchpop. The fix is to upgrade to qpopper
1170 3.0beta or a more recent version. Better yet, switch to IMAP.
1173 closebrak = string.find(greetline, ">")
1174 if closebrak > 0 and greetline[closebrak+1] == "\r":
1175 warnings = warnings + """
1176 It looks like you could use APOP on this server and avoid sending it your
1177 password in clear. You should talk to the mailserver administrator about
1181 if string.find(greetline, "IMAP2bis") > 0:
1182 warnings = warnings + """
1183 IMAP2bis servers have a minor problem; they can't peek at messages without
1184 marking them seen. If you take a line hit during the retrieval, the
1185 interrupted message may get left on the server, marked seen.
1187 To work around this, it is recommended that you set the `fetchall'
1188 option on all user entries associated with this server, so any stuck
1189 mail will be retrieved next time around.
1192 if string.find(greetline, "POP3 Server Ready") > 0:
1193 warnings = warnings + """
1194 Some server that uses this greeting line has been observed to choke on
1195 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1197 if string.find(greetline, "IMAP4rev1") > 0:
1198 warnings = warnings + """
1199 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1200 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1201 has therefore been extremely well tested with this class of server.
1204 warnings = warnings + """
1205 Fetchmail doesn't know anything special about this server type.
1208 # Display success window with warnings
1209 title = "Autoprobe of " + realhost + " succeeded"
1210 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1211 self.protocol.set(protocol)
1212 confwin.title(title)
1213 confwin.iconname(title)
1214 Label(confwin, text=title).pack()
1215 Message(confwin, text=confirm, width=600).pack()
1216 Button(confwin, text='Done',
1217 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1220 # User editing stuff
1224 'title' : 'User option help',
1225 'banner': 'User options',
1227 You may use this panel to set options
1228 that may differ between individual
1231 Once you have a user configuration set
1232 up as you like it, you can select `OK' to
1233 store it in the user list maintained in
1234 the site configuration window.
1236 If you wish to discard the changes you have
1237 made to user options, select `Quit'.
1241 'title' : 'Local name help',
1242 'banner': 'Local names',
1244 The local name(s) in a user entry are the
1245 people on the client machine who should
1246 receive mail from the poll described.
1248 Note: if a user entry has more than one
1249 local name, messages will be retrieved
1250 in multidrop mode. This complicates
1251 the configuration issues; see the manual
1252 page section on multidrop mode.
1255 class UserEdit(Frame, MyWidget):
1256 def __init__(self, username, parent):
1257 self.parent = parent
1259 for user in parent.server.users:
1260 if user.remote == username:
1262 if self.user == None:
1264 self.user.remote = username
1265 self.user.localnames = [username]
1266 parent.server.users.append(self.user)
1268 def edit(self, mode, master=None):
1269 Frame.__init__(self, master)
1271 self.master.title('Fetchmail user ' + self.user.remote
1272 + ' querying ' + self.parent.server.pollname);
1273 self.master.iconname('Fetchmail user ' + self.user.remote);
1274 self.post(User, 'user')
1275 self.makeWidgets(mode, self.parent.server.pollname)
1276 self.keepalive = [] # Use this to anchor the PhotoImage object
1277 make_icon_window(self, fetchmail_gif)
1280 # self.wait_window()
1284 del self.parent.subwidgets[self.user.remote]
1285 Widget.destroy(self.master)
1288 if ConfirmQuit(self, 'user option editing'):
1292 self.fetch(User, 'user')
1295 def makeWidgets(self, mode, servername):
1296 dispose_window(self,
1297 "User options for " + self.user.remote + " querying " + servername,
1300 if mode != 'novice':
1301 leftwin = Frame(self);
1305 secwin = Frame(leftwin, relief=RAISED, bd=5)
1306 Label(secwin, text="Authentication").pack(side=TOP)
1307 LabeledEntry(secwin, 'Password:',
1308 self.password, '12').pack(side=TOP, fill=X)
1309 secwin.pack(fill=X, anchor=N)
1311 names = Frame(leftwin, relief=RAISED, bd=5)
1312 Label(names, text="Local names").pack(side=TOP)
1313 ListEdit("New name: ",
1314 self.user.localnames, None, None, names, localhelp)
1315 names.pack(fill=X, anchor=N)
1317 if mode != 'novice':
1318 targwin = Frame(leftwin, relief=RAISED, bd=5)
1319 Label(targwin, text="Forwarding Options").pack(side=TOP)
1320 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1321 ListEdit("New listener:",
1322 self.user.smtphunt, None, None, targwin, None)
1323 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1324 self.smtpaddress, '26').pack(side=TOP, fill=X)
1325 LabeledEntry(targwin, 'Connection setup command:',
1326 self.preconnect, '26').pack(side=TOP, fill=X)
1327 LabeledEntry(targwin, 'Connection wrapup command:',
1328 self.postconnect, '26').pack(side=TOP, fill=X)
1329 LabeledEntry(targwin, 'Local delivery agent:',
1330 self.mda, '26').pack(side=TOP, fill=X)
1331 LabeledEntry(targwin, 'BSMTP output file:',
1332 self.bsmtp, '26').pack(side=TOP, fill=X)
1333 LabeledEntry(targwin, 'Listener spam-block codes:',
1334 self.antispam, '26').pack(side=TOP, fill=X)
1335 LabeledEntry(targwin, 'Pass-through properties:',
1336 self.properties, '26').pack(side=TOP, fill=X)
1337 Checkbutton(targwin, text="Use LMTP?",
1338 variable=self.lmtp).pack(side=TOP, fill=X)
1339 targwin.pack(fill=X, anchor=N)
1341 if mode != 'novice':
1342 leftwin.pack(side=LEFT, fill=X, anchor=N)
1343 rightwin = Frame(self)
1347 optwin = Frame(rightwin, relief=RAISED, bd=5)
1348 Label(optwin, text="Processing Options").pack(side=TOP)
1349 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1350 variable=self.keep).pack(side=TOP, anchor=W)
1351 Checkbutton(optwin, text="Fetch old messages as well as new",
1352 variable=self.fetchall).pack(side=TOP, anchor=W)
1353 if mode != 'novice':
1354 Checkbutton(optwin, text="Flush seen messages before retrieval",
1355 variable=self.flush).pack(side=TOP, anchor=W)
1356 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1357 variable=self.rewrite).pack(side=TOP, anchor=W)
1358 Checkbutton(optwin, text="Force CR/LF at end of each line",
1359 variable=self.forcecr).pack(side=TOP, anchor=W)
1360 Checkbutton(optwin, text="Strip CR from end of each line",
1361 variable=self.stripcr).pack(side=TOP, anchor=W)
1362 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1363 variable=self.pass8bits).pack(side=TOP, anchor=W)
1364 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1365 variable=self.mimedecode).pack(side=TOP, anchor=W)
1366 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1367 variable=self.dropstatus).pack(side=TOP, anchor=W)
1370 if mode != 'novice':
1371 limwin = Frame(rightwin, relief=RAISED, bd=5)
1372 Label(limwin, text="Resource Limits").pack(side=TOP)
1373 LabeledEntry(limwin, 'Message size limit:',
1374 self.limit, '30').pack(side=TOP, fill=X)
1375 LabeledEntry(limwin, 'Size warning interval:',
1376 self.warnings, '30').pack(side=TOP, fill=X)
1377 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1378 self.fetchlimit, '30').pack(side=TOP, fill=X)
1379 LabeledEntry(limwin, 'Max messages to forward per poll:',
1380 self.batchlimit, '30').pack(side=TOP, fill=X)
1381 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1382 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1383 self.expunge, '30').pack(side=TOP, fill=X)
1386 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1387 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1388 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1389 ListEdit("New folder:", self.user.mailboxes,
1390 None, None, foldwin, None)
1391 foldwin.pack(fill=X, anchor=N)
1393 if mode != 'novice':
1394 rightwin.pack(side=LEFT)
1400 # Top-level window that offers either novice or expert mode
1401 # (but not both at once; it disappears when one is selected).
1404 class Configurator(Frame):
1405 def __init__(self, outfile, master, onexit, parent):
1406 Frame.__init__(self, master)
1407 self.outfile = outfile
1408 self.onexit = onexit
1409 self.parent = parent
1410 self.master.title('fetchmail configurator');
1411 self.master.iconname('fetchmail configurator');
1413 self.keepalive = [] # Use this to anchor the PhotoImage object
1414 make_icon_window(self, fetchmail_gif)
1416 Message(self, text="""
1417 Use `Novice Configuration' for basic fetchmail setup;
1418 with this, you can easily set up a single-drop connection
1419 to one remote mail server.
1420 """, width=600).pack(side=TOP)
1421 Button(self, text='Novice Configuration',
1422 fg='blue', command=self.novice).pack()
1424 Message(self, text="""
1425 Use `Expert Configuration' for advanced fetchmail setup,
1426 including multiple-site or multidrop connections.
1427 """, width=600).pack(side=TOP)
1428 Button(self, text='Expert Configuration',
1429 fg='blue', command=self.expert).pack()
1431 Message(self, text="""
1432 Or you can just select `Quit' to leave the configurator now and
1433 return to the main panel.
1434 """, width=600).pack(side=TOP)
1435 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1436 master.protocol("WM_DELETE_WINDOW", self.leave)
1439 self.master.destroy()
1440 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1443 self.master.destroy()
1444 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1447 self.master.destroy()
1450 # Run a command an a scrolling text widget, displaying its output
1452 class RunWindow(Frame):
1453 def __init__(self, command, master, parent):
1454 Frame.__init__(self, master)
1455 self.master = master
1456 self.master.title('fetchmail run window');
1457 self.master.iconname('fetchmail run window');
1460 text="Running "+command,
1461 bd=2).pack(side=TOP, pady=10)
1462 self.keepalive = [] # Use this to anchor the PhotoImage object
1463 make_icon_window(self, fetchmail_gif)
1465 # This is a scrolling text window
1466 textframe = Frame(self)
1467 scroll = Scrollbar(textframe)
1468 self.textwidget = Text(textframe, setgrid=TRUE)
1469 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1470 self.textwidget.config(yscrollcommand=scroll.set)
1471 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1472 scroll.config(command=self.textwidget.yview)
1473 scroll.pack(side=RIGHT, fill=BOTH)
1474 textframe.pack(side=TOP)
1476 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1478 self.update() # Draw widget before executing fetchmail
1480 child_stdout = os.popen(command + " 2>&1", "r")
1482 ch = child_stdout.read(1)
1485 self.textwidget.insert(END, ch)
1486 self.textwidget.insert(END, "Done.")
1487 self.textwidget.see(END);
1490 Widget.destroy(self.master)
1492 # Here's where we choose either configuration or launching
1494 class MainWindow(Frame):
1495 def __init__(self, outfile, master=None):
1496 Frame.__init__(self, master)
1497 self.outfile = outfile
1498 self.master.title('fetchmail launcher');
1499 self.master.iconname('fetchmail launcher');
1502 text='Fetchmailconf ' + version,
1503 bd=2).pack(side=TOP, pady=10)
1504 self.keepalive = [] # Use this to anchor the PhotoImage object
1505 make_icon_window(self, fetchmail_gif)
1508 Message(self, text="""
1509 Use `Configure fetchmail' to tell fetchmail about the remote
1510 servers it should poll (the host name, your username there,
1511 whether to use POP or IMAP, and so forth).
1512 """, width=600).pack(side=TOP)
1513 self.configbutton = Button(self, text='Configure fetchmail',
1514 fg='blue', command=self.configure)
1515 self.configbutton.pack()
1517 Message(self, text="""
1518 Use `Test fetchmail' to run fetchmail with debugging enabled.
1519 This is a good way to test out a new configuration.
1520 """, width=600).pack(side=TOP)
1521 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1523 Message(self, text="""
1524 Use `Run fetchmail' to run fetchmail in foreground.
1525 Progress messages will be shown, but not debug messages.
1526 """, width=600).pack(side=TOP)
1527 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1529 Message(self, text="""
1530 Or you can just select `Quit' to exit the launcher now.
1531 """, width=600).pack(side=TOP)
1532 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1534 def configure(self):
1535 self.configbutton.configure(state=DISABLED)
1536 Configurator(self.outfile, Toplevel(),
1537 lambda self=self: self.configbutton.configure(state=NORMAL),
1541 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1544 RunWindow("fetchmail -d0", Toplevel(), self)
1549 # Functions for turning a dictionary into an instantiated object tree.
1551 def intersect(list1, list2):
1552 # Compute set intersection of lists
1559 def setdiff(list1, list2):
1560 # Compute set difference of lists
1567 def copy_instance(toclass, fromdict):
1568 # Initialize a class object of given type from a conformant dictionary.
1569 for fld in fromdict.keys():
1570 if not fld in dictmembers:
1571 dictmembers.append(fld)
1572 # The `optional' fields are the ones we can ignore for purposes of
1573 # conformability checking; they'll still get copied if they are
1574 # present in the dictionary.
1575 optional = ('interface', 'monitor', 'netsec')
1576 class_sig = setdiff(toclass.__dict__.keys(), optional)
1578 dict_keys = setdiff(fromdict.keys(), optional)
1580 common = intersect(class_sig, dict_keys)
1581 if 'typemap' in class_sig:
1582 class_sig.remove('typemap')
1583 if tuple(class_sig) != tuple(dict_keys):
1584 print "Fields don't match what fetchmailconf expected:"
1585 # print "Class signature: " + `class_sig`
1586 # print "Dictionary keys: " + `dict_keys`
1587 diff = setdiff(class_sig, common)
1589 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1590 diff = setdiff(dict_keys, common)
1592 print "Not matched in dictionary keys: " + `diff`
1595 for x in fromdict.keys():
1596 setattr(toclass, x, fromdict[x])
1599 # And this is the main sequence. How it works:
1601 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1602 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1603 # Run execfile on the file to pull fetchmailrc into Python global space.
1604 # You don't want static data, though; you want, instead, a tree of objects
1605 # with the same data members and added appropriate methods.
1607 # This is what the copy_instance function() is for. It tries to copy a
1608 # dictionary field by field into a class, aborting if the class and dictionary
1609 # have different data members (except for any typemap member in the class;
1610 # that one is strictly for use by the MyWidget supperclass).
1612 # Once the object tree is set up, require user to choose novice or expert
1613 # mode and instantiate an edit object for the configuration. Class methods
1614 # will take it all from there.
1616 # Options (not documented because they're for fetchmailconf debuggers only):
1617 # -d: Read the configuration and dump it to stdout before editing. Dump
1618 # the edited result to stdout as well.
1619 # -f: specify the run control file to read.
1621 if __name__ == '__main__':
1624 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1625 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1626 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1627 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1628 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1629 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1630 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1631 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1632 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1633 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1634 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1635 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1636 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1637 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1638 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1639 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1640 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1641 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1642 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1643 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1644 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1645 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1646 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1647 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1648 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1649 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1650 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1651 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1652 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1653 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1654 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1655 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1657 # Note on making icons: the above was generated by the following procedure:
1660 # data = open("fetchmail.gif", "rb").read()
1661 # print "fetchmail_gif =\\"
1662 # print repr(base64.encodestring(data))
1666 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1667 dump = rcfile = None;
1668 for (switch, val) in options:
1669 if (switch == '-d'):
1671 elif (switch == '-f'):
1674 # Get client host's FQDN
1675 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1678 ConfigurationDefaults = Configuration()
1679 ServerDefaults = Server()
1680 UserDefaults = User()
1682 # Read the existing configuration
1683 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1685 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1687 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1692 print "`" + cmd + "' run failure, status " + `s`
1695 print "Unknown error while running fetchmail --configdump"
1702 print "Can't read configuration output of fetchmail --configdump."
1708 # The tricky part -- initializing objects from the configuration global
1709 # `Configuration' is the top level of the object tree we're going to mung.
1710 # The dictmembers list is used to track the set of fields the dictionary
1711 # contains; in particular, we can use it to tell whether things like the
1712 # monitor, interface, and netsec fields are present.
1714 Fetchmailrc = Configuration()
1715 copy_instance(Fetchmailrc, fetchmailrc)
1716 Fetchmailrc.servers = [];
1717 for server in fetchmailrc['servers']:
1719 copy_instance(Newsite, server)
1720 Fetchmailrc.servers.append(Newsite)
1722 for user in server['users']:
1724 copy_instance(Newuser, user)
1725 Newsite.users.append(Newuser)
1727 # We may want to display the configuration and quit
1729 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1731 # The theory here is that -f alone sets the rcfile location,
1732 # but -d and -f together mean the new configuration should go to stdout.
1733 if not rcfile and not dump:
1734 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1736 # OK, now run the configuration edit
1737 root = MainWindow(rcfile)
1740 # The following sets edit modes for GNU EMACS