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, "GroupWise") > 0:
1129 warnings = warnings + """
1130 The Novell GroupWise IMAP server would be better named GroupFoolish;
1131 it is (according to the designer of IMAP) unusably broken. Among
1132 other things, it doesn't include a required content length in its
1133 BODY[TEXT] response.<p>
1135 Fetchmail works around this problem, but we strongly recommend voting
1136 with your dollars for a server that isn't brain-dead. If you stick
1137 with code as shoddy as GroupWise seems to be, you will probably pay
1138 for it with other problems.<p>
1142 if string.find(greetline, "sprynet.com") > 0:
1143 warnings = warnings + """
1144 You appear to be using a SpryNet server. In mid-1999 it was reported that
1145 the SpryNet TOP command marks messages seen. Therefore, for proper error
1146 recovery in the event of a line drop, it is strongly recommended that you
1147 turn on `fetchall' on all user entries associated with this server.
1151 # Steve VanDevender <stevev@efn.org> writes:
1152 # The only system I have seen this happen with is cucipop-1.31
1153 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1154 # 2.x and probably quite a few other systems. It appears to be a
1155 # bug or bad interaction with the SunOS realloc() -- it turns out
1156 # that internally cucipop does allocate a certain data structure in
1157 # multiples of 16, using realloc() to bump it up to the next
1158 # multiple if it needs more.
1160 # The distinctive symptom is that when there are 16 messages in the
1161 # inbox, you can RETR and DELE all 16 messages successfully, but on
1162 # QUIT cucipop returns something like "-ERR Error locking your
1163 # mailbox" and aborts without updating it.
1165 # The cucipop banner looks like:
1167 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1169 if string.find(greetline, "Cubic Circle") > 0:
1170 warnings = warnings + """
1171 I see your server is running cucipop. Better make sure the server box
1172 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1173 under that version, and doesn't cope with the result gracefully. Newer
1174 SunOS and Solaris machines run cucipop OK.
1177 if string.find(greetline, "QPOP") > 0:
1178 warnings = warnings + """
1179 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1180 knows all about qpopper. However, be aware that the 2.53 version of
1181 qpopper does something odd that causes fetchmail to hang with a socket
1182 error on very large messages. This is probably not a fetchmail bug, as
1183 it has been observed with fetchpop. The fix is to upgrade to qpopper
1184 3.0beta or a more recent version. Better yet, switch to IMAP.
1187 closebrak = string.find(greetline, ">")
1188 if closebrak > 0 and greetline[closebrak+1] == "\r":
1189 warnings = warnings + """
1190 It looks like you could use APOP on this server and avoid sending it your
1191 password in clear. You should talk to the mailserver administrator about
1195 if string.find(greetline, "IMAP2bis") > 0:
1196 warnings = warnings + """
1197 IMAP2bis servers have a minor problem; they can't peek at messages without
1198 marking them seen. If you take a line hit during the retrieval, the
1199 interrupted message may get left on the server, marked seen.
1201 To work around this, it is recommended that you set the `fetchall'
1202 option on all user entries associated with this server, so any stuck
1203 mail will be retrieved next time around.
1206 if string.find(greetline, "POP3 Server Ready") > 0:
1207 warnings = warnings + """
1208 Some server that uses this greeting line has been observed to choke on
1209 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1211 if string.find(greetline, "IMAP4rev1") > 0:
1212 warnings = warnings + """
1213 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1214 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1215 has therefore been extremely well tested with this class of server.
1218 warnings = warnings + """
1219 Fetchmail doesn't know anything special about this server type.
1222 # Display success window with warnings
1223 title = "Autoprobe of " + realhost + " succeeded"
1224 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1225 self.protocol.set(protocol)
1226 confwin.title(title)
1227 confwin.iconname(title)
1228 Label(confwin, text=title).pack()
1229 Message(confwin, text=confirm, width=600).pack()
1230 Button(confwin, text='Done',
1231 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1234 # User editing stuff
1238 'title' : 'User option help',
1239 'banner': 'User options',
1241 You may use this panel to set options
1242 that may differ between individual
1245 Once you have a user configuration set
1246 up as you like it, you can select `OK' to
1247 store it in the user list maintained in
1248 the site configuration window.
1250 If you wish to discard the changes you have
1251 made to user options, select `Quit'.
1255 'title' : 'Local name help',
1256 'banner': 'Local names',
1258 The local name(s) in a user entry are the
1259 people on the client machine who should
1260 receive mail from the poll described.
1262 Note: if a user entry has more than one
1263 local name, messages will be retrieved
1264 in multidrop mode. This complicates
1265 the configuration issues; see the manual
1266 page section on multidrop mode.
1269 class UserEdit(Frame, MyWidget):
1270 def __init__(self, username, parent):
1271 self.parent = parent
1273 for user in parent.server.users:
1274 if user.remote == username:
1276 if self.user == None:
1278 self.user.remote = username
1279 self.user.localnames = [username]
1280 parent.server.users.append(self.user)
1282 def edit(self, mode, master=None):
1283 Frame.__init__(self, master)
1285 self.master.title('Fetchmail user ' + self.user.remote
1286 + ' querying ' + self.parent.server.pollname);
1287 self.master.iconname('Fetchmail user ' + self.user.remote);
1288 self.post(User, 'user')
1289 self.makeWidgets(mode, self.parent.server.pollname)
1290 self.keepalive = [] # Use this to anchor the PhotoImage object
1291 make_icon_window(self, fetchmail_gif)
1294 # self.wait_window()
1298 del self.parent.subwidgets[self.user.remote]
1299 Widget.destroy(self.master)
1302 if ConfirmQuit(self, 'user option editing'):
1306 self.fetch(User, 'user')
1309 def makeWidgets(self, mode, servername):
1310 dispose_window(self,
1311 "User options for " + self.user.remote + " querying " + servername,
1314 if mode != 'novice':
1315 leftwin = Frame(self);
1319 secwin = Frame(leftwin, relief=RAISED, bd=5)
1320 Label(secwin, text="Authentication").pack(side=TOP)
1321 LabeledEntry(secwin, 'Password:',
1322 self.password, '12').pack(side=TOP, fill=X)
1323 secwin.pack(fill=X, anchor=N)
1325 names = Frame(leftwin, relief=RAISED, bd=5)
1326 Label(names, text="Local names").pack(side=TOP)
1327 ListEdit("New name: ",
1328 self.user.localnames, None, None, names, localhelp)
1329 names.pack(fill=X, anchor=N)
1331 if mode != 'novice':
1332 targwin = Frame(leftwin, relief=RAISED, bd=5)
1333 Label(targwin, text="Forwarding Options").pack(side=TOP)
1334 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1335 ListEdit("New listener:",
1336 self.user.smtphunt, None, None, targwin, None)
1337 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1338 self.smtpaddress, '26').pack(side=TOP, fill=X)
1339 LabeledEntry(targwin, 'Connection setup command:',
1340 self.preconnect, '26').pack(side=TOP, fill=X)
1341 LabeledEntry(targwin, 'Connection wrapup command:',
1342 self.postconnect, '26').pack(side=TOP, fill=X)
1343 LabeledEntry(targwin, 'Local delivery agent:',
1344 self.mda, '26').pack(side=TOP, fill=X)
1345 LabeledEntry(targwin, 'BSMTP output file:',
1346 self.bsmtp, '26').pack(side=TOP, fill=X)
1347 LabeledEntry(targwin, 'Listener spam-block codes:',
1348 self.antispam, '26').pack(side=TOP, fill=X)
1349 LabeledEntry(targwin, 'Pass-through properties:',
1350 self.properties, '26').pack(side=TOP, fill=X)
1351 Checkbutton(targwin, text="Use LMTP?",
1352 variable=self.lmtp).pack(side=TOP, fill=X)
1353 targwin.pack(fill=X, anchor=N)
1355 if mode != 'novice':
1356 leftwin.pack(side=LEFT, fill=X, anchor=N)
1357 rightwin = Frame(self)
1361 optwin = Frame(rightwin, relief=RAISED, bd=5)
1362 Label(optwin, text="Processing Options").pack(side=TOP)
1363 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1364 variable=self.keep).pack(side=TOP, anchor=W)
1365 Checkbutton(optwin, text="Fetch old messages as well as new",
1366 variable=self.fetchall).pack(side=TOP, anchor=W)
1367 if mode != 'novice':
1368 Checkbutton(optwin, text="Flush seen messages before retrieval",
1369 variable=self.flush).pack(side=TOP, anchor=W)
1370 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1371 variable=self.rewrite).pack(side=TOP, anchor=W)
1372 Checkbutton(optwin, text="Force CR/LF at end of each line",
1373 variable=self.forcecr).pack(side=TOP, anchor=W)
1374 Checkbutton(optwin, text="Strip CR from end of each line",
1375 variable=self.stripcr).pack(side=TOP, anchor=W)
1376 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1377 variable=self.pass8bits).pack(side=TOP, anchor=W)
1378 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1379 variable=self.mimedecode).pack(side=TOP, anchor=W)
1380 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1381 variable=self.dropstatus).pack(side=TOP, anchor=W)
1384 if mode != 'novice':
1385 limwin = Frame(rightwin, relief=RAISED, bd=5)
1386 Label(limwin, text="Resource Limits").pack(side=TOP)
1387 LabeledEntry(limwin, 'Message size limit:',
1388 self.limit, '30').pack(side=TOP, fill=X)
1389 LabeledEntry(limwin, 'Size warning interval:',
1390 self.warnings, '30').pack(side=TOP, fill=X)
1391 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1392 self.fetchlimit, '30').pack(side=TOP, fill=X)
1393 LabeledEntry(limwin, 'Max messages to forward per poll:',
1394 self.batchlimit, '30').pack(side=TOP, fill=X)
1395 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1396 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1397 self.expunge, '30').pack(side=TOP, fill=X)
1400 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1401 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1402 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1403 ListEdit("New folder:", self.user.mailboxes,
1404 None, None, foldwin, None)
1405 foldwin.pack(fill=X, anchor=N)
1407 if mode != 'novice':
1408 rightwin.pack(side=LEFT)
1414 # Top-level window that offers either novice or expert mode
1415 # (but not both at once; it disappears when one is selected).
1418 class Configurator(Frame):
1419 def __init__(self, outfile, master, onexit, parent):
1420 Frame.__init__(self, master)
1421 self.outfile = outfile
1422 self.onexit = onexit
1423 self.parent = parent
1424 self.master.title('fetchmail configurator');
1425 self.master.iconname('fetchmail configurator');
1427 self.keepalive = [] # Use this to anchor the PhotoImage object
1428 make_icon_window(self, fetchmail_gif)
1430 Message(self, text="""
1431 Use `Novice Configuration' for basic fetchmail setup;
1432 with this, you can easily set up a single-drop connection
1433 to one remote mail server.
1434 """, width=600).pack(side=TOP)
1435 Button(self, text='Novice Configuration',
1436 fg='blue', command=self.novice).pack()
1438 Message(self, text="""
1439 Use `Expert Configuration' for advanced fetchmail setup,
1440 including multiple-site or multidrop connections.
1441 """, width=600).pack(side=TOP)
1442 Button(self, text='Expert Configuration',
1443 fg='blue', command=self.expert).pack()
1445 Message(self, text="""
1446 Or you can just select `Quit' to leave the configurator now and
1447 return to the main panel.
1448 """, width=600).pack(side=TOP)
1449 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1450 master.protocol("WM_DELETE_WINDOW", self.leave)
1453 self.master.destroy()
1454 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1457 self.master.destroy()
1458 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1461 self.master.destroy()
1464 # Run a command an a scrolling text widget, displaying its output
1466 class RunWindow(Frame):
1467 def __init__(self, command, master, parent):
1468 Frame.__init__(self, master)
1469 self.master = master
1470 self.master.title('fetchmail run window');
1471 self.master.iconname('fetchmail run window');
1474 text="Running "+command,
1475 bd=2).pack(side=TOP, pady=10)
1476 self.keepalive = [] # Use this to anchor the PhotoImage object
1477 make_icon_window(self, fetchmail_gif)
1479 # This is a scrolling text window
1480 textframe = Frame(self)
1481 scroll = Scrollbar(textframe)
1482 self.textwidget = Text(textframe, setgrid=TRUE)
1483 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1484 self.textwidget.config(yscrollcommand=scroll.set)
1485 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1486 scroll.config(command=self.textwidget.yview)
1487 scroll.pack(side=RIGHT, fill=BOTH)
1488 textframe.pack(side=TOP)
1490 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1492 self.update() # Draw widget before executing fetchmail
1494 child_stdout = os.popen(command + " 2>&1", "r")
1496 ch = child_stdout.read(1)
1499 self.textwidget.insert(END, ch)
1500 self.textwidget.insert(END, "Done.")
1501 self.textwidget.see(END);
1504 Widget.destroy(self.master)
1506 # Here's where we choose either configuration or launching
1508 class MainWindow(Frame):
1509 def __init__(self, outfile, master=None):
1510 Frame.__init__(self, master)
1511 self.outfile = outfile
1512 self.master.title('fetchmail launcher');
1513 self.master.iconname('fetchmail launcher');
1516 text='Fetchmailconf ' + version,
1517 bd=2).pack(side=TOP, pady=10)
1518 self.keepalive = [] # Use this to anchor the PhotoImage object
1519 make_icon_window(self, fetchmail_gif)
1522 Message(self, text="""
1523 Use `Configure fetchmail' to tell fetchmail about the remote
1524 servers it should poll (the host name, your username there,
1525 whether to use POP or IMAP, and so forth).
1526 """, width=600).pack(side=TOP)
1527 self.configbutton = Button(self, text='Configure fetchmail',
1528 fg='blue', command=self.configure)
1529 self.configbutton.pack()
1531 Message(self, text="""
1532 Use `Test fetchmail' to run fetchmail with debugging enabled.
1533 This is a good way to test out a new configuration.
1534 """, width=600).pack(side=TOP)
1535 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1537 Message(self, text="""
1538 Use `Run fetchmail' to run fetchmail in foreground.
1539 Progress messages will be shown, but not debug messages.
1540 """, width=600).pack(side=TOP)
1541 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1543 Message(self, text="""
1544 Or you can just select `Quit' to exit the launcher now.
1545 """, width=600).pack(side=TOP)
1546 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1548 def configure(self):
1549 self.configbutton.configure(state=DISABLED)
1550 Configurator(self.outfile, Toplevel(),
1551 lambda self=self: self.configbutton.configure(state=NORMAL),
1555 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1558 RunWindow("fetchmail -d0", Toplevel(), self)
1563 # Functions for turning a dictionary into an instantiated object tree.
1565 def intersect(list1, list2):
1566 # Compute set intersection of lists
1573 def setdiff(list1, list2):
1574 # Compute set difference of lists
1581 def copy_instance(toclass, fromdict):
1582 # Initialize a class object of given type from a conformant dictionary.
1583 for fld in fromdict.keys():
1584 if not fld in dictmembers:
1585 dictmembers.append(fld)
1586 # The `optional' fields are the ones we can ignore for purposes of
1587 # conformability checking; they'll still get copied if they are
1588 # present in the dictionary.
1589 optional = ('interface', 'monitor', 'netsec')
1590 class_sig = setdiff(toclass.__dict__.keys(), optional)
1592 dict_keys = setdiff(fromdict.keys(), optional)
1594 common = intersect(class_sig, dict_keys)
1595 if 'typemap' in class_sig:
1596 class_sig.remove('typemap')
1597 if tuple(class_sig) != tuple(dict_keys):
1598 print "Fields don't match what fetchmailconf expected:"
1599 # print "Class signature: " + `class_sig`
1600 # print "Dictionary keys: " + `dict_keys`
1601 diff = setdiff(class_sig, common)
1603 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1604 diff = setdiff(dict_keys, common)
1606 print "Not matched in dictionary keys: " + `diff`
1609 for x in fromdict.keys():
1610 setattr(toclass, x, fromdict[x])
1613 # And this is the main sequence. How it works:
1615 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1616 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1617 # Run execfile on the file to pull fetchmailrc into Python global space.
1618 # You don't want static data, though; you want, instead, a tree of objects
1619 # with the same data members and added appropriate methods.
1621 # This is what the copy_instance function() is for. It tries to copy a
1622 # dictionary field by field into a class, aborting if the class and dictionary
1623 # have different data members (except for any typemap member in the class;
1624 # that one is strictly for use by the MyWidget supperclass).
1626 # Once the object tree is set up, require user to choose novice or expert
1627 # mode and instantiate an edit object for the configuration. Class methods
1628 # will take it all from there.
1630 # Options (not documented because they're for fetchmailconf debuggers only):
1631 # -d: Read the configuration and dump it to stdout before editing. Dump
1632 # the edited result to stdout as well.
1633 # -f: specify the run control file to read.
1635 if __name__ == '__main__':
1638 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1639 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1640 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1641 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1642 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1643 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1644 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1645 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1646 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1647 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1648 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1649 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1650 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1651 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1652 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1653 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1654 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1655 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1656 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1657 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1658 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1659 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1660 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1661 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1662 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1663 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1664 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1665 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1666 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1667 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1668 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1669 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1671 # Note on making icons: the above was generated by the following procedure:
1674 # data = open("fetchmail.gif", "rb").read()
1675 # print "fetchmail_gif =\\"
1676 # print repr(base64.encodestring(data))
1680 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1681 dump = rcfile = None;
1682 for (switch, val) in options:
1683 if (switch == '-d'):
1685 elif (switch == '-f'):
1688 # Get client host's FQDN
1689 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1692 ConfigurationDefaults = Configuration()
1693 ServerDefaults = Server()
1694 UserDefaults = User()
1696 # Read the existing configuration
1697 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1699 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1701 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1706 print "`" + cmd + "' run failure, status " + `s`
1709 print "Unknown error while running fetchmail --configdump"
1716 print "Can't read configuration output of fetchmail --configdump."
1722 # The tricky part -- initializing objects from the configuration global
1723 # `Configuration' is the top level of the object tree we're going to mung.
1724 # The dictmembers list is used to track the set of fields the dictionary
1725 # contains; in particular, we can use it to tell whether things like the
1726 # monitor, interface, and netsec fields are present.
1728 Fetchmailrc = Configuration()
1729 copy_instance(Fetchmailrc, fetchmailrc)
1730 Fetchmailrc.servers = [];
1731 for server in fetchmailrc['servers']:
1733 copy_instance(Newsite, server)
1734 Fetchmailrc.servers.append(Newsite)
1736 for user in server['users']:
1738 copy_instance(Newuser, user)
1739 Newsite.users.append(Newuser)
1741 # We may want to display the configuration and quit
1743 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1745 # The theory here is that -f alone sets the rcfile location,
1746 # but -d and -f together mean the new configuration should go to stdout.
1747 if not rcfile and not dump:
1748 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1750 # OK, now run the configuration edit
1751 root = MainWindow(rcfile)
1754 # The following sets edit modes for GNU EMACS