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 # Vounce errors to users
23 self.properties = None # No exiguous properties
24 self.invisible = FALSE # Suppress Received line & spoof?
25 self.syslog = FALSE # Use syslogd for logging?
26 self.servers = [] # List of included sites
27 Configuration.typemap = (
28 ('poll_interval', 'Int'),
29 ('logfile', 'String'),
31 ('postmaster', 'String'),
32 ('bouncemail', 'Boolean'),
33 ('properties', 'String'),
34 ('syslog', 'Boolean'),
35 ('invisible', 'Boolean'))
39 if self.syslog != ConfigurationDefaults.syslog:
40 str = str + ("set syslog\n")
42 str = str + ("set logfile \"%s\"\n" % (self.logfile,));
43 if self.idfile != ConfigurationDefaults.idfile:
44 str = str + ("set idfile \"%s\"\n" % (self.idfile,));
45 if self.postmaster != ConfigurationDefaults.postmaster:
46 str = str + ("set postmaster \"%s\"\n" % (self.postmaster,));
48 str = str + ("set bouncemail\n")
50 str = str + ("set nobouncemail\n")
51 if self.properties != ConfigurationDefaults.properties:
52 str = str + ("set properties \"%s\"\n" % (self.properties,));
53 if self.poll_interval > 0:
54 str = str + "set daemon " + `self.poll_interval` + "\n"
55 for site in self.servers:
56 str = str + repr(site)
59 def __delitem__(self, name):
60 for si in range(len(self.servers)):
61 if self.servers[si].pollname == name:
66 return "[Configuration: " + repr(self) + "]"
70 self.pollname = None # Poll label
71 self.via = None # True name of host
72 self.active = TRUE # Poll status
73 self.interval = 0 # Skip interval
74 self.protocol = 'auto' # Default to auto protocol
75 self.port = 0 # Port number to use
76 self.uidl = FALSE # Don't use RFC1725 UIDLs by default
77 self.auth = 'password' # Default to password authentication
78 self.timeout = 300 # 5-minute timeout
79 self.envelope = 'Received' # Envelope-address header
80 self.envskip = 0 # Number of envelope headers to skip
81 self.qvirtual = None # Name prefix to strip
82 self.aka = [] # List of DNS aka names
83 self.dns = TRUE # Enable DNS lookup on multidrop
84 self.localdomains = [] # Domains to be considered local
85 self.interface = None # IP address and range
86 self.monitor = None # IP address and range
87 self.plugin = None # Plugin command for going to server
88 self.plugout = None # Plugin command for going to listener
89 self.netsec = None # IPV6 security options
90 self.users = [] # List of user entries for site
92 ('pollname', 'String'),
94 ('active', 'Boolean'),
96 ('protocol', 'String'),
101 ('envelope', 'String'),
103 ('qvirtual', 'String'),
106 # leave localdomains out
107 ('interface', 'String'),
108 ('monitor', 'String'),
109 ('plugin', 'String'),
110 ('plugout', 'String'),
111 ('netsec', 'String'))
113 def dump(self, folded):
115 if self.active: str = str + "poll"
116 else: str = str + "skip"
117 str = str + (" " + self.pollname)
119 str = str + (" via \"%s\"\n" % (self.via,));
120 if self.protocol != ServerDefaults.protocol:
121 str = str + " with proto " + self.protocol
122 if self.port != defaultports[self.protocol] and self.port != 0:
123 str = str + " port " + `self.port`
124 if self.timeout != ServerDefaults.timeout:
125 str = str + " timeout " + `self.timeout`
126 if self.interval != ServerDefaults.interval:
127 str = str + " interval " + `self.interval`
128 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
130 str = str + " envelope " + self.envskip + " " + self.envelope
132 str = str + " envelope " + self.envelope
134 str = str + (" qvirtual \"%s\"\n" % (self.qvirtual,));
135 if self.auth != ServerDefaults.auth:
136 str = str + " auth " + self.auth
137 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
138 str = str + " and options"
139 if self.dns != ServerDefaults.dns:
140 str = str + flag2str(self.dns, 'dns')
141 if self.uidl != ServerDefaults.uidl:
142 str = str + flag2str(self.uidl, 'uidl')
143 if folded: str = str + "\n "
144 else: str = str + " "
150 if self.aka and self.localdomains: str = str + " "
151 if self.localdomains:
152 str = str + ("localdomains")
153 for x in self.localdomains:
155 if (self.aka or self.localdomains):
162 str = str + "interface \"" + self.interface + "\""
164 str = str + "monitor \"" + self.monitor + "\""
166 str = str + "netsec \"" + self.netsec + "\""
167 if self.interface or self.monitor or self.netsec:
171 if str[-1] == " ": str = str[0:-1]
173 for user in self.users:
174 str = str + repr(user)
178 def __delitem__(self, name):
179 for ui in range(len(self.users)):
180 if self.users[ui].remote == name:
185 return self.dump(TRUE)
188 return "[Server: " + self.dump(FALSE) + "]"
192 if os.environ.has_key("USER"):
193 self.remote = os.environ["USER"] # Remote username
194 elif os.environ.has_key("LOGNAME"):
195 self.remote = os.environ["LOGNAME"]
197 print "Can't get your username!"
199 self.remote = os.environ["USER"]# Remote username
200 self.localnames = [self.remote,]# Local names
201 self.password = None # Password for mail account access
202 self.mailboxes = [] # Remote folders to retrieve from
203 self.smtphunt = [] # Hosts to forward to
204 self.smtpaddress = None # Append this to MAIL FROM line
205 self.preconnect = None # Connection setup
206 self.postconnect = None # Connection wrapup
207 self.mda = None # Mail Delivery Agent
208 self.bsmtp = None # BSMTP output file
209 self.lmtp = FALSE # Use LMTP rather than SMTP?
210 self.antispam = "571 550 501" # Listener's spam-block code
211 self.keep = FALSE # Keep messages
212 self.flush = FALSE # Flush messages
213 self.fetchall = FALSE # Fetch old messages
214 self.rewrite = TRUE # Rewrite message headers
215 self.forcecr = FALSE # Force LF -> CR/LF
216 self.stripcr = FALSE # Strip CR
217 self.pass8bits = FALSE # Force BODY=7BIT
218 self.mimedecode = TRUE # Undo MIME armoring
219 self.dropstatus = FALSE # Drop incoming Status lines
220 self.limit = 0 # Message size limit
221 self.warnings = 0 # Size warning interval
222 self.fetchlimit = 0 # Max messages fetched per batch
223 self.batchlimit = 0 # Max message forwarded per batch
224 self.expunge = 1 # Interval between expunges (IMAP)
225 self.properties = None # Extension properties
227 ('remote', 'String'),
228 # leave out mailboxes and localnames
229 ('password', 'String'),
231 ('smtpaddress', 'String'),
232 ('preconnect', 'String'),
233 ('postconnect', 'String'),
237 ('antispam', 'String'),
239 ('flush', 'Boolean'),
240 ('fetchall', 'Boolean'),
241 ('rewrite', 'Boolean'),
242 ('forcecr', 'Boolean'),
243 ('stripcr', 'Boolean'),
244 ('pass8bits', 'Boolean'),
245 ('mimedecode', 'Boolean'),
246 ('dropstatus', 'Boolean'),
249 ('fetchlimit', 'Int'),
250 ('batchlimit', 'Int'),
252 ('properties', 'String'))
256 str = str + "user \"" + self.remote + "\" there ";
258 str = str + "with password \"" + self.password + '" '
261 for x in self.localnames:
264 if (self.keep != UserDefaults.keep
265 or self.flush != UserDefaults.flush
266 or self.fetchall != UserDefaults.fetchall
267 or self.rewrite != UserDefaults.rewrite
268 or self.forcecr != UserDefaults.forcecr
269 or self.stripcr != UserDefaults.stripcr
270 or self.pass8bits != UserDefaults.pass8bits
271 or self.mimedecode != UserDefaults.mimedecode
272 or self.dropstatus != UserDefaults.dropstatus):
273 str = str + " options"
274 if self.keep != UserDefaults.keep:
275 str = str + flag2str(self.keep, 'keep')
276 if self.flush != UserDefaults.flush:
277 str = str + flag2str(self.flush, 'flush')
278 if self.fetchall != UserDefaults.fetchall:
279 str = str + flag2str(self.fetchall, 'fetchall')
280 if self.rewrite != UserDefaults.rewrite:
281 str = str + flag2str(self.rewrite, 'rewrite')
282 if self.forcecr != UserDefaults.forcecr:
283 str = str + flag2str(self.forcecr, 'forcecr')
284 if self.stripcr != UserDefaults.stripcr:
285 str = str + flag2str(self.stripcr, 'stripcr')
286 if self.pass8bits != UserDefaults.pass8bits:
287 str = str + flag2str(self.pass8bits, 'pass8bits')
288 if self.mimedecode != UserDefaults.mimedecode:
289 str = str + flag2str(self.mimedecode, 'mimedecode')
290 if self.dropstatus != UserDefaults.dropstatus:
291 str = str + flag2str(self.dropstatus, 'dropstatus')
292 if self.limit != UserDefaults.limit:
293 str = str + " limit " + `self.limit`
294 if self.warnings != UserDefaults.warnings:
295 str = str + " warnings " + `self.warnings`
296 if self.fetchlimit != UserDefaults.fetchlimit:
297 str = str + " fetchlimit " + `self.fetchlimit`
298 if self.batchlimit != UserDefaults.batchlimit:
299 str = str + " batchlimit " + `self.batchlimit`
300 if self.expunge != UserDefaults.expunge:
301 str = str + " expunge " + `self.expunge`
303 trimmed = self.smtphunt;
304 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
305 trimmed = trimmed[0:len(trimmed) - 1]
306 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
307 trimmed = trimmed[0:len(trimmed) - 1]
309 str = str + " smtphost "
314 str = str + " folder"
315 for x in self.mailboxes:
318 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
319 if getattr(self, fld):
320 str = str + " %s %s\n" % (fld, `getattr(self, fld)`)
321 if self.lmtp != UserDefaults.lmtp:
322 str = str + flag2str(self.lmtp, 'lmtp')
323 if self.antispam != UserDefaults.antispam:
324 str = str + " antispam " + self.antispam + "\n"
328 return "[User: " + repr(self) + "]"
334 defaultports = {"auto":0,
344 authlist = ("password", "kerberos")
347 'title' : 'List Selection Help',
348 'banner': 'List Selection',
350 You must select an item in the list box (by clicking on it).
353 def flag2str(value, string):
354 # make a string representation of a .fetchmailrc flag or negated flag
358 if value == FALSE: str = str + ("no ")
362 class LabeledEntry(Frame):
363 # widget consisting of entry field with caption to left
364 def bind(self, key, action):
365 self.E.bind(key, action)
368 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
369 Frame.__init__(self, Master)
370 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
371 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
372 self.L.pack({'side':'left'})
373 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
375 def ButtonBar(frame, legend, ref, alternatives, depth, command):
376 # array of radio buttons, caption to left, picking from a string list
378 width = len(alternatives) / depth;
379 Label(bar, text=legend).pack(side=LEFT)
380 for column in range(width):
381 subframe = Frame(bar)
382 for row in range(depth):
383 ind = width * row + column
384 Radiobutton(subframe,
385 {'text':alternatives[ind],
387 'value':alternatives[ind],
388 'command':command}).pack(side=TOP, anchor=W)
389 subframe.pack(side=LEFT)
393 def helpwin(helpdict):
394 # help message window with a self-destruct button
396 helpwin.title(helpdict['title'])
397 helpwin.iconname(helpdict['title'])
398 Label(helpwin, text=helpdict['banner']).pack()
399 textwin = Message(helpwin, text=helpdict['text'], width=600)
401 Button(helpwin, text='Done',
402 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
404 def make_icon_window(base, image):
406 # Some older pythons will error out on this
407 icon_image = PhotoImage(data=image)
408 icon_window = Toplevel()
409 Label(icon_window, image=icon_image, bg='black').pack()
410 base.master.iconwindow(icon_window)
411 # Avoid TkInter brain death. PhotoImage objects go out of
412 # scope when the enclosing function returns. Therefore
413 # we have to explicitly link them to something.
414 base.keepalive.append(icon_image)
418 class ListEdit(Frame):
419 # edit a list of values (duplicates not allowed) with a supplied editor hook
420 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
422 self.deletor = deletor
425 # Set up a widget to accept new elements
426 self.newval = StringVar(master)
427 newwin = LabeledEntry(master, newlegend, self.newval, '12')
428 newwin.bind('<Double-1>', self.handleNew)
429 newwin.bind('<Return>', self.handleNew)
430 newwin.pack(side=TOP, fill=X, anchor=E)
432 # Edit the existing list
433 listframe = Frame(master)
434 scroll = Scrollbar(listframe)
435 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
438 self.listwidget.insert(END, x)
439 listframe.pack(side=TOP, expand=YES, fill=BOTH)
440 self.listwidget.config(yscrollcommand=scroll.set)
441 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
442 scroll.config(command=self.listwidget.yview)
443 scroll.pack(side=RIGHT, fill=BOTH)
444 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
445 self.listwidget.bind('<Double-1>', self.handleList);
446 self.listwidget.bind('<Return>', self.handleList);
450 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
451 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
453 self.helptxt = helptxt
454 Button(bf, text='Help', fg='blue',
455 command=self.help).pack(side=RIGHT)
459 helpwin(self.helptxt)
461 def handleList(self, event):
464 def handleNew(self, event):
465 item = self.newval.get()
466 entire = self.listwidget.get(0, self.listwidget.index('end'));
467 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
468 self.listwidget.insert('end', item)
469 if self.list != None: self.list.append(item)
473 select = self.listwidget.curselection()
478 if index and self.editor:
479 label = self.listwidget.get(index);
480 apply(self.editor, (label,))
482 def deleteItem(self):
483 select = self.listwidget.curselection()
487 index = string.atoi(select[0])
488 label = self.listwidget.get(index);
489 self.listwidget.delete(index)
490 if self.list != None:
492 if self.deletor != None:
493 apply(self.deletor, (label,))
495 def ConfirmQuit(frame, context):
498 text = 'Really quit ' + context + ' without saving?',
500 strings = ('Yes', 'No'),
504 def dispose_window(master, legend, help, savelegend='OK'):
505 dispose = Frame(master, relief=RAISED, bd=5)
506 Label(dispose, text=legend).pack(side=TOP,pady=10)
507 Button(dispose, text=savelegend, fg='blue',
508 command=master.save).pack(side=LEFT)
509 Button(dispose, text='Quit', fg='blue',
510 command=master.nosave).pack(side=LEFT)
511 Button(dispose, text='Help', fg='blue',
512 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
517 # Common methods for Tkinter widgets -- deals with Tkinter declaration
518 def post(self, widgetclass, field):
519 for x in widgetclass.typemap:
520 if x[1] == 'Boolean':
521 setattr(self, x[0], BooleanVar(self))
522 elif x[1] == 'String':
523 setattr(self, x[0], StringVar(self))
525 setattr(self, x[0], IntVar(self))
526 source = getattr(getattr(self, field), x[0])
528 getattr(self, x[0]).set(source)
530 def fetch(self, widgetclass, field):
531 for x in widgetclass.typemap:
532 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
535 # First, code to set the global fetchmail run controls.
538 configure_novice_help = {
539 'title' : 'Fetchmail novice configurator help',
540 'banner': 'Novice configurator help',
542 In the `Novice Configurator Controls' panel, you can:
544 Press `Save' to save the new fetchmail configuration you have created.
545 Press `Quit' to exit without saving.
546 Press `Help' to bring up this help message.
548 In the `Novice Configuration' panels, you will set up the basic data
549 needed to create a simple fetchmail setup. These include:
551 1. The name of the remote site you want to query.
553 2. Your login name on that site.
555 3. Your password on that site.
557 4. A protocol to use (POP, IMAP, ETRN, etc.)
559 5. A polling interval.
561 6. Options to fetch old messages as well as new, uor to suppress
562 deletion of fetched message.
564 The novice-configuration code will assume that you want to forward mail
565 to a local sendmail listener with no special options.
568 configure_expert_help = {
569 'title' : 'Fetchmail expert configurator help',
570 'banner': 'Expert configurator help',
572 In the `Expert Configurator Controls' panel, you can:
574 Press `Save' to save the new fetchmail configuration you have edited.
575 Press `Quit' to exit without saving.
576 Press `Help' to bring up this help message.
578 In the `Run Controls' panel, you can set the following options that
579 control how fetchmail runs:
582 Number of seconds to wait between polls in the background.
583 If zero, fetchmail will run in foreground.
586 If empty, emit progress and error messages to stderr.
587 Otherwise this gives the name of the files to write to.
588 This field is ignored if the "Log to syslog?" option is on.
591 If empty, store seen-message IDs in .fetchids under user's home
592 directory. If nonempty, use given file name.
595 Who to send multidrop mail to as a last resort if no address can
596 be matched. Normally empty; in this case, fetchmail treats the
597 invoking user as the address of last resort unless that user is
598 root. If that user is root, fetchmail sends to `postmaster'.
601 If this option is on (the default) error mail goes to the sender.
602 Otherwise it goes to the postmaster.
605 If false (the default) fetchmail generates a Received line into
606 each message and generates a HELO from the machine it is running on.
607 If true, fetchmail generates no Received line and HELOs as if it were
610 In the `Remote Mail Configurations' panel, you can:
612 1. Enter the name of a new remote mail server you want fetchmail to query.
614 To do this, simply enter a label for the poll configuration in the
615 `New Server:' box. The label should be a DNS name of the server (unless
616 you are using ssh or some other tunneling method and will fill in the `via'
617 option on the site configuration screen).
619 2. Change the configuration of an existing site.
621 To do this, find the site's label in the listbox and double-click it.
622 This will take you to a site configuration dialogue.
626 class ConfigurationEdit(Frame, MyWidget):
627 def __init__(self, configuration, outfile, master, onexit):
629 self.configuration = configuration
630 self.outfile = outfile
631 self.container = master
633 ConfigurationEdit.mode_to_help = {
634 'novice':configure_novice_help, 'expert':configure_expert_help
637 def server_edit(self, sitename):
638 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
640 def server_delete(self, sitename):
641 if self.subwidgets.has_key(sitename):
642 del self.configuration[sitename]
644 def edit(self, mode):
646 Frame.__init__(self, self.container)
647 self.master.title('fetchmail ' + self.mode + ' configurator');
648 self.master.iconname('fetchmail ' + self.mode + ' configurator');
649 self.keepalive = [] # Use this to anchor the PhotoImage object
650 make_icon_window(self, fetchmail_gif)
652 self.post(Configuration, 'configuration')
655 'Configurator ' + self.mode + ' Controls',
656 ConfigurationEdit.mode_to_help[self.mode],
659 gf = Frame(self, relief=RAISED, bd = 5)
661 text='Fetchmail Run Controls',
662 bd=2).pack(side=TOP, pady=10)
667 if self.mode != 'novice':
669 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
670 log.pack(side=RIGHT, anchor=E)
672 # Set the poll interval
673 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
674 de.pack(side=RIGHT, anchor=E)
679 if self.mode != 'novice':
682 {'text':'Bounces to sender?',
683 'variable':self.bouncemail,
684 'relief':GROOVE}).pack(side=LEFT, anchor=W)
689 {'text':'Log to syslog?',
690 'variable':self.syslog,
691 'relief':GROOVE}).pack(side=LEFT, anchor=W)
692 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
693 log.pack(side=RIGHT, anchor=E)
697 {'text':'Invisible mode?',
698 'variable':self.invisible,
699 'relief':GROOVE}).pack(side=LEFT, anchor=W)
701 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
702 log.pack(side=RIGHT, anchor=E)
706 # Expert mode allows us to edit multiple sites
707 lf = Frame(self, relief=RAISED, bd=5)
709 text='Remote Mail Server Configurations',
710 bd=2).pack(side=TOP, pady=10)
711 ListEdit('New Server:',
712 map(lambda x: x.pollname, self.configuration.servers),
713 lambda site, self=self: self.server_edit(site),
714 lambda site, self=self: self.server_delete(site),
719 for sitename in self.subwidgets.keys():
720 self.subwidgets[sitename].destruct()
721 self.master.destroy()
725 if ConfirmQuit(self, self.mode + " configuration editor"):
729 for sitename in self.subwidgets.keys():
730 self.subwidgets[sitename].save()
731 self.fetch(Configuration, 'configuration')
735 elif not os.path.isfile(self.outfile) or Dialog(self,
736 title = 'Overwrite existing run control file?',
737 text = 'Really overwrite existing run control file?',
739 strings = ('Yes', 'No'),
740 default = 1).num == 0:
741 fm = open(self.outfile, 'w')
743 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
744 fm.write(`self.configuration`)
748 os.chmod(self.outfile, 0600)
752 # Server editing stuff.
755 'title' : 'Remote site help',
756 'banner': 'Remote sites',
758 When you add a site name to the list here,
759 you initialize an entry telling fetchmail
760 how to poll a new site.
762 When you select a sitename (by double-
763 clicking it, or by single-clicking to
764 select and then clicking the Edit button),
765 you will open a window to configure that
770 'title' : 'Server options help',
771 'banner': 'Server Options',
773 The server options screen controls fetchmail
774 options that apply to one of your mailservers.
776 Once you have a mailserver configuration set
777 up as you like it, you can select `OK' to
778 store it in the server list maintained in
779 the main configuration window.
781 If you wish to discard changes to a server
782 configuration, select `Quit'.
786 'title' : 'Run Control help',
787 'banner': 'Run Controls',
789 If the `Poll normally' checkbox is on, the host is polled as part of
790 the normal operation of fetchmail when it is run with no arguments.
791 If it is off, fetchmail will only query this host when it is given as
792 a command-line argument.
794 The `True name of server' box should specify the actual DNS name
795 to query. By default this is the same as the poll name.
797 Normally each host described in the file is queried once each
798 poll cycle. If `Cycles to skip between polls' is greater than 0,
799 that's the number of poll cycles that are skipped between the
800 times this post is actually polled.
802 The `Server timeout' is the number of seconds fetchmail will wait
803 for a reply from the mailserver before concluding it is hung and
808 'title' : 'Protocol and Port help',
809 'banner': 'Protocol and Port',
811 These options control the remote-mail protocol
812 and TCP/IP service port used to query this
815 If you click the `Probe for supported protocols'
816 button, fetchmail will try to find you the most
817 capable server on the selected host (this will
818 only work if you're conncted to the Internet).
819 The probe only checks for ordinary IMAP and POP
820 protocols; fortunately these are the most
821 frequently supported.
823 The `Protocol' button bar offers you a choice of
824 all the different protocols available. The `auto'
825 protocol is the default mode; it probes the host
826 ports for POP3 and IMAP to see if either is
829 Normally the TCP/IP service port to use is
830 dictated by the protocol choice. The `Port'
831 field (only present in expert mode) lets you
832 set a non-standard port.
836 'title' : 'Security option help',
837 'banner': 'Security',
839 The `interface' option allows you to specify a range
840 of IP addresses to monitor for activity. If these
841 addresses are not active, fetchmail will not poll.
842 Specifying this may protect you from a spoofing attack
843 if your client machine has more than one IP gateway
844 address and some of the gateways are to insecure nets.
846 The `monitor' option, if given, specifies the only
847 device through which fetchmail is permitted to connect
848 to servers. This option may be used to prevent
849 fetchmail from triggering an expensive dial-out if the
850 interface is not already active.
852 The `interface' and `monitor' options are available
853 only for Linux and freeBSD systems. See the fetchmail
854 manual page for details on these.
856 The `netsec' option will be configurable only if fetchmail
857 was compiled with IPV6 support. If you need to use it,
858 you probably know what to do.
862 'title' : 'Multidrop option help',
863 'banner': 'Multidrop',
865 These options are only useful with multidrop mode.
866 See the manual page for extended discussion.
870 'title' : 'User list help',
871 'banner': 'User list',
873 When you add a user name to the list here,
874 you initialize an entry telling fetchmail
875 to poll the site on behalf of the new user.
877 When you select a username (by double-
878 clicking it, or by single-clicking to
879 select and then clicking the Edit button),
880 you will open a window to configure the
881 user's options on that site.
884 class ServerEdit(Frame, MyWidget):
885 def __init__(self, host, parent):
889 for site in parent.configuration.servers:
890 if site.pollname == host:
892 if (self.server == None):
893 self.server = Server()
894 self.server.pollname = host
895 self.server.via = None
896 parent.configuration.servers.append(self.server)
898 def edit(self, mode, master=None):
899 Frame.__init__(self, master)
901 self.master.title('Fetchmail host ' + self.server.pollname);
902 self.master.iconname('Fetchmail host ' + self.server.pollname);
903 self.post(Server, 'server')
904 self.makeWidgets(self.server.pollname, mode)
905 self.keepalive = [] # Use this to anchor the PhotoImage object
906 make_icon_window(self, fetchmail_gif)
913 for username in self.subwidgets.keys():
914 self.subwidgets[username].destruct()
915 del self.parent.subwidgets[self.server.pollname]
916 Widget.destroy(self.master)
919 if ConfirmQuit(self, 'server option editing'):
923 self.fetch(Server, 'server')
924 for username in self.subwidgets.keys():
925 self.subwidgets[username].save()
928 def refreshPort(self):
929 proto = self.protocol.get()
930 self.port.set(defaultports[proto])
931 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
933 def user_edit(self, username, mode):
934 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
936 def user_delete(self, username):
937 if self.subwidgets.has_key(username):
938 del self.subwidgets[username]
939 del self.server[username]
941 def makeWidgets(self, host, mode):
942 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
944 leftwin = Frame(self);
948 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
949 Label(ctlwin, text="Run Controls").pack(side=TOP)
950 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
951 LabeledEntry(ctlwin, 'True name of ' + host + ':',
952 self.via, leftwidth).pack(side=TOP, fill=X)
953 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
954 self.interval, leftwidth).pack(side=TOP, fill=X)
955 LabeledEntry(ctlwin, 'Server timeout (seconds):',
956 self.timeout, leftwidth).pack(side=TOP, fill=X)
957 Button(ctlwin, text='Help', fg='blue',
958 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
961 # Compute the available protocols from the compile-time options
963 if 'pop2' in feature_options:
964 protolist.append("POP2")
965 if 'pop3' in feature_options:
966 protolist = protolist + ["POP3", "APOP", "KPOP"]
967 if 'sdps' in feature_options:
968 protolist.append("SDPS")
969 if 'imap' in feature_options:
970 protolist.append("IMAP")
971 if 'imap-gss' in feature_options:
972 protolist.append("IMAP-GSS")
973 if 'imap-k4' in feature_options:
974 protolist.append("IMAP-K4")
975 if 'etrn' in feature_options:
976 protolist.append("ETRN")
978 protwin = Frame(leftwin, relief=RAISED, bd=5)
979 Label(protwin, text="Protocol").pack(side=TOP)
980 ButtonBar(protwin, '',
981 self.protocol, protolist, 2,
984 LabeledEntry(protwin, 'On server TCP/IP port:',
985 self.port, leftwidth).pack(side=TOP, fill=X)
988 text="POP3: track `seen' with client-side UIDLs?",
989 variable=self.uidl).pack(side=TOP)
990 Button(protwin, text='Probe for supported protocols', fg='blue',
991 command=self.autoprobe).pack(side=LEFT)
992 Button(protwin, text='Help', fg='blue',
993 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
996 userwin = Frame(leftwin, relief=RAISED, bd=5)
997 Label(userwin, text="User entries for " + host).pack(side=TOP)
998 ListEdit("New user: ",
999 map(lambda x: x.remote, self.server.users),
1000 lambda u, m=mode, s=self: s.user_edit(u, m),
1001 lambda u, s=self: s.user_delete(u),
1003 userwin.pack(fill=X)
1005 leftwin.pack(side=LEFT, anchor=N, fill=X);
1007 if mode != 'novice':
1008 rightwin = Frame(self);
1010 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1011 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1012 LabeledEntry(mdropwin, 'Envelope address header:',
1013 self.envelope, '22').pack(side=TOP, fill=X)
1014 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1015 self.envskip, '22').pack(side=TOP, fill=X)
1016 LabeledEntry(mdropwin, 'Name prefix to strip:',
1017 self.qvirtual, '22').pack(side=TOP, fill=X)
1018 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1019 variable=self.dns).pack(side=TOP)
1020 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1021 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1022 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1023 ListEdit("New domain: ",
1024 self.server.localdomains, None, None, mdropwin, multihelp)
1025 mdropwin.pack(fill=X)
1027 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1028 secwin = Frame(rightwin, relief=RAISED, bd=5)
1029 Label(secwin, text="Security").pack(side=TOP)
1030 # Don't actually let users set this. KPOP sets it implicitly
1031 # ButtonBar(secwin, 'Authorization mode:',
1032 # self.auth, authlist, 1, None).pack(side=TOP)
1033 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1034 LabeledEntry(secwin, 'IP range to check before poll:',
1035 self.interface, leftwidth).pack(side=TOP, fill=X)
1036 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1037 LabeledEntry(secwin, 'Interface to monitor:',
1038 self.monitor, leftwidth).pack(side=TOP, fill=X)
1039 if 'netsec' in feature_options or 'netsec' in dictmembers:
1040 LabeledEntry(secwin, 'IPV6 security options:',
1041 self.netsec, leftwidth).pack(side=TOP, fill=X)
1042 Button(secwin, text='Help', fg='blue',
1043 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1046 rightwin.pack(side=LEFT, anchor=N);
1048 def autoprobe(self):
1049 # Note: this only handles case (1) near fetchmail.c:892
1050 # We're assuming people smart enough to set up ssh tunneling
1051 # won't need autoprobing.
1053 realhost = self.server.via
1055 realhost = self.server.pollname
1057 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1058 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1060 sock.connect(realhost, port)
1061 greetline = sock.recv(1024)
1067 confwin = Toplevel()
1068 if greetline == None:
1069 title = "Autoprobe of " + realhost + " failed"
1071 Fetchmailconf didn't find any mailservers active.
1072 This could mean the host doesn't support any,
1073 or that your Internet connection is down, or
1074 that the host is so slow that the probe timed
1075 out before getting a response.
1079 # OK, now try to recognize potential problems
1081 if protocol == "POP2":
1082 warnings = warnings + """
1083 It appears you have somehow found a mailserver running only POP2.
1084 Congratulations. Have you considered a career in archaeology?
1086 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1087 Unless the first line of your fetchmail -V output includes the string "POP2",
1088 you'll have to build it from sources yourself with the configure
1089 switch --enable-POP2.
1092 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1093 warnings = warnings + """
1094 This appears to be an old version of the UC Davis POP server. These are
1095 dangerously unreliable (among other problems, they may drop your mailbox
1096 on the floor if your connection is interrupted during the session).
1098 It is strongly recommended that you find a better POP3 server. The fetchmail
1099 FAQ includes pointers to good ones.
1102 if string.find(greetline, "usa.net") > 0:
1103 warnings = warnings + """
1104 You appear to be using USA.NET's free mail service. Their POP3 servers
1105 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1106 fetchmail can compensate. They seem to require that fetchall be switched on
1107 (otherwise you won't necessarily see all your mail, not even new mail).
1108 They also botch the TOP command the fetchmail normally uses for retrieval
1109 (it only retrieves about 10 lines rather than the number specified).
1110 Turning on fetchall will disable the use of TOP.
1112 Therefore, it is strongly recommended that you turn on `fetchall' on all
1113 user entries associated with this server.
1117 # Steve VanDevender <stevev@efn.org> writes:
1118 # The only system I have seen this happen with is cucipop-1.31
1119 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1120 # 2.x and probably quite a few other systems. It appears to be a
1121 # bug or bad interaction with the SunOS realloc() -- it turns out
1122 # that internally cucipop does allocate a certain data structure in
1123 # multiples of 16, using realloc() to bump it up to the next
1124 # multiple if it needs more.
1126 # The distinctive symptom is that when there are 16 messages in the
1127 # inbox, you can RETR and DELE all 16 messages successfully, but on
1128 # QUIT cucipop returns something like "-ERR Error locking your
1129 # mailbox" and aborts without updating it.
1131 # The cucipop banner looks like:
1133 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1135 if string.find(greetline, "Cubic Circle") > 0:
1136 warnings = warnings + """
1137 I see your server is running cucipop. Better make sure the server box
1138 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1139 under that version, and doesn't cope with the result gracefully. Newer
1140 SunOS and Solaris machines run cucipop OK.
1142 if string.find(greetline, "QPOP") > 0:
1143 warnings = warnings + """
1144 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1145 knows all about qpopper.
1148 closebrak = string.find(greetline, ">")
1149 if closebrak > 0 and greetline[closebrak+1] == "\r":
1150 warnings = warnings + """
1151 It looks like you could use APOP on this server and avoid sending it your
1152 password in clear. You should talk to the mailserver administrator about
1156 if string.find(greetline, "csi.com") > 0:
1157 warnings = warnings + """
1158 It appears you're talking to CompuServe. You can use their special RPA
1159 service for authentication, but only if your fetchmail -V output's first
1160 line contains the string "RPA". This is not included in stock fetchmail
1161 binaries; to compile it in, rebuild from sources with the configure
1162 option --enable-RPA.
1164 if string.find(greetline, "IMAP2bis") > 0:
1165 warnings = warnings + """
1166 IMAP2bis servers have a minor problem; they can't peek at messages without
1167 marking them seen. If you take a line hit during the retrieval, the
1168 interrupted message may get left on the server, marked seen.
1170 To work around this, it is recommended that you set the `fetchall'
1171 option on all user entries associated with this server, so any stuck
1172 mail will be retrieved next time around.
1175 if string.find(greetline, "IMAP4rev1") > 0:
1176 warnings = warnings + """
1177 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1178 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1179 has therefore been extremely well tested with this class of server.
1182 warnings = warnings + """
1183 Fetchmail doesn't know anything special about this server type.
1186 # Display success window with warnings
1187 title = "Autoprobe of " + realhost + " succeeded"
1188 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1189 self.protocol.set(protocol)
1190 confwin.title(title)
1191 confwin.iconname(title)
1192 Label(confwin, text=title).pack()
1193 Message(confwin, text=confirm, width=600).pack()
1194 Button(confwin, text='Done',
1195 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1198 # User editing stuff
1202 'title' : 'User option help',
1203 'banner': 'User options',
1205 You may use this panel to set options
1206 that may differ between individual
1209 Once you have a user configuration set
1210 up as you like it, you can select `OK' to
1211 store it in the user list maintained in
1212 the site configuration window.
1214 If you wish to discard the changes you have
1215 made to user options, select `Quit'.
1219 'title' : 'Local name help',
1220 'banner': 'Local names',
1222 The local name(s) in a user entry are the
1223 people on the client machine who should
1224 receive mail from the poll described.
1226 Note: if a user entry has more than one
1227 local name, messages will be retrieved
1228 in multidrop mode. This complicates
1229 the configuration issues; see the manual
1230 page section on multidrop mode.
1233 class UserEdit(Frame, MyWidget):
1234 def __init__(self, username, parent):
1235 self.parent = parent
1237 for user in parent.server.users:
1238 if user.remote == username:
1240 if self.user == None:
1242 self.user.remote = username
1243 self.user.localnames = [username]
1244 parent.server.users.append(self.user)
1246 def edit(self, mode, master=None):
1247 Frame.__init__(self, master)
1249 self.master.title('Fetchmail user ' + self.user.remote
1250 + ' querying ' + self.parent.server.pollname);
1251 self.master.iconname('Fetchmail user ' + self.user.remote);
1252 self.post(User, 'user')
1253 self.makeWidgets(mode, self.parent.server.pollname)
1254 self.keepalive = [] # Use this to anchor the PhotoImage object
1255 make_icon_window(self, fetchmail_gif)
1258 # self.wait_window()
1262 del self.parent.subwidgets[self.user.remote]
1263 Widget.destroy(self.master)
1266 if ConfirmQuit(self, 'user option editing'):
1270 self.fetch(User, 'user')
1273 def makeWidgets(self, mode, servername):
1274 dispose_window(self,
1275 "User options for " + self.user.remote + " querying " + servername,
1278 if mode != 'novice':
1279 leftwin = Frame(self);
1283 secwin = Frame(leftwin, relief=RAISED, bd=5)
1284 Label(secwin, text="Authentication").pack(side=TOP)
1285 LabeledEntry(secwin, 'Password:',
1286 self.password, '12').pack(side=TOP, fill=X)
1287 secwin.pack(fill=X, anchor=N)
1289 names = Frame(leftwin, relief=RAISED, bd=5)
1290 Label(names, text="Local names").pack(side=TOP)
1291 ListEdit("New name: ",
1292 self.user.localnames, None, None, names, localhelp)
1293 names.pack(fill=X, anchor=N)
1295 if mode != 'novice':
1296 targwin = Frame(leftwin, relief=RAISED, bd=5)
1297 Label(targwin, text="Forwarding Options").pack(side=TOP)
1298 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1299 ListEdit("New listener:",
1300 self.user.smtphunt, None, None, targwin, None)
1301 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1302 self.smtpaddress, '26').pack(side=TOP, fill=X)
1303 LabeledEntry(targwin, 'Connection setup command:',
1304 self.preconnect, '26').pack(side=TOP, fill=X)
1305 LabeledEntry(targwin, 'Connection wrapup command:',
1306 self.postconnect, '26').pack(side=TOP, fill=X)
1307 LabeledEntry(targwin, 'Local delivery agent:',
1308 self.mda, '26').pack(side=TOP, fill=X)
1309 LabeledEntry(targwin, 'BSMTP output file:',
1310 self.bsmtp, '26').pack(side=TOP, fill=X)
1311 LabeledEntry(targwin, 'Listener spam-block codes:',
1312 self.antispam, '26').pack(side=TOP, fill=X)
1313 LabeledEntry(targwin, 'Pass-through properties:',
1314 self.properties, '26').pack(side=TOP, fill=X)
1315 Checkbutton(targwin, text="Use LMTP?",
1316 variable=self.lmtp).pack(side=TOP, fill=X)
1317 targwin.pack(fill=X, anchor=N)
1319 if mode != 'novice':
1320 leftwin.pack(side=LEFT, fill=X, anchor=N)
1321 rightwin = Frame(self)
1325 optwin = Frame(rightwin, relief=RAISED, bd=5)
1326 Label(optwin, text="Processing Options").pack(side=TOP)
1327 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1328 variable=self.keep).pack(side=TOP, anchor=W)
1329 Checkbutton(optwin, text="Fetch old messages as well as new",
1330 variable=self.fetchall).pack(side=TOP, anchor=W)
1331 if mode != 'novice':
1332 Checkbutton(optwin, text="Flush seen messages before retrieval",
1333 variable=self.flush).pack(side=TOP, anchor=W)
1334 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1335 variable=self.rewrite).pack(side=TOP, anchor=W)
1336 Checkbutton(optwin, text="Force CR/LF at end of each line",
1337 variable=self.forcecr).pack(side=TOP, anchor=W)
1338 Checkbutton(optwin, text="Strip CR from end of each line",
1339 variable=self.stripcr).pack(side=TOP, anchor=W)
1340 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1341 variable=self.pass8bits).pack(side=TOP, anchor=W)
1342 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1343 variable=self.mimedecode).pack(side=TOP, anchor=W)
1344 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1345 variable=self.dropstatus).pack(side=TOP, anchor=W)
1348 if mode != 'novice':
1349 limwin = Frame(rightwin, relief=RAISED, bd=5)
1350 Label(limwin, text="Resource Limits").pack(side=TOP)
1351 LabeledEntry(limwin, 'Message size limit:',
1352 self.limit, '30').pack(side=TOP, fill=X)
1353 LabeledEntry(limwin, 'Size warning interval:',
1354 self.warnings, '30').pack(side=TOP, fill=X)
1355 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1356 self.fetchlimit, '30').pack(side=TOP, fill=X)
1357 LabeledEntry(limwin, 'Max messages to forward per poll:',
1358 self.batchlimit, '30').pack(side=TOP, fill=X)
1359 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1360 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1361 self.expunge, '30').pack(side=TOP, fill=X)
1364 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1365 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1366 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1367 ListEdit("New folder:", self.user.mailboxes,
1368 None, None, foldwin, None)
1369 foldwin.pack(fill=X, anchor=N)
1371 if mode != 'novice':
1372 rightwin.pack(side=LEFT)
1378 # Top-level window that offers either novice or expert mode
1379 # (but not both at once; it disappears when one is selected).
1382 class Configurator(Frame):
1383 def __init__(self, outfile, master, onexit, parent):
1384 Frame.__init__(self, master)
1385 self.outfile = outfile
1386 self.onexit = onexit
1387 self.parent = parent
1388 self.master.title('fetchmail configurator');
1389 self.master.iconname('fetchmail configurator');
1391 self.keepalive = [] # Use this to anchor the PhotoImage object
1392 make_icon_window(self, fetchmail_gif)
1394 Message(self, text="""
1395 Use `Novice Configuration' for basic fetchmail setup;
1396 with this, you can easily set up a single-drop connection
1397 to one remote mail server.
1398 """, width=600).pack(side=TOP)
1399 Button(self, text='Novice Configuration',
1400 fg='blue', command=self.novice).pack()
1402 Message(self, text="""
1403 Use `Expert Configuration' for advanced fetchmail setup,
1404 including multiple-site or multidrop connections.
1405 """, width=600).pack(side=TOP)
1406 Button(self, text='Expert Configuration',
1407 fg='blue', command=self.expert).pack()
1409 Message(self, text="""
1410 Or you can just select `Quit' to leave the configurator now and
1411 return to the main panel.
1412 """, width=600).pack(side=TOP)
1413 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1416 self.master.destroy()
1417 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1420 self.master.destroy()
1421 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1424 self.master.destroy()
1427 # Run a command an a scrolling text widget, displaying its output
1429 class RunWindow(Frame):
1430 def __init__(self, command, master, parent):
1431 Frame.__init__(self, master)
1432 self.master = master
1433 self.master.title('fetchmail run window');
1434 self.master.iconname('fetchmail run window');
1437 text="Running "+command,
1438 bd=2).pack(side=TOP, pady=10)
1439 self.keepalive = [] # Use this to anchor the PhotoImage object
1440 make_icon_window(self, fetchmail_gif)
1442 # This is a scrolling text window
1443 textframe = Frame(self)
1444 scroll = Scrollbar(textframe)
1445 self.textwidget = Text(textframe, setgrid=TRUE)
1446 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1447 self.textwidget.config(yscrollcommand=scroll.set)
1448 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1449 scroll.config(command=self.textwidget.yview)
1450 scroll.pack(side=RIGHT, fill=BOTH)
1451 textframe.pack(side=TOP)
1453 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1455 self.update() # Draw widget before executing fetchmail
1457 child_stdout = os.popen(command + " 2>&1", "r")
1459 ch = child_stdout.read(1)
1462 self.textwidget.insert(END, ch)
1463 self.textwidget.insert(END, "Done.")
1464 self.textwidget.see(END);
1467 Widget.destroy(self.master)
1469 # Here's where we choose either configuration or launching
1471 class MainWindow(Frame):
1472 def __init__(self, outfile, master=None):
1473 Frame.__init__(self, master)
1474 self.outfile = outfile
1475 self.master.title('fetchmail launcher');
1476 self.master.iconname('fetchmail launcher');
1479 text='Fetchmailconf ' + version,
1480 bd=2).pack(side=TOP, pady=10)
1481 self.keepalive = [] # Use this to anchor the PhotoImage object
1482 make_icon_window(self, fetchmail_gif)
1485 Message(self, text="""
1486 Use `Configure fetchmail' to tell fetchmail about the remote
1487 servers it should poll (the host name, your username there,
1488 whether to use POP or IMAP, and so forth).
1489 """, width=600).pack(side=TOP)
1490 self.configbutton = Button(self, text='Configure fetchmail',
1491 fg='blue', command=self.configure)
1492 self.configbutton.pack()
1494 Message(self, text="""
1495 Use `Test fetchmail' to run fetchmail with debugging enabled.
1496 This is a good way to test out a new configuration.
1497 """, width=600).pack(side=TOP)
1498 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1500 Message(self, text="""
1501 Use `Run fetchmail' to run fetchmail in foreground.
1502 Progress messages will be shown, but not debug messages.
1503 """, width=600).pack(side=TOP)
1504 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1506 Message(self, text="""
1507 Or you can just select `Quit' to exit the launcher now.
1508 """, width=600).pack(side=TOP)
1509 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1511 def configure(self):
1512 self.configbutton.configure(state=DISABLED)
1513 Configurator(self.outfile, Toplevel(),
1514 lambda self=self: self.configbutton.configure(state=NORMAL),
1518 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1521 RunWindow("fetchmail -d0", Toplevel(), self)
1526 # Functions for turning a dictionary into an instantiated object tree.
1528 def intersect(list1, list2):
1529 # Compute set intersection of lists
1536 def setdiff(list1, list2):
1537 # Compute set difference of lists
1544 def copy_instance(toclass, fromdict):
1545 # Initialize a class object of given type from a conformant dictionary.
1546 for fld in fromdict.keys():
1547 if not fld in dictmembers:
1548 dictmembers.append(fld)
1549 # The `optional' fields are the ones we can ignore for purposes of
1550 # conformability checking; they'll still get copied if they are
1551 # present in the dictionary.
1552 optional = ('interface', 'monitor', 'netsec')
1553 class_sig = setdiff(toclass.__dict__.keys(), optional)
1555 dict_keys = setdiff(fromdict.keys(), optional)
1557 common = intersect(class_sig, dict_keys)
1558 if 'typemap' in class_sig:
1559 class_sig.remove('typemap')
1560 if tuple(class_sig) != tuple(dict_keys):
1561 print "Fields don't match what fetchmailconf expected:"
1562 # print "Class signature: " + `class_sig`
1563 # print "Dictionary keys: " + `dict_keys`
1564 diff = setdiff(class_sig, common)
1566 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1567 diff = setdiff(dict_keys, common)
1569 print "Not matched in dictionary keys: " + `diff`
1572 for x in fromdict.keys():
1573 setattr(toclass, x, fromdict[x])
1576 # And this is the main sequence. How it works:
1578 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1579 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1580 # Run execfile on the file to pull fetchmailrc into Python global space.
1581 # You don't want static data, though; you want, instead, a tree of objects
1582 # with the same data members and added appropriate methods.
1584 # This is what the copy_instance function() is for. It tries to copy a
1585 # dictionary field by field into a class, aborting if the class and dictionary
1586 # have different data members (except for any typemap member in the class;
1587 # that one is strictly for use by the MyWidget supperclass).
1589 # Once the object tree is set up, require user to choose novice or expert
1590 # mode and instantiate an edit object for the configuration. Class methods
1591 # will take it all from there.
1593 # Options (not documented because they're for fetchmailconf debuggers only):
1594 # -d: Read the configuration and dump it to stdout before editing. Dump
1595 # the edited result to stdout as well.
1596 # -f: specify the run control file to read.
1598 if __name__ == '__main__':
1601 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1602 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1603 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1604 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1605 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1606 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1607 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1608 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1609 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1610 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1611 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1612 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1613 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1614 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1615 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1616 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1617 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1618 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1619 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1620 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1621 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1622 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1623 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1624 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1625 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1626 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1627 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1628 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1629 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1630 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1631 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1632 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1634 # Note on making icons: the above was generated by the following procedure:
1637 # data = open("fetchmail.gif", "rb").read()
1638 # print "fetchmail_gif =\\"
1639 # print repr(base64.encodestring(data))
1643 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1644 dump = rcfile = None;
1645 for (switch, val) in options:
1646 if (switch == '-d'):
1648 elif (switch == '-f'):
1651 # Get client host's FQDN
1652 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1655 ConfigurationDefaults = Configuration()
1656 ServerDefaults = Server()
1657 UserDefaults = User()
1659 # Read the existing configuration
1660 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1662 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1664 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1669 print "`" + cmd + "' run failure, status " + `s`
1672 print "Unknown error while running fetchmail --configdump"
1679 print "Can't read configuration output of fetchmail --configdump."
1685 # The tricky part -- initializing objects from the configuration global
1686 # `Configuration' is the top level of the object tree we're going to mung.
1687 # The dictmembers list is used to track the set of fields the dictionary
1688 # contains; in particular, we can use it to tell whether things like the
1689 # monitor, interface, and netsec fields are present.
1691 Fetchmailrc = Configuration()
1692 copy_instance(Fetchmailrc, fetchmailrc)
1693 Fetchmailrc.servers = [];
1694 for server in fetchmailrc['servers']:
1696 copy_instance(Newsite, server)
1697 Fetchmailrc.servers.append(Newsite)
1699 for user in server['users']:
1701 copy_instance(Newuser, user)
1702 Newsite.users.append(Newuser)
1704 # We may want to display the configuration and quit
1706 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1708 # The theory here is that -f alone sets the rcfile location,
1709 # but -d and -f together mean the new configuration should go to stdout.
1710 if not rcfile and not dump:
1711 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1713 # OK, now run the configuration edit
1714 root = MainWindow(rcfile)
1717 # The following sets edit modes for GNU EMACS