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.idle = FALSE # IDLE after poll
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 = 0 # Interval between expunges (IMAP)
225 self.ssl = 0 # Enable Seccure Socket Layer
226 self.sslkey = None # SSL key filename
227 self.sslcert = None # SSL certificate filename
228 self.properties = None # Extension properties
230 ('remote', 'String'),
231 # leave out mailboxes and localnames
232 ('password', 'String'),
234 ('smtpaddress', 'String'),
235 ('preconnect', 'String'),
236 ('postconnect', 'String'),
240 ('antispam', 'String'),
242 ('flush', 'Boolean'),
243 ('fetchall', 'Boolean'),
244 ('rewrite', 'Boolean'),
245 ('forcecr', 'Boolean'),
246 ('stripcr', 'Boolean'),
247 ('pass8bits', 'Boolean'),
248 ('mimedecode', 'Boolean'),
249 ('dropstatus', 'Boolean'),
253 ('fetchlimit', 'Int'),
254 ('batchlimit', 'Int'),
257 ('sslkey', 'String'),
258 ('sslcert', 'String'),
259 ('properties', 'String'))
263 res = res + "user " + str(self.remote) + " there ";
265 res = res + "with password " + str(self.password) + " "
268 for x in self.localnames:
271 if (self.keep != UserDefaults.keep
272 or self.flush != UserDefaults.flush
273 or self.fetchall != UserDefaults.fetchall
274 or self.rewrite != UserDefaults.rewrite
275 or self.forcecr != UserDefaults.forcecr
276 or self.stripcr != UserDefaults.stripcr
277 or self.pass8bits != UserDefaults.pass8bits
278 or self.mimedecode != UserDefaults.mimedecode
279 or self.dropstatus != UserDefaults.dropstatus
280 or self.idle != UserDefaults.idle):
281 res = res + " options"
282 if self.keep != UserDefaults.keep:
283 res = res + flag2str(self.keep, 'keep')
284 if self.flush != UserDefaults.flush:
285 res = res + flag2str(self.flush, 'flush')
286 if self.fetchall != UserDefaults.fetchall:
287 res = res + flag2str(self.fetchall, 'fetchall')
288 if self.rewrite != UserDefaults.rewrite:
289 res = res + flag2str(self.rewrite, 'rewrite')
290 if self.forcecr != UserDefaults.forcecr:
291 res = res + flag2str(self.forcecr, 'forcecr')
292 if self.stripcr != UserDefaults.stripcr:
293 res = res + flag2str(self.stripcr, 'stripcr')
294 if self.pass8bits != UserDefaults.pass8bits:
295 res = res + flag2str(self.pass8bits, 'pass8bits')
296 if self.mimedecode != UserDefaults.mimedecode:
297 res = res + flag2str(self.mimedecode, 'mimedecode')
298 if self.dropstatus != UserDefaults.dropstatus:
299 res = res + flag2str(self.dropstatus, 'dropstatus')
300 if self.idle != UserDefaults.idle:
301 res = res + flag2str(self.idle, 'idle')
302 if self.limit != UserDefaults.limit:
303 res = res + " limit " + `self.limit`
304 if self.warnings != UserDefaults.warnings:
305 res = res + " warnings " + `self.warnings`
306 if self.fetchlimit != UserDefaults.fetchlimit:
307 res = res + " fetchlimit " + `self.fetchlimit`
308 if self.batchlimit != UserDefaults.batchlimit:
309 res = res + " batchlimit " + `self.batchlimit`
310 if self.ssl and self.ssl != UserDefaults.ssl:
311 res = res + flag2str(self.ssl, 'ssl')
312 if self.sslkey and self.sslkey != UserDefaults.sslkey:
313 res = res + " sslkey " + `self.sslkey`
314 if self.sslcert and self.sslcert != UserDefaults.sslcert:
315 res = res + " ssl " + `self.sslcert`
316 if self.expunge != UserDefaults.expunge:
317 res = res + " expunge " + `self.expunge`
319 trimmed = self.smtphunt;
320 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
321 trimmed = trimmed[0:len(trimmed) - 1]
322 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
323 trimmed = trimmed[0:len(trimmed) - 1]
325 res = res + " smtphost "
330 res = res + " folder"
331 for x in self.mailboxes:
334 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
335 if getattr(self, fld):
336 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
337 if self.lmtp != UserDefaults.lmtp:
338 res = res + flag2str(self.lmtp, 'lmtp')
339 if self.antispam != UserDefaults.antispam:
340 res = res + " antispam " + self.antispam + "\n"
344 return "[User: " + repr(self) + "]"
350 defaultports = {"auto":0,
360 preauthlist = ("password", "kerberos", "ssh")
363 'title' : 'List Selection Help',
364 'banner': 'List Selection',
366 You must select an item in the list box (by clicking on it).
369 def flag2str(value, string):
370 # make a string representation of a .fetchmailrc flag or negated flag
374 if value == FALSE: str = str + ("no ")
378 class LabeledEntry(Frame):
379 # widget consisting of entry field with caption to left
380 def bind(self, key, action):
381 self.E.bind(key, action)
384 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
385 Frame.__init__(self, Master)
386 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
387 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
388 self.L.pack({'side':'left'})
389 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
391 def ButtonBar(frame, legend, ref, alternatives, depth, command):
392 # array of radio buttons, caption to left, picking from a string list
394 width = len(alternatives) / depth;
395 Label(bar, text=legend).pack(side=LEFT)
396 for column in range(width):
397 subframe = Frame(bar)
398 for row in range(depth):
399 ind = width * row + column
400 Radiobutton(subframe,
401 {'text':alternatives[ind],
403 'value':alternatives[ind],
404 'command':command}).pack(side=TOP, anchor=W)
405 subframe.pack(side=LEFT)
409 def helpwin(helpdict):
410 # help message window with a self-destruct button
412 helpwin.title(helpdict['title'])
413 helpwin.iconname(helpdict['title'])
414 Label(helpwin, text=helpdict['banner']).pack()
415 textframe = Frame(helpwin)
416 scroll = Scrollbar(textframe)
417 helpwin.textwidget = Text(textframe, setgrid=TRUE)
418 textframe.pack(side=TOP, expand=YES, fill=BOTH)
419 helpwin.textwidget.config(yscrollcommand=scroll.set)
420 helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
421 scroll.config(command=helpwin.textwidget.yview)
422 scroll.pack(side=RIGHT, fill=BOTH)
423 helpwin.textwidget.insert(END, helpdict['text']);
424 Button(helpwin, text='Done',
425 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
426 textframe.pack(side=TOP)
428 def make_icon_window(base, image):
430 # Some older pythons will error out on this
431 icon_image = PhotoImage(data=image)
432 icon_window = Toplevel()
433 Label(icon_window, image=icon_image, bg='black').pack()
434 base.master.iconwindow(icon_window)
435 # Avoid TkInter brain death. PhotoImage objects go out of
436 # scope when the enclosing function returns. Therefore
437 # we have to explicitly link them to something.
438 base.keepalive.append(icon_image)
442 class ListEdit(Frame):
443 # edit a list of values (duplicates not allowed) with a supplied editor hook
444 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
446 self.deletor = deletor
449 # Set up a widget to accept new elements
450 self.newval = StringVar(master)
451 newwin = LabeledEntry(master, newlegend, self.newval, '12')
452 newwin.bind('<Double-1>', self.handleNew)
453 newwin.bind('<Return>', self.handleNew)
454 newwin.pack(side=TOP, fill=X, anchor=E)
456 # Edit the existing list
457 listframe = Frame(master)
458 scroll = Scrollbar(listframe)
459 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
462 self.listwidget.insert(END, x)
463 listframe.pack(side=TOP, expand=YES, fill=BOTH)
464 self.listwidget.config(yscrollcommand=scroll.set)
465 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
466 scroll.config(command=self.listwidget.yview)
467 scroll.pack(side=RIGHT, fill=BOTH)
468 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
469 self.listwidget.bind('<Double-1>', self.handleList);
470 self.listwidget.bind('<Return>', self.handleList);
474 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
475 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
477 self.helptxt = helptxt
478 Button(bf, text='Help', fg='blue',
479 command=self.help).pack(side=RIGHT)
483 helpwin(self.helptxt)
485 def handleList(self, event):
488 def handleNew(self, event):
489 item = self.newval.get()
491 entire = self.listwidget.get(0, self.listwidget.index('end'));
492 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
493 self.listwidget.insert('end', item)
494 if self.list != None: self.list.append(item)
496 apply(self.editor, (item,))
500 select = self.listwidget.curselection()
505 if index and self.editor:
506 label = self.listwidget.get(index);
508 apply(self.editor, (label,))
510 def deleteItem(self):
511 select = self.listwidget.curselection()
515 index = string.atoi(select[0])
516 label = self.listwidget.get(index);
517 self.listwidget.delete(index)
518 if self.list != None:
520 if self.deletor != None:
521 apply(self.deletor, (label,))
523 def ConfirmQuit(frame, context):
526 text = 'Really quit ' + context + ' without saving?',
528 strings = ('Yes', 'No'),
532 def dispose_window(master, legend, help, savelegend='OK'):
533 dispose = Frame(master, relief=RAISED, bd=5)
534 Label(dispose, text=legend).pack(side=TOP,pady=10)
535 Button(dispose, text=savelegend, fg='blue',
536 command=master.save).pack(side=LEFT)
537 Button(dispose, text='Quit', fg='blue',
538 command=master.nosave).pack(side=LEFT)
539 Button(dispose, text='Help', fg='blue',
540 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
545 # Common methods for Tkinter widgets -- deals with Tkinter declaration
546 def post(self, widgetclass, field):
547 for x in widgetclass.typemap:
548 if x[1] == 'Boolean':
549 setattr(self, x[0], BooleanVar(self))
550 elif x[1] == 'String':
551 setattr(self, x[0], StringVar(self))
553 setattr(self, x[0], IntVar(self))
554 source = getattr(getattr(self, field), x[0])
556 getattr(self, x[0]).set(source)
558 def fetch(self, widgetclass, field):
559 for x in widgetclass.typemap:
560 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
563 # First, code to set the global fetchmail run controls.
566 configure_novice_help = {
567 'title' : 'Fetchmail novice configurator help',
568 'banner': 'Novice configurator help',
570 In the `Novice Configurator Controls' panel, you can:
572 Press `Save' to save the new fetchmail configuration you have created.
573 Press `Quit' to exit without saving.
574 Press `Help' to bring up this help message.
576 In the `Novice Configuration' panels, you will set up the basic data
577 needed to create a simple fetchmail setup. These include:
579 1. The name of the remote site you want to query.
581 2. Your login name on that site.
583 3. Your password on that site.
585 4. A protocol to use (POP, IMAP, ETRN, etc.)
587 5. A polling interval.
589 6. Options to fetch old messages as well as new, uor to suppress
590 deletion of fetched message.
592 The novice-configuration code will assume that you want to forward mail
593 to a local sendmail listener with no special options.
596 configure_expert_help = {
597 'title' : 'Fetchmail expert configurator help',
598 'banner': 'Expert configurator help',
600 In the `Expert Configurator Controls' panel, you can:
602 Press `Save' to save the new fetchmail configuration you have edited.
603 Press `Quit' to exit without saving.
604 Press `Help' to bring up this help message.
606 In the `Run Controls' panel, you can set the following options that
607 control how fetchmail runs:
610 Number of seconds to wait between polls in the background.
611 If zero, fetchmail will run in foreground.
614 If empty, emit progress and error messages to stderr.
615 Otherwise this gives the name of the files to write to.
616 This field is ignored if the "Log to syslog?" option is on.
619 If empty, store seen-message IDs in .fetchids under user's home
620 directory. If nonempty, use given file name.
623 Who to send multidrop mail to as a last resort if no address can
624 be matched. Normally empty; in this case, fetchmail treats the
625 invoking user as the address of last resort unless that user is
626 root. If that user is root, fetchmail sends to `postmaster'.
629 If this option is on (the default) error mail goes to the sender.
630 Otherwise it goes to the postmaster.
633 If false (the default) fetchmail generates a Received line into
634 each message and generates a HELO from the machine it is running on.
635 If true, fetchmail generates no Received line and HELOs as if it were
638 In the `Remote Mail Configurations' panel, you can:
640 1. Enter the name of a new remote mail server you want fetchmail to query.
642 To do this, simply enter a label for the poll configuration in the
643 `New Server:' box. The label should be a DNS name of the server (unless
644 you are using ssh or some other tunneling method and will fill in the `via'
645 option on the site configuration screen).
647 2. Change the configuration of an existing site.
649 To do this, find the site's label in the listbox and double-click it.
650 This will take you to a site configuration dialogue.
654 class ConfigurationEdit(Frame, MyWidget):
655 def __init__(self, configuration, outfile, master, onexit):
657 self.configuration = configuration
658 self.outfile = outfile
659 self.container = master
661 ConfigurationEdit.mode_to_help = {
662 'novice':configure_novice_help, 'expert':configure_expert_help
665 def server_edit(self, sitename):
666 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
668 def server_delete(self, sitename):
670 for user in self.subwidgets.keys():
672 del self.configuration[sitename]
676 def edit(self, mode):
678 Frame.__init__(self, self.container)
679 self.master.title('fetchmail ' + self.mode + ' configurator');
680 self.master.iconname('fetchmail ' + self.mode + ' configurator');
681 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
682 self.keepalive = [] # Use this to anchor the PhotoImage object
683 make_icon_window(self, fetchmail_icon)
685 self.post(Configuration, 'configuration')
688 'Configurator ' + self.mode + ' Controls',
689 ConfigurationEdit.mode_to_help[self.mode],
692 gf = Frame(self, relief=RAISED, bd = 5)
694 text='Fetchmail Run Controls',
695 bd=2).pack(side=TOP, pady=10)
700 if self.mode != 'novice':
702 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
703 log.pack(side=RIGHT, anchor=E)
705 # Set the poll interval
706 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
707 de.pack(side=RIGHT, anchor=E)
712 if self.mode != 'novice':
715 {'text':'Bounces to sender?',
716 'variable':self.bouncemail,
717 'relief':GROOVE}).pack(side=LEFT, anchor=W)
722 {'text':'Log to syslog?',
723 'variable':self.syslog,
724 'relief':GROOVE}).pack(side=LEFT, anchor=W)
725 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
726 log.pack(side=RIGHT, anchor=E)
730 {'text':'Invisible mode?',
731 'variable':self.invisible,
732 'relief':GROOVE}).pack(side=LEFT, anchor=W)
734 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
735 log.pack(side=RIGHT, anchor=E)
739 # Expert mode allows us to edit multiple sites
740 lf = Frame(self, relief=RAISED, bd=5)
742 text='Remote Mail Server Configurations',
743 bd=2).pack(side=TOP, pady=10)
744 ListEdit('New Server:',
745 map(lambda x: x.pollname, self.configuration.servers),
746 lambda site, self=self: self.server_edit(site),
747 lambda site, self=self: self.server_delete(site),
752 for sitename in self.subwidgets.keys():
753 self.subwidgets[sitename].destruct()
754 self.master.destroy()
758 if ConfirmQuit(self, self.mode + " configuration editor"):
762 for sitename in self.subwidgets.keys():
763 self.subwidgets[sitename].save()
764 self.fetch(Configuration, 'configuration')
768 elif not os.path.isfile(self.outfile) or Dialog(self,
769 title = 'Overwrite existing run control file?',
770 text = 'Really overwrite existing run control file?',
772 strings = ('Yes', 'No'),
773 default = 1).num == 0:
774 fm = open(self.outfile, 'w')
776 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
777 fm.write(`self.configuration`)
781 os.chmod(self.outfile, 0600)
785 # Server editing stuff.
788 'title' : 'Remote site help',
789 'banner': 'Remote sites',
791 When you add a site name to the list here,
792 you initialize an entry telling fetchmail
793 how to poll a new site.
795 When you select a sitename (by double-
796 clicking it, or by single-clicking to
797 select and then clicking the Edit button),
798 you will open a window to configure that
803 'title' : 'Server options help',
804 'banner': 'Server Options',
806 The server options screen controls fetchmail
807 options that apply to one of your mailservers.
809 Once you have a mailserver configuration set
810 up as you like it, you can select `OK' to
811 store it in the server list maintained in
812 the main configuration window.
814 If you wish to discard changes to a server
815 configuration, select `Quit'.
819 'title' : 'Run Control help',
820 'banner': 'Run Controls',
822 If the `Poll normally' checkbox is on, the host is polled as part of
823 the normal operation of fetchmail when it is run with no arguments.
824 If it is off, fetchmail will only query this host when it is given as
825 a command-line argument.
827 The `True name of server' box should specify the actual DNS name
828 to query. By default this is the same as the poll name.
830 Normally each host described in the file is queried once each
831 poll cycle. If `Cycles to skip between polls' is greater than 0,
832 that's the number of poll cycles that are skipped between the
833 times this post is actually polled.
835 The `Server timeout' is the number of seconds fetchmail will wait
836 for a reply from the mailserver before concluding it is hung and
841 'title' : 'Protocol and Port help',
842 'banner': 'Protocol and Port',
844 These options control the remote-mail protocol
845 and TCP/IP service port used to query this
848 If you click the `Probe for supported protocols'
849 button, fetchmail will try to find you the most
850 capable server on the selected host (this will
851 only work if you're conncted to the Internet).
852 The probe only checks for ordinary IMAP and POP
853 protocols; fortunately these are the most
854 frequently supported.
856 The `Protocol' button bar offers you a choice of
857 all the different protocols available. The `auto'
858 protocol is the default mode; it probes the host
859 ports for POP3 and IMAP to see if either is
862 Normally the TCP/IP service port to use is
863 dictated by the protocol choice. The `Port'
864 field (only present in expert mode) lets you
865 set a non-standard port.
869 'title' : 'Security option help',
870 'banner': 'Security',
872 The `interface' option allows you to specify a range
873 of IP addresses to monitor for activity. If these
874 addresses are not active, fetchmail will not poll.
875 Specifying this may protect you from a spoofing attack
876 if your client machine has more than one IP gateway
877 address and some of the gateways are to insecure nets.
879 The `monitor' option, if given, specifies the only
880 device through which fetchmail is permitted to connect
881 to servers. This option may be used to prevent
882 fetchmail from triggering an expensive dial-out if the
883 interface is not already active.
885 The `interface' and `monitor' options are available
886 only for Linux and freeBSD systems. See the fetchmail
887 manual page for details on these.
889 The ssl option enables SSL communication with a mailserver
890 supporting Secure Sockets Layer. The sslkey and sslcert options
891 declare key and certificate files for use with SSL.
893 The `netsec' option will be configurable only if fetchmail
894 was compiled with IPV6 support. If you need to use it,
895 you probably know what to do.
899 'title' : 'Multidrop option help',
900 'banner': 'Multidrop',
902 These options are only useful with multidrop mode.
903 See the manual page for extended discussion.
907 'title' : 'User list help',
908 'banner': 'User list',
910 When you add a user name to the list here,
911 you initialize an entry telling fetchmail
912 to poll the site on behalf of the new user.
914 When you select a username (by double-
915 clicking it, or by single-clicking to
916 select and then clicking the Edit button),
917 you will open a window to configure the
918 user's options on that site.
921 class ServerEdit(Frame, MyWidget):
922 def __init__(self, host, parent):
926 for site in parent.configuration.servers:
927 if site.pollname == host:
929 if (self.server == None):
930 self.server = Server()
931 self.server.pollname = host
932 self.server.via = None
933 parent.configuration.servers.append(self.server)
935 def edit(self, mode, master=None):
936 Frame.__init__(self, master)
938 self.master.title('Fetchmail host ' + self.server.pollname);
939 self.master.iconname('Fetchmail host ' + self.server.pollname);
940 self.post(Server, 'server')
941 self.makeWidgets(self.server.pollname, mode)
942 self.keepalive = [] # Use this to anchor the PhotoImage object
943 make_icon_window(self, fetchmail_icon)
950 for username in self.subwidgets.keys():
951 self.subwidgets[username].destruct()
952 del self.parent.subwidgets[self.server.pollname]
953 Widget.destroy(self.master)
956 if ConfirmQuit(self, 'server option editing'):
960 self.fetch(Server, 'server')
961 for username in self.subwidgets.keys():
962 self.subwidgets[username].save()
965 def refreshPort(self):
966 proto = self.protocol.get()
967 if self.port.get() == 0:
968 self.port.set(defaultports[proto])
969 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
971 def user_edit(self, username, mode):
972 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
974 def user_delete(self, username):
975 if self.subwidgets.has_key(username):
976 self.subwidgets[username].destruct()
977 del self.server[username]
979 def makeWidgets(self, host, mode):
980 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
982 leftwin = Frame(self);
986 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
987 Label(ctlwin, text="Run Controls").pack(side=TOP)
988 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
989 LabeledEntry(ctlwin, 'True name of ' + host + ':',
990 self.via, leftwidth).pack(side=TOP, fill=X)
991 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
992 self.interval, leftwidth).pack(side=TOP, fill=X)
993 LabeledEntry(ctlwin, 'Server timeout (seconds):',
994 self.timeout, leftwidth).pack(side=TOP, fill=X)
995 Button(ctlwin, text='Help', fg='blue',
996 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
999 # Compute the available protocols from the compile-time options
1000 protolist = ['auto']
1001 if 'pop2' in feature_options:
1002 protolist.append("POP2")
1003 if 'pop3' in feature_options:
1004 protolist = protolist + ["POP3", "APOP", "KPOP"]
1005 if 'sdps' in feature_options:
1006 protolist.append("SDPS")
1007 if 'imap' in feature_options:
1008 protolist.append("IMAP")
1009 if 'imap-gss' in feature_options:
1010 protolist.append("IMAP-GSS")
1011 if 'imap-k4' in feature_options:
1012 protolist.append("IMAP-K4")
1013 if 'etrn' in feature_options:
1014 protolist.append("ETRN")
1016 protwin = Frame(leftwin, relief=RAISED, bd=5)
1017 Label(protwin, text="Protocol").pack(side=TOP)
1018 ButtonBar(protwin, '',
1019 self.protocol, protolist, 2,
1021 if mode != 'novice':
1022 LabeledEntry(protwin, 'On server TCP/IP port:',
1023 self.port, leftwidth).pack(side=TOP, fill=X)
1025 Checkbutton(protwin,
1026 text="POP3: track `seen' with client-side UIDLs?",
1027 variable=self.uidl).pack(side=TOP)
1028 Button(protwin, text='Probe for supported protocols', fg='blue',
1029 command=self.autoprobe).pack(side=LEFT)
1030 Button(protwin, text='Help', fg='blue',
1031 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1032 protwin.pack(fill=X)
1034 userwin = Frame(leftwin, relief=RAISED, bd=5)
1035 Label(userwin, text="User entries for " + host).pack(side=TOP)
1036 ListEdit("New user: ",
1037 map(lambda x: x.remote, self.server.users),
1038 lambda u, m=mode, s=self: s.user_edit(u, m),
1039 lambda u, s=self: s.user_delete(u),
1041 userwin.pack(fill=X)
1043 leftwin.pack(side=LEFT, anchor=N, fill=X);
1045 if mode != 'novice':
1046 rightwin = Frame(self);
1048 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1049 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1050 LabeledEntry(mdropwin, 'Envelope address header:',
1051 self.envelope, '22').pack(side=TOP, fill=X)
1052 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1053 self.envskip, '22').pack(side=TOP, fill=X)
1054 LabeledEntry(mdropwin, 'Name prefix to strip:',
1055 self.qvirtual, '22').pack(side=TOP, fill=X)
1056 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1057 variable=self.dns).pack(side=TOP)
1058 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1059 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1060 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1061 ListEdit("New domain: ",
1062 self.server.localdomains, None, None, mdropwin, multihelp)
1063 mdropwin.pack(fill=X)
1065 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1066 secwin = Frame(rightwin, relief=RAISED, bd=5)
1067 Label(secwin, text="Security").pack(side=TOP)
1068 # Don't actually let users set this. KPOP sets it implicitly
1069 # ButtonBar(secwin, 'Preauthorization mode:',
1070 # self.preauth, preauthlist, 1, None).pack(side=TOP)
1071 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1072 LabeledEntry(secwin, 'IP range to check before poll:',
1073 self.interface, leftwidth).pack(side=TOP, fill=X)
1074 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1075 LabeledEntry(secwin, 'Interface to monitor:',
1076 self.monitor, leftwidth).pack(side=TOP, fill=X)
1077 if 'netsec' in feature_options or 'netsec' in dictmembers:
1078 LabeledEntry(secwin, 'IPV6 security options:',
1079 self.netsec, leftwidth).pack(side=TOP, fill=X)
1080 Button(secwin, text='Help', fg='blue',
1081 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1084 rightwin.pack(side=LEFT, anchor=N);
1086 def autoprobe(self):
1087 # Note: this only handles case (1) near fetchmail.c:1032
1088 # We're assuming people smart enough to set up ssh tunneling
1089 # won't need autoprobing.
1091 realhost = self.server.via
1093 realhost = self.server.pollname
1095 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1096 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1098 sock.connect(realhost, port)
1099 greetline = sock.recv(1024)
1105 confwin = Toplevel()
1106 if greetline == None:
1107 title = "Autoprobe of " + realhost + " failed"
1109 Fetchmailconf didn't find any mailservers active.
1110 This could mean the host doesn't support any,
1111 or that your Internet connection is down, or
1112 that the host is so slow that the probe timed
1113 out before getting a response.
1117 # OK, now try to recognize potential problems
1119 if protocol == "POP2":
1120 warnings = warnings + """
1121 It appears you have somehow found a mailserver running only POP2.
1122 Congratulations. Have you considered a career in archaeology?
1124 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1125 Unless the first line of your fetchmail -V output includes the string "POP2",
1126 you'll have to build it from sources yourself with the configure
1127 switch --enable-POP2.
1131 ### POP3 servers start here
1133 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1134 warnings = warnings + """
1135 This appears to be an old version of the UC Davis POP server. These are
1136 dangerously unreliable (among other problems, they may drop your mailbox
1137 on the floor if your connection is interrupted during the session).
1139 It is strongly recommended that you find a better POP3 server. The fetchmail
1140 FAQ includes pointers to good ones.
1143 # Steve VanDevender <stevev@efn.org> writes:
1144 # The only system I have seen this happen with is cucipop-1.31
1145 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1146 # 2.x and probably quite a few other systems. It appears to be a
1147 # bug or bad interaction with the SunOS realloc() -- it turns out
1148 # that internally cucipop does allocate a certain data structure in
1149 # multiples of 16, using realloc() to bump it up to the next
1150 # multiple if it needs more.
1152 # The distinctive symptom is that when there are 16 messages in the
1153 # inbox, you can RETR and DELE all 16 messages successfully, but on
1154 # QUIT cucipop returns something like "-ERR Error locking your
1155 # mailbox" and aborts without updating it.
1157 # The cucipop banner looks like:
1159 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1161 if string.find(greetline, "Cubic Circle") > 0:
1162 warnings = warnings + """
1163 I see your server is running cucipop. Better make sure the server box
1164 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1165 under that version, and doesn't cope with the result gracefully. Newer
1166 SunOS and Solaris machines run cucipop OK.
1169 # The greeting line on the server known to be buggy is:
1170 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1172 if string.find(greetline, "FTGate") > 0:
1173 warnings = warnings + """
1174 This POP server has a weird bug; it says OK twice in response to TOP.
1175 Its response to RETR is normal, so use the `fetchall' option.
1178 if string.find(greetline, "OpenMail") > 0:
1179 warnings = warnings + """
1180 You appear to be using some version of HP OpenMail. Many versions of
1181 OpenMail do not process the "TOP" command correctly; the symptom is that
1182 only the header and first line of each message is retrieved. To work
1183 around this bug, turn on `fetchall' on all user entries associated with
1187 if string.find(greetline, "POP-Max") > 0:
1188 warnings = warnings + """
1189 The Mail Max POP3 server screws up on mail with attachments. It reports
1190 the message size with attachments included, but doesn't download them on a
1191 RETR or TOP (this violates the IMAP RFCs). You should get rid of it --
1192 and the brain-dead NT server it rode in on.
1195 if string.find(greetline, "POP3 Server Ready") > 0:
1196 warnings = warnings + """
1197 Some server that uses this greeting line has been observed to choke on
1198 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1201 if string.find(greetline, "QPOP") > 0:
1202 warnings = warnings + """
1203 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1204 knows all about qpopper. However, be aware that the 2.53 version of
1205 qpopper does something odd that causes fetchmail to hang with a socket
1206 error on very large messages. This is probably not a fetchmail bug, as
1207 it has been observed with fetchpop. The fix is to upgrade to qpopper
1208 3.0beta or a more recent version. Better yet, switch to IMAP.
1211 if string.find(greetline, " sprynet.com") > 0:
1212 warnings = warnings + """
1213 You appear to be using a SpryNet server. In mid-1999 it was reported that
1214 the SpryNet TOP command marks messages seen. Therefore, for proper error
1215 recovery in the event of a line drop, it is strongly recommended that you
1216 turn on `fetchall' on all user entries associated with this server.
1219 if string.find(greetline, "TEMS POP3") > 0:
1220 warnings = warnings + """
1221 Your POP3 server has "TEMS" in its header line. At least one such
1222 server does not process the "TOP" command correctly; the symptom is
1223 that fetchmail hangs when trying to retrieve mail. To work around
1224 this bug, turn on `fetchall' on all user entries associated with this
1228 if string.find(greetline, " usa.net") > 0:
1229 warnings = warnings + """
1230 You appear to be using USA.NET's free mail service. Their POP3 servers
1231 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1232 fetchmail can compensate. They seem to require that fetchall be switched on
1233 (otherwise you won't necessarily see all your mail, not even new mail).
1234 They also botch the TOP command the fetchmail normally uses for retrieval
1235 (it only retrieves about 10 lines rather than the number specified).
1236 Turning on fetchall will disable the use of TOP.
1238 Therefore, it is strongly recommended that you turn on `fetchall' on all
1239 user entries associated with this server.
1243 ### IMAP servers start here
1245 if string.find(greetline, "GroupWise") > 0:
1246 warnings = warnings + """
1247 The Novell GroupWise IMAP server would be better named GroupFoolish;
1248 it is (according to the designer of IMAP) unusably broken. Among
1249 other things, it doesn't include a required content length in its
1250 BODY[TEXT] response.<p>
1252 Fetchmail works around this problem, but we strongly recommend voting
1253 with your dollars for a server that isn't brain-dead. If you stick
1254 with code as shoddy as GroupWise seems to be, you will probably pay
1255 for it with other problems.<p>
1258 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1259 warnings = warnings + """
1260 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1261 set the messages's Seen flag. As a result, if you use the keep option the
1262 same messages will be downloaded over and over.
1265 if string.find(greetline, "InterChange") > 0:
1266 warnings = warnings + """
1267 The InterChange IMAP server screws up on mail with attachments. It
1268 doesn't fetch them if you give it a BODY[TEXT] request, though it
1269 does if you request RFC822.TEXT. According to the IMAP RFCs and their
1270 maintainer these should be equivalent -- and we can't drop the
1271 BODY[TEXT] form because M$ Exchange (quite legally under RFC2062)
1275 if string.find(greetline, "Imail") > 0:
1276 warnings = warnings + """
1277 We've seen a bug report indicating that this IMAP server (at least as of
1278 version 5.0.7) returns an invalid body size for messages with MIME
1279 attachments; the effect is to drop the attachments on the floor. We
1280 recommend you upgrade to a non-broken IMAP server.
1283 if string.find(greetline, "Domino IMAP4") > 0:
1284 warnings = warnings + """
1285 Your IMAP server appears to be Lotus Domino. This server, at least up
1286 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1287 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1288 will see attachments as part of the message text. If your Domino server's
1289 POP3 facility is enabled, we recommend you fall back on it.
1293 ### Checks for protocol variants start here
1295 closebrak = string.find(greetline, ">")
1296 if closebrak > 0 and greetline[closebrak+1] == "\r":
1297 warnings = warnings + """
1298 It looks like you could use APOP on this server and avoid sending it your
1299 password in clear. You should talk to the mailserver administrator about
1303 if string.find(greetline, "IMAP2bis") > 0:
1304 warnings = warnings + """
1305 IMAP2bis servers have a minor problem; they can't peek at messages without
1306 marking them seen. If you take a line hit during the retrieval, the
1307 interrupted message may get left on the server, marked seen.
1309 To work around this, it is recommended that you set the `fetchall'
1310 option on all user entries associated with this server, so any stuck
1311 mail will be retrieved next time around.
1313 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1314 a pointer to an open-source implementation.
1317 if string.find(greetline, "IMAP4rev1") > 0:
1318 warnings = warnings + """
1319 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1320 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1321 has therefore been extremely well tested with this class of server.
1325 warnings = warnings + """
1326 Fetchmail doesn't know anything special about this server type.
1330 # Display success window with warnings
1331 title = "Autoprobe of " + realhost + " succeeded"
1332 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1333 self.protocol.set(protocol)
1334 confwin.title(title)
1335 confwin.iconname(title)
1336 Label(confwin, text=title).pack()
1337 Message(confwin, text=confirm, width=600).pack()
1338 Button(confwin, text='Done',
1339 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1342 # User editing stuff
1346 'title' : 'User option help',
1347 'banner': 'User options',
1349 You may use this panel to set options
1350 that may differ between individual
1353 Once you have a user configuration set
1354 up as you like it, you can select `OK' to
1355 store it in the user list maintained in
1356 the site configuration window.
1358 If you wish to discard the changes you have
1359 made to user options, select `Quit'.
1363 'title' : 'Local name help',
1364 'banner': 'Local names',
1366 The local name(s) in a user entry are the
1367 people on the client machine who should
1368 receive mail from the poll described.
1370 Note: if a user entry has more than one
1371 local name, messages will be retrieved
1372 in multidrop mode. This complicates
1373 the configuration issues; see the manual
1374 page section on multidrop mode.
1377 class UserEdit(Frame, MyWidget):
1378 def __init__(self, username, parent):
1379 self.parent = parent
1381 for user in parent.server.users:
1382 if user.remote == username:
1384 if self.user == None:
1386 self.user.remote = username
1387 self.user.localnames = [username]
1388 parent.server.users.append(self.user)
1390 def edit(self, mode, master=None):
1391 Frame.__init__(self, master)
1393 self.master.title('Fetchmail user ' + self.user.remote
1394 + ' querying ' + self.parent.server.pollname);
1395 self.master.iconname('Fetchmail user ' + self.user.remote);
1396 self.post(User, 'user')
1397 self.makeWidgets(mode, self.parent.server.pollname)
1398 self.keepalive = [] # Use this to anchor the PhotoImage object
1399 make_icon_window(self, fetchmail_icon)
1402 # self.wait_window()
1406 # Yes, this test can fail -- if you delete the parent window.
1407 if self.parent.subwidgets.has_key(self.user.remote):
1408 del self.parent.subwidgets[self.user.remote]
1409 Widget.destroy(self.master)
1412 if ConfirmQuit(self, 'user option editing'):
1416 self.fetch(User, 'user')
1419 def makeWidgets(self, mode, servername):
1420 dispose_window(self,
1421 "User options for " + self.user.remote + " querying " + servername,
1424 if mode != 'novice':
1425 leftwin = Frame(self);
1429 secwin = Frame(leftwin, relief=RAISED, bd=5)
1430 Label(secwin, text="Authentication").pack(side=TOP)
1431 LabeledEntry(secwin, 'Password:',
1432 self.password, '12').pack(side=TOP, fill=X)
1433 secwin.pack(fill=X, anchor=N)
1435 if 'ssl' in feature_options or 'ssl' in dictmembers:
1436 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1437 Checkbutton(sslwin, text="Use SSL?",
1438 variable=self.ssl).pack(side=TOP, fill=X)
1439 LabeledEntry(sslwin, 'SSL key:',
1440 self.sslkey, '14').pack(side=TOP, fill=X)
1441 LabeledEntry(sslwin, 'SSL certificate:',
1442 self.sslcert, '14').pack(side=TOP, fill=X)
1443 sslwin.pack(fill=X, anchor=N)
1445 names = Frame(leftwin, relief=RAISED, bd=5)
1446 Label(names, text="Local names").pack(side=TOP)
1447 ListEdit("New name: ",
1448 self.user.localnames, None, None, names, localhelp)
1449 names.pack(fill=X, anchor=N)
1451 if mode != 'novice':
1452 targwin = Frame(leftwin, relief=RAISED, bd=5)
1453 Label(targwin, text="Forwarding Options").pack(side=TOP)
1454 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1455 ListEdit("New listener:",
1456 self.user.smtphunt, None, None, targwin, None)
1457 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1458 self.smtpaddress, '26').pack(side=TOP, fill=X)
1459 LabeledEntry(targwin, 'Connection setup command:',
1460 self.preconnect, '26').pack(side=TOP, fill=X)
1461 LabeledEntry(targwin, 'Connection wrapup command:',
1462 self.postconnect, '26').pack(side=TOP, fill=X)
1463 LabeledEntry(targwin, 'Local delivery agent:',
1464 self.mda, '26').pack(side=TOP, fill=X)
1465 LabeledEntry(targwin, 'BSMTP output file:',
1466 self.bsmtp, '26').pack(side=TOP, fill=X)
1467 LabeledEntry(targwin, 'Listener spam-block codes:',
1468 self.antispam, '26').pack(side=TOP, fill=X)
1469 LabeledEntry(targwin, 'Pass-through properties:',
1470 self.properties, '26').pack(side=TOP, fill=X)
1471 Checkbutton(targwin, text="Use LMTP?",
1472 variable=self.lmtp).pack(side=TOP, fill=X)
1473 targwin.pack(fill=X, anchor=N)
1475 if mode != 'novice':
1476 leftwin.pack(side=LEFT, fill=X, anchor=N)
1477 rightwin = Frame(self)
1481 optwin = Frame(rightwin, relief=RAISED, bd=5)
1482 Label(optwin, text="Processing Options").pack(side=TOP)
1483 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1484 variable=self.keep).pack(side=TOP, anchor=W)
1485 Checkbutton(optwin, text="Fetch old messages as well as new",
1486 variable=self.fetchall).pack(side=TOP, anchor=W)
1487 if mode != 'novice':
1488 Checkbutton(optwin, text="Flush seen messages before retrieval",
1489 variable=self.flush).pack(side=TOP, anchor=W)
1490 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1491 variable=self.rewrite).pack(side=TOP, anchor=W)
1492 Checkbutton(optwin, text="Force CR/LF at end of each line",
1493 variable=self.forcecr).pack(side=TOP, anchor=W)
1494 Checkbutton(optwin, text="Strip CR from end of each line",
1495 variable=self.stripcr).pack(side=TOP, anchor=W)
1496 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1497 variable=self.pass8bits).pack(side=TOP, anchor=W)
1498 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1499 variable=self.mimedecode).pack(side=TOP, anchor=W)
1500 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1501 variable=self.dropstatus).pack(side=TOP, anchor=W)
1504 if mode != 'novice':
1505 limwin = Frame(rightwin, relief=RAISED, bd=5)
1506 Label(limwin, text="Resource Limits").pack(side=TOP)
1507 LabeledEntry(limwin, 'Message size limit:',
1508 self.limit, '30').pack(side=TOP, fill=X)
1509 LabeledEntry(limwin, 'Size warning interval:',
1510 self.warnings, '30').pack(side=TOP, fill=X)
1511 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1512 self.fetchlimit, '30').pack(side=TOP, fill=X)
1513 LabeledEntry(limwin, 'Max messages to forward per poll:',
1514 self.batchlimit, '30').pack(side=TOP, fill=X)
1515 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1516 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1517 self.expunge, '30').pack(side=TOP, fill=X)
1518 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1519 variable=self.idle).pack(side=TOP, anchor=W)
1522 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1523 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1524 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1525 ListEdit("New folder:", self.user.mailboxes,
1526 None, None, foldwin, None)
1527 foldwin.pack(fill=X, anchor=N)
1529 if mode != 'novice':
1530 rightwin.pack(side=LEFT)
1536 # Top-level window that offers either novice or expert mode
1537 # (but not both at once; it disappears when one is selected).
1540 class Configurator(Frame):
1541 def __init__(self, outfile, master, onexit, parent):
1542 Frame.__init__(self, master)
1543 self.outfile = outfile
1544 self.onexit = onexit
1545 self.parent = parent
1546 self.master.title('fetchmail configurator');
1547 self.master.iconname('fetchmail configurator');
1549 self.keepalive = [] # Use this to anchor the PhotoImage object
1550 make_icon_window(self, fetchmail_icon)
1552 Message(self, text="""
1553 Use `Novice Configuration' for basic fetchmail setup;
1554 with this, you can easily set up a single-drop connection
1555 to one remote mail server.
1556 """, width=600).pack(side=TOP)
1557 Button(self, text='Novice Configuration',
1558 fg='blue', command=self.novice).pack()
1560 Message(self, text="""
1561 Use `Expert Configuration' for advanced fetchmail setup,
1562 including multiple-site or multidrop connections.
1563 """, width=600).pack(side=TOP)
1564 Button(self, text='Expert Configuration',
1565 fg='blue', command=self.expert).pack()
1567 Message(self, text="""
1568 Or you can just select `Quit' to leave the configurator now and
1569 return to the main panel.
1570 """, width=600).pack(side=TOP)
1571 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1572 master.protocol("WM_DELETE_WINDOW", self.leave)
1575 self.master.destroy()
1576 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1579 self.master.destroy()
1580 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1583 self.master.destroy()
1586 # Run a command in a scrolling text widget, displaying its output
1588 class RunWindow(Frame):
1589 def __init__(self, command, master, parent):
1590 Frame.__init__(self, master)
1591 self.master = master
1592 self.master.title('fetchmail run window');
1593 self.master.iconname('fetchmail run window');
1596 text="Running "+command,
1597 bd=2).pack(side=TOP, pady=10)
1598 self.keepalive = [] # Use this to anchor the PhotoImage object
1599 make_icon_window(self, fetchmail_icon)
1601 # This is a scrolling text window
1602 textframe = Frame(self)
1603 scroll = Scrollbar(textframe)
1604 self.textwidget = Text(textframe, setgrid=TRUE)
1605 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1606 self.textwidget.config(yscrollcommand=scroll.set)
1607 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1608 scroll.config(command=self.textwidget.yview)
1609 scroll.pack(side=RIGHT, fill=BOTH)
1610 textframe.pack(side=TOP)
1612 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1614 self.update() # Draw widget before executing fetchmail
1616 child_stdout = os.popen(command + " 2>&1", "r")
1618 ch = child_stdout.read(1)
1621 self.textwidget.insert(END, ch)
1622 self.textwidget.insert(END, "Done.")
1623 self.textwidget.see(END);
1626 Widget.destroy(self.master)
1628 # Here's where we choose either configuration or launching
1630 class MainWindow(Frame):
1631 def __init__(self, outfile, master=None):
1632 Frame.__init__(self, master)
1633 self.outfile = outfile
1634 self.master.title('fetchmail launcher');
1635 self.master.iconname('fetchmail launcher');
1638 text='Fetchmailconf ' + version,
1639 bd=2).pack(side=TOP, pady=10)
1640 self.keepalive = [] # Use this to anchor the PhotoImage object
1641 make_icon_window(self, fetchmail_icon)
1644 ## Test icon display with the following:
1645 # icon_image = PhotoImage(data=fetchmail_icon)
1646 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1647 # self.keepalive.append(icon_image)
1649 Message(self, text="""
1650 Use `Configure fetchmail' to tell fetchmail about the remote
1651 servers it should poll (the host name, your username there,
1652 whether to use POP or IMAP, and so forth).
1653 """, width=600).pack(side=TOP)
1654 self.configbutton = Button(self, text='Configure fetchmail',
1655 fg='blue', command=self.configure)
1656 self.configbutton.pack()
1658 Message(self, text="""
1659 Use `Test fetchmail' to run fetchmail with debugging enabled.
1660 This is a good way to test out a new configuration.
1661 """, width=600).pack(side=TOP)
1662 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1664 Message(self, text="""
1665 Use `Run fetchmail' to run fetchmail in foreground.
1666 Progress messages will be shown, but not debug messages.
1667 """, width=600).pack(side=TOP)
1668 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1670 Message(self, text="""
1671 Or you can just select `Quit' to exit the launcher now.
1672 """, width=600).pack(side=TOP)
1673 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1675 def configure(self):
1676 self.configbutton.configure(state=DISABLED)
1677 Configurator(self.outfile, Toplevel(),
1678 lambda self=self: self.configbutton.configure(state=NORMAL),
1681 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1684 RunWindow("fetchmail -d0", Toplevel(), self)
1689 # Functions for turning a dictionary into an instantiated object tree.
1691 def intersect(list1, list2):
1692 # Compute set intersection of lists
1699 def setdiff(list1, list2):
1700 # Compute set difference of lists
1707 def copy_instance(toclass, fromdict):
1708 # Initialize a class object of given type from a conformant dictionary.
1709 for fld in fromdict.keys():
1710 if not fld in dictmembers:
1711 dictmembers.append(fld)
1712 # The `optional' fields are the ones we can ignore for purposes of
1713 # conformability checking; they'll still get copied if they are
1714 # present in the dictionary.
1715 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1716 class_sig = setdiff(toclass.__dict__.keys(), optional)
1718 dict_keys = setdiff(fromdict.keys(), optional)
1720 common = intersect(class_sig, dict_keys)
1721 if 'typemap' in class_sig:
1722 class_sig.remove('typemap')
1723 if tuple(class_sig) != tuple(dict_keys):
1724 print "Fields don't match what fetchmailconf expected:"
1725 # print "Class signature: " + `class_sig`
1726 # print "Dictionary keys: " + `dict_keys`
1727 diff = setdiff(class_sig, common)
1729 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1730 diff = setdiff(dict_keys, common)
1732 print "Not matched in dictionary keys: " + `diff`
1735 for x in fromdict.keys():
1736 setattr(toclass, x, fromdict[x])
1739 # And this is the main sequence. How it works:
1741 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1742 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1743 # Run execfile on the file to pull fetchmailrc into Python global space.
1744 # You don't want static data, though; you want, instead, a tree of objects
1745 # with the same data members and added appropriate methods.
1747 # This is what the copy_instance function() is for. It tries to copy a
1748 # dictionary field by field into a class, aborting if the class and dictionary
1749 # have different data members (except for any typemap member in the class;
1750 # that one is strictly for use by the MyWidget supperclass).
1752 # Once the object tree is set up, require user to choose novice or expert
1753 # mode and instantiate an edit object for the configuration. Class methods
1754 # will take it all from there.
1756 # Options (not documented because they're for fetchmailconf debuggers only):
1757 # -d: Read the configuration and dump it to stdout before editing. Dump
1758 # the edited result to stdout as well.
1759 # -f: specify the run control file to read.
1761 if __name__ == '__main__':
1763 if not os.environ.has_key("DISPLAY"):
1764 print "fetchmailconf must be run under X"
1767 fetchmail_icon = """
1768 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1769 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1770 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1771 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1772 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1773 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1774 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1775 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1776 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1777 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1778 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1779 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1780 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1781 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1782 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1783 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1784 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1785 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1786 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1787 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1788 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1789 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1790 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1791 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1792 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1793 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1794 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1795 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1796 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1797 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1798 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1799 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1801 # The base64 data in the string above was generated by the following procedure:
1804 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1808 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1809 dump = rcfile = None;
1810 for (switch, val) in options:
1811 if (switch == '-d'):
1813 elif (switch == '-f'):
1816 # Get client host's FQDN
1817 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1820 ConfigurationDefaults = Configuration()
1821 ServerDefaults = Server()
1822 UserDefaults = User()
1824 # Read the existing configuration
1825 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1827 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1829 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1834 print "`" + cmd + "' run failure, status " + `s`
1837 print "Unknown error while running fetchmail --configdump"
1844 print "Can't read configuration output of fetchmail --configdump."
1850 # The tricky part -- initializing objects from the configuration global
1851 # `Configuration' is the top level of the object tree we're going to mung.
1852 # The dictmembers list is used to track the set of fields the dictionary
1853 # contains; in particular, we can use it to tell whether things like the
1854 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1856 Fetchmailrc = Configuration()
1857 copy_instance(Fetchmailrc, fetchmailrc)
1858 Fetchmailrc.servers = [];
1859 for server in fetchmailrc['servers']:
1861 copy_instance(Newsite, server)
1862 Fetchmailrc.servers.append(Newsite)
1864 for user in server['users']:
1866 copy_instance(Newuser, user)
1867 Newsite.users.append(Newuser)
1869 # We may want to display the configuration and quit
1871 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1873 # The theory here is that -f alone sets the rcfile location,
1874 # but -d and -f together mean the new configuration should go to stdout.
1875 if not rcfile and not dump:
1876 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1878 # OK, now run the configuration edit
1879 root = MainWindow(rcfile)
1882 # The following sets edit modes for GNU EMACS