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 '" + `self.remote` + "' there ";
265 res = res + "with password '" + `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
1190 reports the message size with attachments included, but doesn't
1191 download them on a RETR or TOP (this violates the IMAP RFCs). It also
1192 doesn't implement TOP correctly. You should get rid of it -- and the
1193 brain-dead NT server it rode in on.
1196 if string.find(greetline, "POP3 Server Ready") > 0:
1197 warnings = warnings + """
1198 Some server that uses this greeting line has been observed to choke on
1199 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1202 if string.find(greetline, "QPOP") > 0:
1203 warnings = warnings + """
1204 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1205 knows all about qpopper. However, be aware that the 2.53 version of
1206 qpopper does something odd that causes fetchmail to hang with a socket
1207 error on very large messages. This is probably not a fetchmail bug, as
1208 it has been observed with fetchpop. The fix is to upgrade to qpopper
1209 3.0beta or a more recent version. Better yet, switch to IMAP.
1212 if string.find(greetline, " sprynet.com") > 0:
1213 warnings = warnings + """
1214 You appear to be using a SpryNet server. In mid-1999 it was reported that
1215 the SpryNet TOP command marks messages seen. Therefore, for proper error
1216 recovery in the event of a line drop, it is strongly recommended that you
1217 turn on `fetchall' on all user entries associated with this server.
1220 if string.find(greetline, "TEMS POP3") > 0:
1221 warnings = warnings + """
1222 Your POP3 server has "TEMS" in its header line. At least one such
1223 server does not process the "TOP" command correctly; the symptom is
1224 that fetchmail hangs when trying to retrieve mail. To work around
1225 this bug, turn on `fetchall' on all user entries associated with this
1229 if string.find(greetline, " spray.se") > 0:
1230 warnings = warnings + """
1231 Your POP3 server has "spray.se" in its header line. In May 2000 at
1232 least one such server did not process the "TOP" command correctly; the
1233 symptom is that messages are treated as headerless. To work around
1234 this bug, turn on `fetchall' on all user entries associated with this
1238 if string.find(greetline, " usa.net") > 0:
1239 warnings = warnings + """
1240 You appear to be using USA.NET's free mail service. Their POP3 servers
1241 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1242 fetchmail can compensate. They seem to require that fetchall be switched on
1243 (otherwise you won't necessarily see all your mail, not even new mail).
1244 They also botch the TOP command the fetchmail normally uses for retrieval
1245 (it only retrieves about 10 lines rather than the number specified).
1246 Turning on fetchall will disable the use of TOP.
1248 Therefore, it is strongly recommended that you turn on `fetchall' on all
1249 user entries associated with this server.
1253 ### IMAP servers start here
1255 if string.find(greetline, "GroupWise") > 0:
1256 warnings = warnings + """
1257 The Novell GroupWise IMAP server would be better named GroupFoolish;
1258 it is (according to the designer of IMAP) unusably broken. Among
1259 other things, it doesn't include a required content length in its
1260 BODY[TEXT] response.<p>
1262 Fetchmail works around this problem, but we strongly recommend voting
1263 with your dollars for a server that isn't brain-dead. If you stick
1264 with code as shoddy as GroupWise seems to be, you will probably pay
1265 for it with other problems.<p>
1268 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1269 warnings = warnings + """
1270 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1271 set the messages's Seen flag. As a result, if you use the keep option the
1272 same messages will be downloaded over and over.
1275 if string.find(greetline, "InterChange") > 0:
1276 warnings = warnings + """
1277 The InterChange IMAP server screws up on mail with attachments. It
1278 doesn't fetch them if you give it a BODY[TEXT] request, though it
1279 does if you request RFC822.TEXT. According to the IMAP RFCs and their
1280 maintainer these should be equivalent -- and we can't drop the
1281 BODY[TEXT] form because M$ Exchange (quite legally under RFC2062)
1285 if string.find(greetline, "Imail") > 0:
1286 warnings = warnings + """
1287 We've seen a bug report indicating that this IMAP server (at least as of
1288 version 5.0.7) returns an invalid body size for messages with MIME
1289 attachments; the effect is to drop the attachments on the floor. We
1290 recommend you upgrade to a non-broken IMAP server.
1293 if string.find(greetline, "Domino IMAP4") > 0:
1294 warnings = warnings + """
1295 Your IMAP server appears to be Lotus Domino. This server, at least up
1296 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1297 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1298 will see attachments as part of the message text. If your Domino server's
1299 POP3 facility is enabled, we recommend you fall back on it.
1303 ### Checks for protocol variants start here
1305 closebrak = string.find(greetline, ">")
1306 if closebrak > 0 and greetline[closebrak+1] == "\r":
1307 warnings = warnings + """
1308 It looks like you could use APOP on this server and avoid sending it your
1309 password in clear. You should talk to the mailserver administrator about
1313 if string.find(greetline, "IMAP2bis") > 0:
1314 warnings = warnings + """
1315 IMAP2bis servers have a minor problem; they can't peek at messages without
1316 marking them seen. If you take a line hit during the retrieval, the
1317 interrupted message may get left on the server, marked seen.
1319 To work around this, it is recommended that you set the `fetchall'
1320 option on all user entries associated with this server, so any stuck
1321 mail will be retrieved next time around.
1323 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1324 a pointer to an open-source implementation.
1327 if string.find(greetline, "IMAP4rev1") > 0:
1328 warnings = warnings + """
1329 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1330 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1331 has therefore been extremely well tested with this class of server.
1335 warnings = warnings + """
1336 Fetchmail doesn't know anything special about this server type.
1340 # Display success window with warnings
1341 title = "Autoprobe of " + realhost + " succeeded"
1342 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1343 self.protocol.set(protocol)
1344 confwin.title(title)
1345 confwin.iconname(title)
1346 Label(confwin, text=title).pack()
1347 Message(confwin, text=confirm, width=600).pack()
1348 Button(confwin, text='Done',
1349 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1352 # User editing stuff
1356 'title' : 'User option help',
1357 'banner': 'User options',
1359 You may use this panel to set options
1360 that may differ between individual
1363 Once you have a user configuration set
1364 up as you like it, you can select `OK' to
1365 store it in the user list maintained in
1366 the site configuration window.
1368 If you wish to discard the changes you have
1369 made to user options, select `Quit'.
1373 'title' : 'Local name help',
1374 'banner': 'Local names',
1376 The local name(s) in a user entry are the
1377 people on the client machine who should
1378 receive mail from the poll described.
1380 Note: if a user entry has more than one
1381 local name, messages will be retrieved
1382 in multidrop mode. This complicates
1383 the configuration issues; see the manual
1384 page section on multidrop mode.
1387 class UserEdit(Frame, MyWidget):
1388 def __init__(self, username, parent):
1389 self.parent = parent
1391 for user in parent.server.users:
1392 if user.remote == username:
1394 if self.user == None:
1396 self.user.remote = username
1397 self.user.localnames = [username]
1398 parent.server.users.append(self.user)
1400 def edit(self, mode, master=None):
1401 Frame.__init__(self, master)
1403 self.master.title('Fetchmail user ' + self.user.remote
1404 + ' querying ' + self.parent.server.pollname);
1405 self.master.iconname('Fetchmail user ' + self.user.remote);
1406 self.post(User, 'user')
1407 self.makeWidgets(mode, self.parent.server.pollname)
1408 self.keepalive = [] # Use this to anchor the PhotoImage object
1409 make_icon_window(self, fetchmail_icon)
1412 # self.wait_window()
1416 # Yes, this test can fail -- if you delete the parent window.
1417 if self.parent.subwidgets.has_key(self.user.remote):
1418 del self.parent.subwidgets[self.user.remote]
1419 Widget.destroy(self.master)
1422 if ConfirmQuit(self, 'user option editing'):
1426 self.fetch(User, 'user')
1429 def makeWidgets(self, mode, servername):
1430 dispose_window(self,
1431 "User options for " + self.user.remote + " querying " + servername,
1434 if mode != 'novice':
1435 leftwin = Frame(self);
1439 secwin = Frame(leftwin, relief=RAISED, bd=5)
1440 Label(secwin, text="Authentication").pack(side=TOP)
1441 LabeledEntry(secwin, 'Password:',
1442 self.password, '12').pack(side=TOP, fill=X)
1443 secwin.pack(fill=X, anchor=N)
1445 if 'ssl' in feature_options or 'ssl' in dictmembers:
1446 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1447 Checkbutton(sslwin, text="Use SSL?",
1448 variable=self.ssl).pack(side=TOP, fill=X)
1449 LabeledEntry(sslwin, 'SSL key:',
1450 self.sslkey, '14').pack(side=TOP, fill=X)
1451 LabeledEntry(sslwin, 'SSL certificate:',
1452 self.sslcert, '14').pack(side=TOP, fill=X)
1453 sslwin.pack(fill=X, anchor=N)
1455 names = Frame(leftwin, relief=RAISED, bd=5)
1456 Label(names, text="Local names").pack(side=TOP)
1457 ListEdit("New name: ",
1458 self.user.localnames, None, None, names, localhelp)
1459 names.pack(fill=X, anchor=N)
1461 if mode != 'novice':
1462 targwin = Frame(leftwin, relief=RAISED, bd=5)
1463 Label(targwin, text="Forwarding Options").pack(side=TOP)
1464 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1465 ListEdit("New listener:",
1466 self.user.smtphunt, None, None, targwin, None)
1467 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1468 self.smtpaddress, '26').pack(side=TOP, fill=X)
1469 LabeledEntry(targwin, 'Connection setup command:',
1470 self.preconnect, '26').pack(side=TOP, fill=X)
1471 LabeledEntry(targwin, 'Connection wrapup command:',
1472 self.postconnect, '26').pack(side=TOP, fill=X)
1473 LabeledEntry(targwin, 'Local delivery agent:',
1474 self.mda, '26').pack(side=TOP, fill=X)
1475 LabeledEntry(targwin, 'BSMTP output file:',
1476 self.bsmtp, '26').pack(side=TOP, fill=X)
1477 LabeledEntry(targwin, 'Listener spam-block codes:',
1478 self.antispam, '26').pack(side=TOP, fill=X)
1479 LabeledEntry(targwin, 'Pass-through properties:',
1480 self.properties, '26').pack(side=TOP, fill=X)
1481 Checkbutton(targwin, text="Use LMTP?",
1482 variable=self.lmtp).pack(side=TOP, fill=X)
1483 targwin.pack(fill=X, anchor=N)
1485 if mode != 'novice':
1486 leftwin.pack(side=LEFT, fill=X, anchor=N)
1487 rightwin = Frame(self)
1491 optwin = Frame(rightwin, relief=RAISED, bd=5)
1492 Label(optwin, text="Processing Options").pack(side=TOP)
1493 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1494 variable=self.keep).pack(side=TOP, anchor=W)
1495 Checkbutton(optwin, text="Fetch old messages as well as new",
1496 variable=self.fetchall).pack(side=TOP, anchor=W)
1497 if mode != 'novice':
1498 Checkbutton(optwin, text="Flush seen messages before retrieval",
1499 variable=self.flush).pack(side=TOP, anchor=W)
1500 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1501 variable=self.rewrite).pack(side=TOP, anchor=W)
1502 Checkbutton(optwin, text="Force CR/LF at end of each line",
1503 variable=self.forcecr).pack(side=TOP, anchor=W)
1504 Checkbutton(optwin, text="Strip CR from end of each line",
1505 variable=self.stripcr).pack(side=TOP, anchor=W)
1506 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1507 variable=self.pass8bits).pack(side=TOP, anchor=W)
1508 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1509 variable=self.mimedecode).pack(side=TOP, anchor=W)
1510 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1511 variable=self.dropstatus).pack(side=TOP, anchor=W)
1514 if mode != 'novice':
1515 limwin = Frame(rightwin, relief=RAISED, bd=5)
1516 Label(limwin, text="Resource Limits").pack(side=TOP)
1517 LabeledEntry(limwin, 'Message size limit:',
1518 self.limit, '30').pack(side=TOP, fill=X)
1519 LabeledEntry(limwin, 'Size warning interval:',
1520 self.warnings, '30').pack(side=TOP, fill=X)
1521 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1522 self.fetchlimit, '30').pack(side=TOP, fill=X)
1523 LabeledEntry(limwin, 'Max messages to forward per poll:',
1524 self.batchlimit, '30').pack(side=TOP, fill=X)
1525 if self.parent.server.protocol != 'ETRN':
1526 LabeledEntry(limwin, 'Interval between expunges:',
1527 self.expunge, '30').pack(side=TOP, fill=X)
1528 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1529 variable=self.idle).pack(side=TOP, anchor=W)
1532 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1533 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1534 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1535 ListEdit("New folder:", self.user.mailboxes,
1536 None, None, foldwin, None)
1537 foldwin.pack(fill=X, anchor=N)
1539 if mode != 'novice':
1540 rightwin.pack(side=LEFT)
1546 # Top-level window that offers either novice or expert mode
1547 # (but not both at once; it disappears when one is selected).
1550 class Configurator(Frame):
1551 def __init__(self, outfile, master, onexit, parent):
1552 Frame.__init__(self, master)
1553 self.outfile = outfile
1554 self.onexit = onexit
1555 self.parent = parent
1556 self.master.title('fetchmail configurator');
1557 self.master.iconname('fetchmail configurator');
1559 self.keepalive = [] # Use this to anchor the PhotoImage object
1560 make_icon_window(self, fetchmail_icon)
1562 Message(self, text="""
1563 Use `Novice Configuration' for basic fetchmail setup;
1564 with this, you can easily set up a single-drop connection
1565 to one remote mail server.
1566 """, width=600).pack(side=TOP)
1567 Button(self, text='Novice Configuration',
1568 fg='blue', command=self.novice).pack()
1570 Message(self, text="""
1571 Use `Expert Configuration' for advanced fetchmail setup,
1572 including multiple-site or multidrop connections.
1573 """, width=600).pack(side=TOP)
1574 Button(self, text='Expert Configuration',
1575 fg='blue', command=self.expert).pack()
1577 Message(self, text="""
1578 Or you can just select `Quit' to leave the configurator now and
1579 return to the main panel.
1580 """, width=600).pack(side=TOP)
1581 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1582 master.protocol("WM_DELETE_WINDOW", self.leave)
1585 self.master.destroy()
1586 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1589 self.master.destroy()
1590 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1593 self.master.destroy()
1596 # Run a command in a scrolling text widget, displaying its output
1598 class RunWindow(Frame):
1599 def __init__(self, command, master, parent):
1600 Frame.__init__(self, master)
1601 self.master = master
1602 self.master.title('fetchmail run window');
1603 self.master.iconname('fetchmail run window');
1606 text="Running "+command,
1607 bd=2).pack(side=TOP, pady=10)
1608 self.keepalive = [] # Use this to anchor the PhotoImage object
1609 make_icon_window(self, fetchmail_icon)
1611 # This is a scrolling text window
1612 textframe = Frame(self)
1613 scroll = Scrollbar(textframe)
1614 self.textwidget = Text(textframe, setgrid=TRUE)
1615 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1616 self.textwidget.config(yscrollcommand=scroll.set)
1617 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1618 scroll.config(command=self.textwidget.yview)
1619 scroll.pack(side=RIGHT, fill=BOTH)
1620 textframe.pack(side=TOP)
1622 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1624 self.update() # Draw widget before executing fetchmail
1626 child_stdout = os.popen(command + " 2>&1", "r")
1628 ch = child_stdout.read(1)
1631 self.textwidget.insert(END, ch)
1632 self.textwidget.insert(END, "Done.")
1633 self.textwidget.see(END);
1636 Widget.destroy(self.master)
1638 # Here's where we choose either configuration or launching
1640 class MainWindow(Frame):
1641 def __init__(self, outfile, master=None):
1642 Frame.__init__(self, master)
1643 self.outfile = outfile
1644 self.master.title('fetchmail launcher');
1645 self.master.iconname('fetchmail launcher');
1648 text='Fetchmailconf ' + version,
1649 bd=2).pack(side=TOP, pady=10)
1650 self.keepalive = [] # Use this to anchor the PhotoImage object
1651 make_icon_window(self, fetchmail_icon)
1654 ## Test icon display with the following:
1655 # icon_image = PhotoImage(data=fetchmail_icon)
1656 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1657 # self.keepalive.append(icon_image)
1659 Message(self, text="""
1660 Use `Configure fetchmail' to tell fetchmail about the remote
1661 servers it should poll (the host name, your username there,
1662 whether to use POP or IMAP, and so forth).
1663 """, width=600).pack(side=TOP)
1664 self.configbutton = Button(self, text='Configure fetchmail',
1665 fg='blue', command=self.configure)
1666 self.configbutton.pack()
1668 Message(self, text="""
1669 Use `Test fetchmail' to run fetchmail with debugging enabled.
1670 This is a good way to test out a new configuration.
1671 """, width=600).pack(side=TOP)
1672 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1674 Message(self, text="""
1675 Use `Run fetchmail' to run fetchmail in foreground.
1676 Progress messages will be shown, but not debug messages.
1677 """, width=600).pack(side=TOP)
1678 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1680 Message(self, text="""
1681 Or you can just select `Quit' to exit the launcher now.
1682 """, width=600).pack(side=TOP)
1683 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1685 def configure(self):
1686 self.configbutton.configure(state=DISABLED)
1687 Configurator(self.outfile, Toplevel(),
1688 lambda self=self: self.configbutton.configure(state=NORMAL),
1691 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1694 RunWindow("fetchmail -d0", Toplevel(), self)
1699 # Functions for turning a dictionary into an instantiated object tree.
1701 def intersect(list1, list2):
1702 # Compute set intersection of lists
1709 def setdiff(list1, list2):
1710 # Compute set difference of lists
1717 def copy_instance(toclass, fromdict):
1718 # Initialize a class object of given type from a conformant dictionary.
1719 for fld in fromdict.keys():
1720 if not fld in dictmembers:
1721 dictmembers.append(fld)
1722 # The `optional' fields are the ones we can ignore for purposes of
1723 # conformability checking; they'll still get copied if they are
1724 # present in the dictionary.
1725 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1726 class_sig = setdiff(toclass.__dict__.keys(), optional)
1728 dict_keys = setdiff(fromdict.keys(), optional)
1730 common = intersect(class_sig, dict_keys)
1731 if 'typemap' in class_sig:
1732 class_sig.remove('typemap')
1733 if tuple(class_sig) != tuple(dict_keys):
1734 print "Fields don't match what fetchmailconf expected:"
1735 # print "Class signature: " + `class_sig`
1736 # print "Dictionary keys: " + `dict_keys`
1737 diff = setdiff(class_sig, common)
1739 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1740 diff = setdiff(dict_keys, common)
1742 print "Not matched in dictionary keys: " + `diff`
1745 for x in fromdict.keys():
1746 setattr(toclass, x, fromdict[x])
1749 # And this is the main sequence. How it works:
1751 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1752 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1753 # Run execfile on the file to pull fetchmailrc into Python global space.
1754 # You don't want static data, though; you want, instead, a tree of objects
1755 # with the same data members and added appropriate methods.
1757 # This is what the copy_instance function() is for. It tries to copy a
1758 # dictionary field by field into a class, aborting if the class and dictionary
1759 # have different data members (except for any typemap member in the class;
1760 # that one is strictly for use by the MyWidget supperclass).
1762 # Once the object tree is set up, require user to choose novice or expert
1763 # mode and instantiate an edit object for the configuration. Class methods
1764 # will take it all from there.
1766 # Options (not documented because they're for fetchmailconf debuggers only):
1767 # -d: Read the configuration and dump it to stdout before editing. Dump
1768 # the edited result to stdout as well.
1769 # -f: specify the run control file to read.
1771 if __name__ == '__main__':
1773 if not os.environ.has_key("DISPLAY"):
1774 print "fetchmailconf must be run under X"
1777 fetchmail_icon = """
1778 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1779 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1780 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1781 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1782 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1783 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1784 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1785 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1786 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1787 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1788 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1789 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1790 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1791 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1792 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1793 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1794 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1795 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1796 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1797 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1798 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1799 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1800 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1801 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1802 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1803 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1804 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1805 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1806 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1807 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1808 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1809 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1811 # The base64 data in the string above was generated by the following procedure:
1814 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1818 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1819 dump = rcfile = None;
1820 for (switch, val) in options:
1821 if (switch == '-d'):
1823 elif (switch == '-f'):
1826 # Get client host's FQDN
1827 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1830 ConfigurationDefaults = Configuration()
1831 ServerDefaults = Server()
1832 UserDefaults = User()
1834 # Read the existing configuration. We set the umask to 077 to make sure
1835 # that group & other read/write permissions are shut off -- we wouldn't
1836 # want crackers to snoop password information out of the tempfile.
1837 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1839 cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1841 cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
1846 print "`" + cmd + "' run failure, status " + `s`
1849 print "Unknown error while running fetchmail --configdump"
1856 print "Can't read configuration output of fetchmail --configdump."
1862 # The tricky part -- initializing objects from the configuration global
1863 # `Configuration' is the top level of the object tree we're going to mung.
1864 # The dictmembers list is used to track the set of fields the dictionary
1865 # contains; in particular, we can use it to tell whether things like the
1866 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1868 Fetchmailrc = Configuration()
1869 copy_instance(Fetchmailrc, fetchmailrc)
1870 Fetchmailrc.servers = [];
1871 for server in fetchmailrc['servers']:
1873 copy_instance(Newsite, server)
1874 Fetchmailrc.servers.append(Newsite)
1876 for user in server['users']:
1878 copy_instance(Newuser, user)
1879 Newsite.users.append(Newuser)
1881 # We may want to display the configuration and quit
1883 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1885 # The theory here is that -f alone sets the rcfile location,
1886 # but -d and -f together mean the new configuration should go to stdout.
1887 if not rcfile and not dump:
1888 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1890 # OK, now run the configuration edit
1891 root = MainWindow(rcfile)
1894 # The following sets edit modes for GNU EMACS