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: res = res + "poll"
116 else: res = res + "skip"
117 res = res + (" " + self.pollname)
119 res = res + (" via " + str(self.via) + "\n");
120 if self.protocol != ServerDefaults.protocol:
121 res = res + " with proto " + self.protocol
122 if self.port != defaultports[self.protocol] and self.port != 0:
123 res = res + " port " + `self.port`
124 if self.timeout != ServerDefaults.timeout:
125 res = res + " timeout " + `self.timeout`
126 if self.interval != ServerDefaults.interval:
127 res = res + " interval " + `self.interval`
128 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
130 res = res + " envelope " + `self.envskip` + " " + self.envelope
132 res = res + " envelope " + self.envelope
134 res = res + (" qvirtual " + str(self.qvirtual) + "\n");
135 if self.preauth != ServerDefaults.preauth:
136 res = res + " preauth " + self.preauth
137 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
138 res = res + " and options"
139 if self.dns != ServerDefaults.dns:
140 res = res + flag2str(self.dns, 'dns')
141 if self.uidl != ServerDefaults.uidl:
142 res = res + flag2str(self.uidl, 'uidl')
143 if folded: res = res + "\n "
144 else: res = res + " "
150 if self.aka and self.localdomains: res = res + " "
151 if self.localdomains:
152 res = res + ("localdomains")
153 for x in self.localdomains:
155 if (self.aka or self.localdomains):
162 res = res + "interface " + str(self.interface)
164 res = res + "monitor " + str(self.monitor)
166 res = res + "netsec " + str(self.netsec)
167 if self.interface or self.monitor or self.netsec:
171 if res[-1] == " ": res = res[0:-1]
173 for user in self.users:
174 res = res + repr(user)
178 def __delitem__(self, name):
179 for ui in range(len(self.users)):
180 if self.users[ui].remote == name:
185 return self.dump(TRUE)
188 return "[Server: " + self.dump(FALSE) + "]"
192 if os.environ.has_key("USER"):
193 self.remote = os.environ["USER"] # Remote username
194 elif os.environ.has_key("LOGNAME"):
195 self.remote = os.environ["LOGNAME"]
197 print "Can't get your username!"
199 self.localnames = [self.remote,]# Local names
200 self.password = None # Password for mail account access
201 self.mailboxes = [] # Remote folders to retrieve from
202 self.smtphunt = [] # Hosts to forward to
203 self.smtpaddress = None # Append this to MAIL FROM line
204 self.preconnect = None # Connection setup
205 self.postconnect = None # Connection wrapup
206 self.mda = None # Mail Delivery Agent
207 self.bsmtp = None # BSMTP output file
208 self.lmtp = FALSE # Use LMTP rather than SMTP?
209 self.antispam = "571 550 501" # Listener's spam-block code
210 self.keep = FALSE # Keep messages
211 self.flush = FALSE # Flush messages
212 self.fetchall = FALSE # Fetch old messages
213 self.rewrite = TRUE # Rewrite message headers
214 self.forcecr = FALSE # Force LF -> CR/LF
215 self.stripcr = FALSE # Strip CR
216 self.pass8bits = FALSE # Force BODY=7BIT
217 self.mimedecode = FALSE # Undo MIME armoring
218 self.dropstatus = FALSE # Drop incoming Status lines
219 self.dropdelivered = FALSE # Drop incoming Delivered-To lines
220 self.idle = FALSE # IDLE after poll
221 self.limit = 0 # Message size limit
222 self.warnings = 0 # Size warning interval
223 self.fetchlimit = 0 # Max messages fetched per batch
224 self.batchlimit = 0 # Max message forwarded per batch
225 self.expunge = 0 # Interval between expunges (IMAP)
226 self.ssl = 0 # Enable Seccure Socket Layer
227 self.sslkey = None # SSL key filename
228 self.sslcert = None # SSL certificate filename
229 self.properties = None # Extension properties
231 ('remote', 'String'),
232 # leave out mailboxes and localnames
233 ('password', 'String'),
235 ('smtpaddress', 'String'),
236 ('preconnect', 'String'),
237 ('postconnect', 'String'),
241 ('antispam', 'String'),
243 ('flush', 'Boolean'),
244 ('fetchall', 'Boolean'),
245 ('rewrite', 'Boolean'),
246 ('forcecr', 'Boolean'),
247 ('stripcr', 'Boolean'),
248 ('pass8bits', 'Boolean'),
249 ('mimedecode', 'Boolean'),
250 ('dropstatus', 'Boolean'),
251 ('dropdelivered', 'Boolean'),
255 ('fetchlimit', 'Int'),
256 ('batchlimit', 'Int'),
259 ('sslkey', 'String'),
260 ('sslcert', 'String'),
261 ('properties', 'String'))
265 res = res + "user " + `self.remote` + " there ";
267 res = res + "with password " + `self.password` + " "
270 for x in self.localnames:
273 if (self.keep != UserDefaults.keep
274 or self.flush != UserDefaults.flush
275 or self.fetchall != UserDefaults.fetchall
276 or self.rewrite != UserDefaults.rewrite
277 or self.forcecr != UserDefaults.forcecr
278 or self.stripcr != UserDefaults.stripcr
279 or self.pass8bits != UserDefaults.pass8bits
280 or self.mimedecode != UserDefaults.mimedecode
281 or self.dropstatus != UserDefaults.dropstatus
282 or self.dropdelivered != UserDefaults.dropdelivered
283 or self.idle != UserDefaults.idle):
284 res = res + " options"
285 if self.keep != UserDefaults.keep:
286 res = res + flag2str(self.keep, 'keep')
287 if self.flush != UserDefaults.flush:
288 res = res + flag2str(self.flush, 'flush')
289 if self.fetchall != UserDefaults.fetchall:
290 res = res + flag2str(self.fetchall, 'fetchall')
291 if self.rewrite != UserDefaults.rewrite:
292 res = res + flag2str(self.rewrite, 'rewrite')
293 if self.forcecr != UserDefaults.forcecr:
294 res = res + flag2str(self.forcecr, 'forcecr')
295 if self.stripcr != UserDefaults.stripcr:
296 res = res + flag2str(self.stripcr, 'stripcr')
297 if self.pass8bits != UserDefaults.pass8bits:
298 res = res + flag2str(self.pass8bits, 'pass8bits')
299 if self.mimedecode != UserDefaults.mimedecode:
300 res = res + flag2str(self.mimedecode, 'mimedecode')
301 if self.dropstatus != UserDefaults.dropstatus:
302 res = res + flag2str(self.dropstatus, 'dropstatus')
303 if self.dropdelivered != UserDefaults.dropdelivered:
304 res = res + flag2str(self.dropdelivered, 'dropdelivered')
305 if self.idle != UserDefaults.idle:
306 res = res + flag2str(self.idle, 'idle')
307 if self.limit != UserDefaults.limit:
308 res = res + " limit " + `self.limit`
309 if self.warnings != UserDefaults.warnings:
310 res = res + " warnings " + `self.warnings`
311 if self.fetchlimit != UserDefaults.fetchlimit:
312 res = res + " fetchlimit " + `self.fetchlimit`
313 if self.batchlimit != UserDefaults.batchlimit:
314 res = res + " batchlimit " + `self.batchlimit`
315 if self.ssl and self.ssl != UserDefaults.ssl:
316 res = res + flag2str(self.ssl, 'ssl')
317 if self.sslkey and self.sslkey != UserDefaults.sslkey:
318 res = res + " sslkey " + `self.sslkey`
319 if self.sslcert and self.sslcert != UserDefaults.sslcert:
320 res = res + " sslcert " + `self.sslcert`
321 if self.expunge != UserDefaults.expunge:
322 res = res + " expunge " + `self.expunge`
324 trimmed = self.smtphunt;
325 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
326 trimmed = trimmed[0:len(trimmed) - 1]
327 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
328 trimmed = trimmed[0:len(trimmed) - 1]
330 res = res + " smtphost "
335 res = res + " folder"
336 for x in self.mailboxes:
339 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
340 if getattr(self, fld):
341 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
342 if self.lmtp != UserDefaults.lmtp:
343 res = res + flag2str(self.lmtp, 'lmtp')
344 if self.antispam != UserDefaults.antispam:
345 res = res + " antispam " + self.antispam + "\n"
349 return "[User: " + repr(self) + "]"
355 defaultports = {"auto":0,
365 preauthlist = ("password", "kerberos", "ssh")
368 'title' : 'List Selection Help',
369 'banner': 'List Selection',
371 You must select an item in the list box (by clicking on it).
374 def flag2str(value, string):
375 # make a string representation of a .fetchmailrc flag or negated flag
379 if value == FALSE: str = str + ("no ")
383 class LabeledEntry(Frame):
384 # widget consisting of entry field with caption to left
385 def bind(self, key, action):
386 self.E.bind(key, action)
389 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
390 Frame.__init__(self, Master)
391 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
392 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
393 self.L.pack({'side':'left'})
394 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
396 def ButtonBar(frame, legend, ref, alternatives, depth, command):
397 # array of radio buttons, caption to left, picking from a string list
399 width = len(alternatives) / depth;
400 Label(bar, text=legend).pack(side=LEFT)
401 for column in range(width):
402 subframe = Frame(bar)
403 for row in range(depth):
404 ind = width * row + column
405 Radiobutton(subframe,
406 {'text':alternatives[ind],
408 'value':alternatives[ind],
409 'command':command}).pack(side=TOP, anchor=W)
410 subframe.pack(side=LEFT)
414 def helpwin(helpdict):
415 # help message window with a self-destruct button
417 helpwin.title(helpdict['title'])
418 helpwin.iconname(helpdict['title'])
419 Label(helpwin, text=helpdict['banner']).pack()
420 textframe = Frame(helpwin)
421 scroll = Scrollbar(textframe)
422 helpwin.textwidget = Text(textframe, setgrid=TRUE)
423 textframe.pack(side=TOP, expand=YES, fill=BOTH)
424 helpwin.textwidget.config(yscrollcommand=scroll.set)
425 helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
426 scroll.config(command=helpwin.textwidget.yview)
427 scroll.pack(side=RIGHT, fill=BOTH)
428 helpwin.textwidget.insert(END, helpdict['text']);
429 Button(helpwin, text='Done',
430 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
431 textframe.pack(side=TOP)
433 def make_icon_window(base, image):
435 # Some older pythons will error out on this
436 icon_image = PhotoImage(data=image)
437 icon_window = Toplevel()
438 Label(icon_window, image=icon_image, bg='black').pack()
439 base.master.iconwindow(icon_window)
440 # Avoid TkInter brain death. PhotoImage objects go out of
441 # scope when the enclosing function returns. Therefore
442 # we have to explicitly link them to something.
443 base.keepalive.append(icon_image)
447 class ListEdit(Frame):
448 # edit a list of values (duplicates not allowed) with a supplied editor hook
449 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
451 self.deletor = deletor
454 # Set up a widget to accept new elements
455 self.newval = StringVar(master)
456 newwin = LabeledEntry(master, newlegend, self.newval, '12')
457 newwin.bind('<Double-1>', self.handleNew)
458 newwin.bind('<Return>', self.handleNew)
459 newwin.pack(side=TOP, fill=X, anchor=E)
461 # Edit the existing list
462 listframe = Frame(master)
463 scroll = Scrollbar(listframe)
464 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
467 self.listwidget.insert(END, x)
468 listframe.pack(side=TOP, expand=YES, fill=BOTH)
469 self.listwidget.config(yscrollcommand=scroll.set)
470 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
471 scroll.config(command=self.listwidget.yview)
472 scroll.pack(side=RIGHT, fill=BOTH)
473 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
474 self.listwidget.bind('<Double-1>', self.handleList);
475 self.listwidget.bind('<Return>', self.handleList);
479 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
480 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
482 self.helptxt = helptxt
483 Button(bf, text='Help', fg='blue',
484 command=self.help).pack(side=RIGHT)
488 helpwin(self.helptxt)
490 def handleList(self, event):
493 def handleNew(self, event):
494 item = self.newval.get()
496 entire = self.listwidget.get(0, self.listwidget.index('end'));
497 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
498 self.listwidget.insert('end', item)
499 if self.list != None: self.list.append(item)
501 apply(self.editor, (item,))
505 select = self.listwidget.curselection()
510 if index and self.editor:
511 label = self.listwidget.get(index);
513 apply(self.editor, (label,))
515 def deleteItem(self):
516 select = self.listwidget.curselection()
520 index = string.atoi(select[0])
521 label = self.listwidget.get(index);
522 self.listwidget.delete(index)
523 if self.list != None:
525 if self.deletor != None:
526 apply(self.deletor, (label,))
528 def ConfirmQuit(frame, context):
531 text = 'Really quit ' + context + ' without saving?',
533 strings = ('Yes', 'No'),
537 def dispose_window(master, legend, help, savelegend='OK'):
538 dispose = Frame(master, relief=RAISED, bd=5)
539 Label(dispose, text=legend).pack(side=TOP,pady=10)
540 Button(dispose, text=savelegend, fg='blue',
541 command=master.save).pack(side=LEFT)
542 Button(dispose, text='Quit', fg='blue',
543 command=master.nosave).pack(side=LEFT)
544 Button(dispose, text='Help', fg='blue',
545 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
550 # Common methods for Tkinter widgets -- deals with Tkinter declaration
551 def post(self, widgetclass, field):
552 for x in widgetclass.typemap:
553 if x[1] == 'Boolean':
554 setattr(self, x[0], BooleanVar(self))
555 elif x[1] == 'String':
556 setattr(self, x[0], StringVar(self))
558 setattr(self, x[0], IntVar(self))
559 source = getattr(getattr(self, field), x[0])
561 getattr(self, x[0]).set(source)
563 def fetch(self, widgetclass, field):
564 for x in widgetclass.typemap:
565 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
568 # First, code to set the global fetchmail run controls.
571 configure_novice_help = {
572 'title' : 'Fetchmail novice configurator help',
573 'banner': 'Novice configurator help',
575 In the `Novice Configurator Controls' panel, you can:
577 Press `Save' to save the new fetchmail configuration you have created.
578 Press `Quit' to exit without saving.
579 Press `Help' to bring up this help message.
581 In the `Novice Configuration' panels, you will set up the basic data
582 needed to create a simple fetchmail setup. These include:
584 1. The name of the remote site you want to query.
586 2. Your login name on that site.
588 3. Your password on that site.
590 4. A protocol to use (POP, IMAP, ETRN, etc.)
592 5. A polling interval.
594 6. Options to fetch old messages as well as new, uor to suppress
595 deletion of fetched message.
597 The novice-configuration code will assume that you want to forward mail
598 to a local sendmail listener with no special options.
601 configure_expert_help = {
602 'title' : 'Fetchmail expert configurator help',
603 'banner': 'Expert configurator help',
605 In the `Expert Configurator Controls' panel, you can:
607 Press `Save' to save the new fetchmail configuration you have edited.
608 Press `Quit' to exit without saving.
609 Press `Help' to bring up this help message.
611 In the `Run Controls' panel, you can set the following options that
612 control how fetchmail runs:
615 Number of seconds to wait between polls in the background.
616 If zero, fetchmail will run in foreground.
619 If empty, emit progress and error messages to stderr.
620 Otherwise this gives the name of the files to write to.
621 This field is ignored if the "Log to syslog?" option is on.
624 If empty, store seen-message IDs in .fetchids under user's home
625 directory. If nonempty, use given file name.
628 Who to send multidrop mail to as a last resort if no address can
629 be matched. Normally empty; in this case, fetchmail treats the
630 invoking user as the address of last resort unless that user is
631 root. If that user is root, fetchmail sends to `postmaster'.
634 If this option is on (the default) error mail goes to the sender.
635 Otherwise it goes to the postmaster.
638 If false (the default) fetchmail generates a Received line into
639 each message and generates a HELO from the machine it is running on.
640 If true, fetchmail generates no Received line and HELOs as if it were
643 In the `Remote Mail Configurations' panel, you can:
645 1. Enter the name of a new remote mail server you want fetchmail to query.
647 To do this, simply enter a label for the poll configuration in the
648 `New Server:' box. The label should be a DNS name of the server (unless
649 you are using ssh or some other tunneling method and will fill in the `via'
650 option on the site configuration screen).
652 2. Change the configuration of an existing site.
654 To do this, find the site's label in the listbox and double-click it.
655 This will take you to a site configuration dialogue.
659 class ConfigurationEdit(Frame, MyWidget):
660 def __init__(self, configuration, outfile, master, onexit):
662 self.configuration = configuration
663 self.outfile = outfile
664 self.container = master
666 ConfigurationEdit.mode_to_help = {
667 'novice':configure_novice_help, 'expert':configure_expert_help
670 def server_edit(self, sitename):
671 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
673 def server_delete(self, sitename):
675 for user in self.subwidgets.keys():
677 del self.configuration[sitename]
681 def edit(self, mode):
683 Frame.__init__(self, self.container)
684 self.master.title('fetchmail ' + self.mode + ' configurator');
685 self.master.iconname('fetchmail ' + self.mode + ' configurator');
686 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
687 self.keepalive = [] # Use this to anchor the PhotoImage object
688 make_icon_window(self, fetchmail_icon)
690 self.post(Configuration, 'configuration')
693 'Configurator ' + self.mode + ' Controls',
694 ConfigurationEdit.mode_to_help[self.mode],
697 gf = Frame(self, relief=RAISED, bd = 5)
699 text='Fetchmail Run Controls',
700 bd=2).pack(side=TOP, pady=10)
705 if self.mode != 'novice':
707 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
708 log.pack(side=RIGHT, anchor=E)
710 # Set the poll interval
711 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
712 de.pack(side=RIGHT, anchor=E)
717 if self.mode != 'novice':
720 {'text':'Bounces to sender?',
721 'variable':self.bouncemail,
722 'relief':GROOVE}).pack(side=LEFT, anchor=W)
727 {'text':'Log to syslog?',
728 'variable':self.syslog,
729 'relief':GROOVE}).pack(side=LEFT, anchor=W)
730 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
731 log.pack(side=RIGHT, anchor=E)
735 {'text':'Invisible mode?',
736 'variable':self.invisible,
737 'relief':GROOVE}).pack(side=LEFT, anchor=W)
739 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
740 log.pack(side=RIGHT, anchor=E)
744 # Expert mode allows us to edit multiple sites
745 lf = Frame(self, relief=RAISED, bd=5)
747 text='Remote Mail Server Configurations',
748 bd=2).pack(side=TOP, pady=10)
749 ListEdit('New Server:',
750 map(lambda x: x.pollname, self.configuration.servers),
751 lambda site, self=self: self.server_edit(site),
752 lambda site, self=self: self.server_delete(site),
757 for sitename in self.subwidgets.keys():
758 self.subwidgets[sitename].destruct()
759 self.master.destroy()
763 if ConfirmQuit(self, self.mode + " configuration editor"):
767 for sitename in self.subwidgets.keys():
768 self.subwidgets[sitename].save()
769 self.fetch(Configuration, 'configuration')
773 elif not os.path.isfile(self.outfile) or Dialog(self,
774 title = 'Overwrite existing run control file?',
775 text = 'Really overwrite existing run control file?',
777 strings = ('Yes', 'No'),
778 default = 1).num == 0:
779 fm = open(self.outfile, 'w')
781 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
782 fm.write(`self.configuration`)
786 os.chmod(self.outfile, 0600)
790 # Server editing stuff.
793 'title' : 'Remote site help',
794 'banner': 'Remote sites',
796 When you add a site name to the list here,
797 you initialize an entry telling fetchmail
798 how to poll a new site.
800 When you select a sitename (by double-
801 clicking it, or by single-clicking to
802 select and then clicking the Edit button),
803 you will open a window to configure that
808 'title' : 'Server options help',
809 'banner': 'Server Options',
811 The server options screen controls fetchmail
812 options that apply to one of your mailservers.
814 Once you have a mailserver configuration set
815 up as you like it, you can select `OK' to
816 store it in the server list maintained in
817 the main configuration window.
819 If you wish to discard changes to a server
820 configuration, select `Quit'.
824 'title' : 'Run Control help',
825 'banner': 'Run Controls',
827 If the `Poll normally' checkbox is on, the host is polled as part of
828 the normal operation of fetchmail when it is run with no arguments.
829 If it is off, fetchmail will only query this host when it is given as
830 a command-line argument.
832 The `True name of server' box should specify the actual DNS name
833 to query. By default this is the same as the poll name.
835 Normally each host described in the file is queried once each
836 poll cycle. If `Cycles to skip between polls' is greater than 0,
837 that's the number of poll cycles that are skipped between the
838 times this post is actually polled.
840 The `Server timeout' is the number of seconds fetchmail will wait
841 for a reply from the mailserver before concluding it is hung and
846 'title' : 'Protocol and Port help',
847 'banner': 'Protocol and Port',
849 These options control the remote-mail protocol
850 and TCP/IP service port used to query this
853 If you click the `Probe for supported protocols'
854 button, fetchmail will try to find you the most
855 capable server on the selected host (this will
856 only work if you're conncted to the Internet).
857 The probe only checks for ordinary IMAP and POP
858 protocols; fortunately these are the most
859 frequently supported.
861 The `Protocol' button bar offers you a choice of
862 all the different protocols available. The `auto'
863 protocol is the default mode; it probes the host
864 ports for POP3 and IMAP to see if either is
867 Normally the TCP/IP service port to use is
868 dictated by the protocol choice. The `Port'
869 field (only present in expert mode) lets you
870 set a non-standard port.
874 'title' : 'Security option help',
875 'banner': 'Security',
877 The `interface' option allows you to specify a range
878 of IP addresses to monitor for activity. If these
879 addresses are not active, fetchmail will not poll.
880 Specifying this may protect you from a spoofing attack
881 if your client machine has more than one IP gateway
882 address and some of the gateways are to insecure nets.
884 The `monitor' option, if given, specifies the only
885 device through which fetchmail is permitted to connect
886 to servers. This option may be used to prevent
887 fetchmail from triggering an expensive dial-out if the
888 interface is not already active.
890 The `interface' and `monitor' options are available
891 only for Linux and freeBSD systems. See the fetchmail
892 manual page for details on these.
894 The ssl option enables SSL communication with a mailserver
895 supporting Secure Sockets Layer. The sslkey and sslcert options
896 declare key and certificate files for use with SSL.
898 The `netsec' option will be configurable only if fetchmail
899 was compiled with IPV6 support. If you need to use it,
900 you probably know what to do.
904 'title' : 'Multidrop option help',
905 'banner': 'Multidrop',
907 These options are only useful with multidrop mode.
908 See the manual page for extended discussion.
912 'title' : 'User list help',
913 'banner': 'User list',
915 When you add a user name to the list here,
916 you initialize an entry telling fetchmail
917 to poll the site on behalf of the new user.
919 When you select a username (by double-
920 clicking it, or by single-clicking to
921 select and then clicking the Edit button),
922 you will open a window to configure the
923 user's options on that site.
926 class ServerEdit(Frame, MyWidget):
927 def __init__(self, host, parent):
931 for site in parent.configuration.servers:
932 if site.pollname == host:
934 if (self.server == None):
935 self.server = Server()
936 self.server.pollname = host
937 self.server.via = None
938 parent.configuration.servers.append(self.server)
940 def edit(self, mode, master=None):
941 Frame.__init__(self, master)
943 self.master.title('Fetchmail host ' + self.server.pollname);
944 self.master.iconname('Fetchmail host ' + self.server.pollname);
945 self.post(Server, 'server')
946 self.makeWidgets(self.server.pollname, mode)
947 self.keepalive = [] # Use this to anchor the PhotoImage object
948 make_icon_window(self, fetchmail_icon)
955 for username in self.subwidgets.keys():
956 self.subwidgets[username].destruct()
957 del self.parent.subwidgets[self.server.pollname]
958 Widget.destroy(self.master)
961 if ConfirmQuit(self, 'server option editing'):
965 self.fetch(Server, 'server')
966 for username in self.subwidgets.keys():
967 self.subwidgets[username].save()
970 def refreshPort(self):
971 proto = self.protocol.get()
972 if self.port.get() == 0:
973 self.port.set(defaultports[proto])
974 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
976 def user_edit(self, username, mode):
977 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
979 def user_delete(self, username):
980 if self.subwidgets.has_key(username):
981 self.subwidgets[username].destruct()
982 del self.server[username]
984 def makeWidgets(self, host, mode):
985 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
987 leftwin = Frame(self);
991 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
992 Label(ctlwin, text="Run Controls").pack(side=TOP)
993 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
994 LabeledEntry(ctlwin, 'True name of ' + host + ':',
995 self.via, leftwidth).pack(side=TOP, fill=X)
996 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
997 self.interval, leftwidth).pack(side=TOP, fill=X)
998 LabeledEntry(ctlwin, 'Server timeout (seconds):',
999 self.timeout, leftwidth).pack(side=TOP, fill=X)
1000 Button(ctlwin, text='Help', fg='blue',
1001 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
1004 # Compute the available protocols from the compile-time options
1005 protolist = ['auto']
1006 if 'pop2' in feature_options:
1007 protolist.append("POP2")
1008 if 'pop3' in feature_options:
1009 protolist = protolist + ["POP3", "APOP", "KPOP"]
1010 if 'sdps' in feature_options:
1011 protolist.append("SDPS")
1012 if 'imap' in feature_options:
1013 protolist.append("IMAP")
1014 if 'imap-gss' in feature_options:
1015 protolist.append("IMAP-GSS")
1016 if 'imap-k4' in feature_options:
1017 protolist.append("IMAP-K4")
1018 if 'etrn' in feature_options:
1019 protolist.append("ETRN")
1021 protwin = Frame(leftwin, relief=RAISED, bd=5)
1022 Label(protwin, text="Protocol").pack(side=TOP)
1023 ButtonBar(protwin, '',
1024 self.protocol, protolist, 2,
1026 if mode != 'novice':
1027 LabeledEntry(protwin, 'On server TCP/IP port:',
1028 self.port, leftwidth).pack(side=TOP, fill=X)
1030 Checkbutton(protwin,
1031 text="POP3: track `seen' with client-side UIDLs?",
1032 variable=self.uidl).pack(side=TOP)
1033 Button(protwin, text='Probe for supported protocols', fg='blue',
1034 command=self.autoprobe).pack(side=LEFT)
1035 Button(protwin, text='Help', fg='blue',
1036 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1037 protwin.pack(fill=X)
1039 userwin = Frame(leftwin, relief=RAISED, bd=5)
1040 Label(userwin, text="User entries for " + host).pack(side=TOP)
1041 ListEdit("New user: ",
1042 map(lambda x: x.remote, self.server.users),
1043 lambda u, m=mode, s=self: s.user_edit(u, m),
1044 lambda u, s=self: s.user_delete(u),
1046 userwin.pack(fill=X)
1048 leftwin.pack(side=LEFT, anchor=N, fill=X);
1050 if mode != 'novice':
1051 rightwin = Frame(self);
1053 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1054 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1055 LabeledEntry(mdropwin, 'Envelope address header:',
1056 self.envelope, '22').pack(side=TOP, fill=X)
1057 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1058 self.envskip, '22').pack(side=TOP, fill=X)
1059 LabeledEntry(mdropwin, 'Name prefix to strip:',
1060 self.qvirtual, '22').pack(side=TOP, fill=X)
1061 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1062 variable=self.dns).pack(side=TOP)
1063 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1064 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1065 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1066 ListEdit("New domain: ",
1067 self.server.localdomains, None, None, mdropwin, multihelp)
1068 mdropwin.pack(fill=X)
1070 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1071 secwin = Frame(rightwin, relief=RAISED, bd=5)
1072 Label(secwin, text="Security").pack(side=TOP)
1073 # Don't actually let users set this. KPOP sets it implicitly
1074 # ButtonBar(secwin, 'Preauthorization mode:',
1075 # self.preauth, preauthlist, 1, None).pack(side=TOP)
1076 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1077 LabeledEntry(secwin, 'IP range to check before poll:',
1078 self.interface, leftwidth).pack(side=TOP, fill=X)
1079 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1080 LabeledEntry(secwin, 'Interface to monitor:',
1081 self.monitor, leftwidth).pack(side=TOP, fill=X)
1082 if 'netsec' in feature_options or 'netsec' in dictmembers:
1083 LabeledEntry(secwin, 'IPV6 security options:',
1084 self.netsec, leftwidth).pack(side=TOP, fill=X)
1085 Button(secwin, text='Help', fg='blue',
1086 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1089 rightwin.pack(side=LEFT, anchor=N);
1091 def autoprobe(self):
1092 # Note: this only handles case (1) near fetchmail.c:1032
1093 # We're assuming people smart enough to set up ssh tunneling
1094 # won't need autoprobing.
1096 realhost = self.server.via
1098 realhost = self.server.pollname
1100 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1101 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1103 sock.connect(realhost, port)
1104 greetline = sock.recv(1024)
1110 confwin = Toplevel()
1111 if greetline == None:
1112 title = "Autoprobe of " + realhost + " failed"
1114 Fetchmailconf didn't find any mailservers active.
1115 This could mean the host doesn't support any,
1116 or that your Internet connection is down, or
1117 that the host is so slow that the probe timed
1118 out before getting a response.
1122 # OK, now try to recognize potential problems
1124 if protocol == "POP2":
1125 warnings = warnings + """
1126 It appears you have somehow found a mailserver running only POP2.
1127 Congratulations. Have you considered a career in archaeology?
1129 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1130 Unless the first line of your fetchmail -V output includes the string "POP2",
1131 you'll have to build it from sources yourself with the configure
1132 switch --enable-POP2.
1136 ### POP3 servers start here
1138 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1139 warnings = warnings + """
1140 This appears to be an old version of the UC Davis POP server. These are
1141 dangerously unreliable (among other problems, they may drop your mailbox
1142 on the floor if your connection is interrupted during the session).
1144 It is strongly recommended that you find a better POP3 server. The fetchmail
1145 FAQ includes pointers to good ones.
1148 # Steve VanDevender <stevev@efn.org> writes:
1149 # The only system I have seen this happen with is cucipop-1.31
1150 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1151 # 2.x and probably quite a few other systems. It appears to be a
1152 # bug or bad interaction with the SunOS realloc() -- it turns out
1153 # that internally cucipop does allocate a certain data structure in
1154 # multiples of 16, using realloc() to bump it up to the next
1155 # multiple if it needs more.
1157 # The distinctive symptom is that when there are 16 messages in the
1158 # inbox, you can RETR and DELE all 16 messages successfully, but on
1159 # QUIT cucipop returns something like "-ERR Error locking your
1160 # mailbox" and aborts without updating it.
1162 # The cucipop banner looks like:
1164 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1166 if string.find(greetline, "Cubic Circle") > 0:
1167 warnings = warnings + """
1168 I see your server is running cucipop. Better make sure the server box
1169 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1170 under that version, and doesn't cope with the result gracefully. Newer
1171 SunOS and Solaris machines run cucipop OK.
1174 # The greeting line on the server known to be buggy is:
1175 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1177 if string.find(greetline, "FTGate") > 0:
1178 warnings = warnings + """
1179 This POP server has a weird bug; it says OK twice in response to TOP.
1180 Its response to RETR is normal, so use the `fetchall' option.
1183 if string.find(greetline, "OpenMail") > 0:
1184 warnings = warnings + """
1185 You appear to be using some version of HP OpenMail. Many versions of
1186 OpenMail do not process the "TOP" command correctly; the symptom is that
1187 only the header and first line of each message is retrieved. To work
1188 around this bug, turn on `fetchall' on all user entries associated with
1192 if string.find(greetline, "POP-Max") > 0:
1193 warnings = warnings + """
1194 The Mail Max POP3 server screws up on mail with attachments. It
1195 reports the message size with attachments included, but doesn't
1196 download them on a RETR or TOP (this violates the IMAP RFCs). It also
1197 doesn't implement TOP correctly. You should get rid of it -- and the
1198 brain-dead NT server it rode in on.
1201 if string.find(greetline, "POP3 Server Ready") > 0:
1202 warnings = warnings + """
1203 Some server that uses this greeting line has been observed to choke on
1204 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1207 if string.find(greetline, "QPOP") > 0:
1208 warnings = warnings + """
1209 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1210 knows all about qpopper. However, be aware that the 2.53 version of
1211 qpopper does something odd that causes fetchmail to hang with a socket
1212 error on very large messages. This is probably not a fetchmail bug, as
1213 it has been observed with fetchpop. The fix is to upgrade to qpopper
1214 3.0beta or a more recent version. Better yet, switch to IMAP.
1217 if string.find(greetline, " sprynet.com") > 0:
1218 warnings = warnings + """
1219 You appear to be using a SpryNet server. In mid-1999 it was reported that
1220 the SpryNet TOP command marks messages seen. Therefore, for proper error
1221 recovery in the event of a line drop, it is strongly recommended that you
1222 turn on `fetchall' on all user entries associated with this server.
1225 if string.find(greetline, "TEMS POP3") > 0:
1226 warnings = warnings + """
1227 Your POP3 server has "TEMS" in its header line. At least one such
1228 server does not process the "TOP" command correctly; the symptom is
1229 that fetchmail hangs when trying to retrieve mail. To work around
1230 this bug, turn on `fetchall' on all user entries associated with this
1234 if string.find(greetline, " spray.se") > 0:
1235 warnings = warnings + """
1236 Your POP3 server has "spray.se" in its header line. In May 2000 at
1237 least one such server did not process the "TOP" command correctly; the
1238 symptom is that messages are treated as headerless. To work around
1239 this bug, turn on `fetchall' on all user entries associated with this
1243 if string.find(greetline, " usa.net") > 0:
1244 warnings = warnings + """
1245 You appear to be using USA.NET's free mail service. Their POP3 servers
1246 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1247 fetchmail can compensate. They seem to require that fetchall be switched on
1248 (otherwise you won't necessarily see all your mail, not even new mail).
1249 They also botch the TOP command the fetchmail normally uses for retrieval
1250 (it only retrieves about 10 lines rather than the number specified).
1251 Turning on fetchall will disable the use of TOP.
1253 Therefore, it is strongly recommended that you turn on `fetchall' on all
1254 user entries associated with this server.
1257 if string.find(greetline, " Novonyx POP3") > 0:
1258 warnings = warnings + """
1259 Your mailserver is running Novonyx POP3. This server, at least as of
1260 version 2.17, seems to have problems handling and reporting seen bits.
1261 You may have to use the fetchall option.
1265 ### IMAP servers start here
1267 if string.find(greetline, "GroupWise") > 0:
1268 warnings = warnings + """
1269 The Novell GroupWise IMAP server would be better named GroupFoolish;
1270 it is (according to the designer of IMAP) unusably broken. Among
1271 other things, it doesn't include a required content length in its
1272 BODY[TEXT] response.<p>
1274 Fetchmail works around this problem, but we strongly recommend voting
1275 with your dollars for a server that isn't brain-dead. If you stick
1276 with code as shoddy as GroupWise seems to be, you will probably pay
1277 for it with other problems.<p>
1280 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1281 warnings = warnings + """
1282 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1283 set the messages's Seen flag. As a result, if you use the keep option the
1284 same messages will be downloaded over and over.
1287 if string.find(greetline, "InterChange") > 0:
1288 warnings = warnings + """
1289 The InterChange IMAP server screws up on mail with attachments. It
1290 doesn't fetch them if you give it a BODY[TEXT] request, though it
1291 does if you request RFC822.TEXT. According to the IMAP RFCs and their
1292 maintainer these should be equivalent -- and we can't drop the
1293 BODY[TEXT] form because M$ Exchange (quite legally under RFC2062)
1297 if string.find(greetline, "Imail") > 0:
1298 warnings = warnings + """
1299 We've seen a bug report indicating that this IMAP server (at least as of
1300 version 5.0.7) returns an invalid body size for messages with MIME
1301 attachments; the effect is to drop the attachments on the floor. We
1302 recommend you upgrade to a non-broken IMAP server.
1305 if string.find(greetline, "Domino IMAP4") > 0:
1306 warnings = warnings + """
1307 Your IMAP server appears to be Lotus Domino. This server, at least up
1308 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1309 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1310 will see attachments as part of the message text. If your Domino server's
1311 POP3 facility is enabled, we recommend you fall back on it.
1315 ### Checks for protocol variants start here
1317 closebrak = string.find(greetline, ">")
1318 if closebrak > 0 and greetline[closebrak+1] == "\r":
1319 warnings = warnings + """
1320 It looks like you could use APOP on this server and avoid sending it your
1321 password in clear. You should talk to the mailserver administrator about
1325 if string.find(greetline, "IMAP2bis") > 0:
1326 warnings = warnings + """
1327 IMAP2bis servers have a minor problem; they can't peek at messages without
1328 marking them seen. If you take a line hit during the retrieval, the
1329 interrupted message may get left on the server, marked seen.
1331 To work around this, it is recommended that you set the `fetchall'
1332 option on all user entries associated with this server, so any stuck
1333 mail will be retrieved next time around.
1335 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1336 a pointer to an open-source implementation.
1339 if string.find(greetline, "IMAP4rev1") > 0:
1340 warnings = warnings + """
1341 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1342 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1343 has therefore been extremely well tested with this class of server.
1347 warnings = warnings + """
1348 Fetchmail doesn't know anything special about this server type.
1352 # Display success window with warnings
1353 title = "Autoprobe of " + realhost + " succeeded"
1354 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1355 self.protocol.set(protocol)
1356 confwin.title(title)
1357 confwin.iconname(title)
1358 Label(confwin, text=title).pack()
1359 Message(confwin, text=confirm, width=600).pack()
1360 Button(confwin, text='Done',
1361 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1364 # User editing stuff
1368 'title' : 'User option help',
1369 'banner': 'User options',
1371 You may use this panel to set options
1372 that may differ between individual
1375 Once you have a user configuration set
1376 up as you like it, you can select `OK' to
1377 store it in the user list maintained in
1378 the site configuration window.
1380 If you wish to discard the changes you have
1381 made to user options, select `Quit'.
1385 'title' : 'Local name help',
1386 'banner': 'Local names',
1388 The local name(s) in a user entry are the
1389 people on the client machine who should
1390 receive mail from the poll described.
1392 Note: if a user entry has more than one
1393 local name, messages will be retrieved
1394 in multidrop mode. This complicates
1395 the configuration issues; see the manual
1396 page section on multidrop mode.
1399 class UserEdit(Frame, MyWidget):
1400 def __init__(self, username, parent):
1401 self.parent = parent
1403 for user in parent.server.users:
1404 if user.remote == username:
1406 if self.user == None:
1408 self.user.remote = username
1409 self.user.localnames = [username]
1410 parent.server.users.append(self.user)
1412 def edit(self, mode, master=None):
1413 Frame.__init__(self, master)
1415 self.master.title('Fetchmail user ' + self.user.remote
1416 + ' querying ' + self.parent.server.pollname);
1417 self.master.iconname('Fetchmail user ' + self.user.remote);
1418 self.post(User, 'user')
1419 self.makeWidgets(mode, self.parent.server.pollname)
1420 self.keepalive = [] # Use this to anchor the PhotoImage object
1421 make_icon_window(self, fetchmail_icon)
1424 # self.wait_window()
1428 # Yes, this test can fail -- if you delete the parent window.
1429 if self.parent.subwidgets.has_key(self.user.remote):
1430 del self.parent.subwidgets[self.user.remote]
1431 Widget.destroy(self.master)
1434 if ConfirmQuit(self, 'user option editing'):
1438 self.fetch(User, 'user')
1441 def makeWidgets(self, mode, servername):
1442 dispose_window(self,
1443 "User options for " + self.user.remote + " querying " + servername,
1446 if mode != 'novice':
1447 leftwin = Frame(self);
1451 secwin = Frame(leftwin, relief=RAISED, bd=5)
1452 Label(secwin, text="Authentication").pack(side=TOP)
1453 LabeledEntry(secwin, 'Password:',
1454 self.password, '12').pack(side=TOP, fill=X)
1455 secwin.pack(fill=X, anchor=N)
1457 if 'ssl' in feature_options or 'ssl' in dictmembers:
1458 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1459 Checkbutton(sslwin, text="Use SSL?",
1460 variable=self.ssl).pack(side=TOP, fill=X)
1461 LabeledEntry(sslwin, 'SSL key:',
1462 self.sslkey, '14').pack(side=TOP, fill=X)
1463 LabeledEntry(sslwin, 'SSL certificate:',
1464 self.sslcert, '14').pack(side=TOP, fill=X)
1465 sslwin.pack(fill=X, anchor=N)
1467 names = Frame(leftwin, relief=RAISED, bd=5)
1468 Label(names, text="Local names").pack(side=TOP)
1469 ListEdit("New name: ",
1470 self.user.localnames, None, None, names, localhelp)
1471 names.pack(fill=X, anchor=N)
1473 if mode != 'novice':
1474 targwin = Frame(leftwin, relief=RAISED, bd=5)
1475 Label(targwin, text="Forwarding Options").pack(side=TOP)
1476 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1477 ListEdit("New listener:",
1478 self.user.smtphunt, None, None, targwin, None)
1479 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1480 self.smtpaddress, '26').pack(side=TOP, fill=X)
1481 LabeledEntry(targwin, 'Connection setup command:',
1482 self.preconnect, '26').pack(side=TOP, fill=X)
1483 LabeledEntry(targwin, 'Connection wrapup command:',
1484 self.postconnect, '26').pack(side=TOP, fill=X)
1485 LabeledEntry(targwin, 'Local delivery agent:',
1486 self.mda, '26').pack(side=TOP, fill=X)
1487 LabeledEntry(targwin, 'BSMTP output file:',
1488 self.bsmtp, '26').pack(side=TOP, fill=X)
1489 LabeledEntry(targwin, 'Listener spam-block codes:',
1490 self.antispam, '26').pack(side=TOP, fill=X)
1491 LabeledEntry(targwin, 'Pass-through properties:',
1492 self.properties, '26').pack(side=TOP, fill=X)
1493 Checkbutton(targwin, text="Use LMTP?",
1494 variable=self.lmtp).pack(side=TOP, fill=X)
1495 targwin.pack(fill=X, anchor=N)
1497 if mode != 'novice':
1498 leftwin.pack(side=LEFT, fill=X, anchor=N)
1499 rightwin = Frame(self)
1503 optwin = Frame(rightwin, relief=RAISED, bd=5)
1504 Label(optwin, text="Processing Options").pack(side=TOP)
1505 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1506 variable=self.keep).pack(side=TOP, anchor=W)
1507 Checkbutton(optwin, text="Fetch old messages as well as new",
1508 variable=self.fetchall).pack(side=TOP, anchor=W)
1509 if mode != 'novice':
1510 Checkbutton(optwin, text="Flush seen messages before retrieval",
1511 variable=self.flush).pack(side=TOP, anchor=W)
1512 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1513 variable=self.rewrite).pack(side=TOP, anchor=W)
1514 Checkbutton(optwin, text="Force CR/LF at end of each line",
1515 variable=self.forcecr).pack(side=TOP, anchor=W)
1516 Checkbutton(optwin, text="Strip CR from end of each line",
1517 variable=self.stripcr).pack(side=TOP, anchor=W)
1518 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1519 variable=self.pass8bits).pack(side=TOP, anchor=W)
1520 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1521 variable=self.mimedecode).pack(side=TOP, anchor=W)
1522 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1523 variable=self.dropstatus).pack(side=TOP, anchor=W)
1524 Checkbutton(optwin, text="Drop Delivered-To lines from forwarded messages",
1525 variable=self.dropdelivered).pack(side=TOP, anchor=W)
1528 if mode != 'novice':
1529 limwin = Frame(rightwin, relief=RAISED, bd=5)
1530 Label(limwin, text="Resource Limits").pack(side=TOP)
1531 LabeledEntry(limwin, 'Message size limit:',
1532 self.limit, '30').pack(side=TOP, fill=X)
1533 LabeledEntry(limwin, 'Size warning interval:',
1534 self.warnings, '30').pack(side=TOP, fill=X)
1535 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1536 self.fetchlimit, '30').pack(side=TOP, fill=X)
1537 LabeledEntry(limwin, 'Max messages to forward per poll:',
1538 self.batchlimit, '30').pack(side=TOP, fill=X)
1539 if self.parent.server.protocol != 'ETRN':
1540 LabeledEntry(limwin, 'Interval between expunges:',
1541 self.expunge, '30').pack(side=TOP, fill=X)
1542 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1543 variable=self.idle).pack(side=TOP, anchor=W)
1546 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1547 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1548 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1549 ListEdit("New folder:", self.user.mailboxes,
1550 None, None, foldwin, None)
1551 foldwin.pack(fill=X, anchor=N)
1553 if mode != 'novice':
1554 rightwin.pack(side=LEFT)
1560 # Top-level window that offers either novice or expert mode
1561 # (but not both at once; it disappears when one is selected).
1564 class Configurator(Frame):
1565 def __init__(self, outfile, master, onexit, parent):
1566 Frame.__init__(self, master)
1567 self.outfile = outfile
1568 self.onexit = onexit
1569 self.parent = parent
1570 self.master.title('fetchmail configurator');
1571 self.master.iconname('fetchmail configurator');
1573 self.keepalive = [] # Use this to anchor the PhotoImage object
1574 make_icon_window(self, fetchmail_icon)
1576 Message(self, text="""
1577 Use `Novice Configuration' for basic fetchmail setup;
1578 with this, you can easily set up a single-drop connection
1579 to one remote mail server.
1580 """, width=600).pack(side=TOP)
1581 Button(self, text='Novice Configuration',
1582 fg='blue', command=self.novice).pack()
1584 Message(self, text="""
1585 Use `Expert Configuration' for advanced fetchmail setup,
1586 including multiple-site or multidrop connections.
1587 """, width=600).pack(side=TOP)
1588 Button(self, text='Expert Configuration',
1589 fg='blue', command=self.expert).pack()
1591 Message(self, text="""
1592 Or you can just select `Quit' to leave the configurator now and
1593 return to the main panel.
1594 """, width=600).pack(side=TOP)
1595 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1596 master.protocol("WM_DELETE_WINDOW", self.leave)
1599 self.master.destroy()
1600 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1603 self.master.destroy()
1604 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1607 self.master.destroy()
1610 # Run a command in a scrolling text widget, displaying its output
1612 class RunWindow(Frame):
1613 def __init__(self, command, master, parent):
1614 Frame.__init__(self, master)
1615 self.master = master
1616 self.master.title('fetchmail run window');
1617 self.master.iconname('fetchmail run window');
1620 text="Running "+command,
1621 bd=2).pack(side=TOP, pady=10)
1622 self.keepalive = [] # Use this to anchor the PhotoImage object
1623 make_icon_window(self, fetchmail_icon)
1625 # This is a scrolling text window
1626 textframe = Frame(self)
1627 scroll = Scrollbar(textframe)
1628 self.textwidget = Text(textframe, setgrid=TRUE)
1629 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1630 self.textwidget.config(yscrollcommand=scroll.set)
1631 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1632 scroll.config(command=self.textwidget.yview)
1633 scroll.pack(side=RIGHT, fill=BOTH)
1634 textframe.pack(side=TOP)
1636 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1638 self.update() # Draw widget before executing fetchmail
1640 child_stdout = os.popen(command + " 2>&1", "r")
1642 ch = child_stdout.read(1)
1645 self.textwidget.insert(END, ch)
1646 self.textwidget.insert(END, "Done.")
1647 self.textwidget.see(END);
1650 Widget.destroy(self.master)
1652 # Here's where we choose either configuration or launching
1654 class MainWindow(Frame):
1655 def __init__(self, outfile, master=None):
1656 Frame.__init__(self, master)
1657 self.outfile = outfile
1658 self.master.title('fetchmail launcher');
1659 self.master.iconname('fetchmail launcher');
1662 text='Fetchmailconf ' + version,
1663 bd=2).pack(side=TOP, pady=10)
1664 self.keepalive = [] # Use this to anchor the PhotoImage object
1665 make_icon_window(self, fetchmail_icon)
1668 ## Test icon display with the following:
1669 # icon_image = PhotoImage(data=fetchmail_icon)
1670 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1671 # self.keepalive.append(icon_image)
1673 Message(self, text="""
1674 Use `Configure fetchmail' to tell fetchmail about the remote
1675 servers it should poll (the host name, your username there,
1676 whether to use POP or IMAP, and so forth).
1677 """, width=600).pack(side=TOP)
1678 self.configbutton = Button(self, text='Configure fetchmail',
1679 fg='blue', command=self.configure)
1680 self.configbutton.pack()
1682 Message(self, text="""
1683 Use `Test fetchmail' to run fetchmail with debugging enabled.
1684 This is a good way to test out a new configuration.
1685 """, width=600).pack(side=TOP)
1686 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1688 Message(self, text="""
1689 Use `Run fetchmail' to run fetchmail in foreground.
1690 Progress messages will be shown, but not debug messages.
1691 """, width=600).pack(side=TOP)
1692 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1694 Message(self, text="""
1695 Or you can just select `Quit' to exit the launcher now.
1696 """, width=600).pack(side=TOP)
1697 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1699 def configure(self):
1700 self.configbutton.configure(state=DISABLED)
1701 Configurator(self.outfile, Toplevel(),
1702 lambda self=self: self.configbutton.configure(state=NORMAL),
1705 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1708 RunWindow("fetchmail -d0", Toplevel(), self)
1713 # Functions for turning a dictionary into an instantiated object tree.
1715 def intersect(list1, list2):
1716 # Compute set intersection of lists
1723 def setdiff(list1, list2):
1724 # Compute set difference of lists
1731 def copy_instance(toclass, fromdict):
1732 # Initialize a class object of given type from a conformant dictionary.
1733 for fld in fromdict.keys():
1734 if not fld in dictmembers:
1735 dictmembers.append(fld)
1736 # The `optional' fields are the ones we can ignore for purposes of
1737 # conformability checking; they'll still get copied if they are
1738 # present in the dictionary.
1739 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1740 class_sig = setdiff(toclass.__dict__.keys(), optional)
1742 dict_keys = setdiff(fromdict.keys(), optional)
1744 common = intersect(class_sig, dict_keys)
1745 if 'typemap' in class_sig:
1746 class_sig.remove('typemap')
1747 if tuple(class_sig) != tuple(dict_keys):
1748 print "Fields don't match what fetchmailconf expected:"
1749 # print "Class signature: " + `class_sig`
1750 # print "Dictionary keys: " + `dict_keys`
1751 diff = setdiff(class_sig, common)
1753 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1754 diff = setdiff(dict_keys, common)
1756 print "Not matched in dictionary keys: " + `diff`
1759 for x in fromdict.keys():
1760 setattr(toclass, x, fromdict[x])
1763 # And this is the main sequence. How it works:
1765 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1766 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1767 # Run execfile on the file to pull fetchmailrc into Python global space.
1768 # You don't want static data, though; you want, instead, a tree of objects
1769 # with the same data members and added appropriate methods.
1771 # This is what the copy_instance function() is for. It tries to copy a
1772 # dictionary field by field into a class, aborting if the class and dictionary
1773 # have different data members (except for any typemap member in the class;
1774 # that one is strictly for use by the MyWidget supperclass).
1776 # Once the object tree is set up, require user to choose novice or expert
1777 # mode and instantiate an edit object for the configuration. Class methods
1778 # will take it all from there.
1780 # Options (not documented because they're for fetchmailconf debuggers only):
1781 # -d: Read the configuration and dump it to stdout before editing. Dump
1782 # the edited result to stdout as well.
1783 # -f: specify the run control file to read.
1785 if __name__ == '__main__':
1787 if not os.environ.has_key("DISPLAY"):
1788 print "fetchmailconf must be run under X"
1791 fetchmail_icon = """
1792 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1793 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1794 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1795 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1796 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1797 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1798 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1799 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1800 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1801 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1802 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1803 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1804 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1805 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1806 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1807 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1808 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1809 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1810 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1811 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1812 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1813 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1814 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1815 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1816 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1817 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1818 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1819 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1820 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1821 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1822 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1823 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1825 # The base64 data in the string above was generated by the following procedure:
1828 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1832 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1833 dump = rcfile = None;
1834 for (switch, val) in options:
1835 if (switch == '-d'):
1837 elif (switch == '-f'):
1840 # Get client host's FQDN
1841 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1844 ConfigurationDefaults = Configuration()
1845 ServerDefaults = Server()
1846 UserDefaults = User()
1848 # Read the existing configuration. We set the umask to 077 to make sure
1849 # that group & other read/write permissions are shut off -- we wouldn't
1850 # want crackers to snoop password information out of the tempfile.
1851 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1853 cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1855 cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
1860 print "`" + cmd + "' run failure, status " + `s`
1863 print "Unknown error while running fetchmail --configdump"
1870 print "Can't read configuration output of fetchmail --configdump."
1876 # The tricky part -- initializing objects from the configuration global
1877 # `Configuration' is the top level of the object tree we're going to mung.
1878 # The dictmembers list is used to track the set of fields the dictionary
1879 # contains; in particular, we can use it to tell whether things like the
1880 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1882 Fetchmailrc = Configuration()
1883 copy_instance(Fetchmailrc, fetchmailrc)
1884 Fetchmailrc.servers = [];
1885 for server in fetchmailrc['servers']:
1887 copy_instance(Newsite, server)
1888 Fetchmailrc.servers.append(Newsite)
1890 for user in server['users']:
1892 copy_instance(Newuser, user)
1893 Newsite.users.append(Newuser)
1895 # We may want to display the configuration and quit
1897 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1899 # The theory here is that -f alone sets the rcfile location,
1900 # but -d and -f together mean the new configuration should go to stdout.
1901 if not rcfile and not dump:
1902 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1904 # OK, now run the configuration edit
1905 root = MainWindow(rcfile)
1908 # The following sets edit modes for GNU EMACS