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)
495 apply(self.editor, (item,))
499 select = self.listwidget.curselection()
504 if index and self.editor:
505 label = self.listwidget.get(index);
506 apply(self.editor, (label,))
508 def deleteItem(self):
509 select = self.listwidget.curselection()
513 index = string.atoi(select[0])
514 label = self.listwidget.get(index);
515 self.listwidget.delete(index)
516 if self.list != None:
518 if self.deletor != None:
519 apply(self.deletor, (label,))
521 def ConfirmQuit(frame, context):
524 text = 'Really quit ' + context + ' without saving?',
526 strings = ('Yes', 'No'),
530 def dispose_window(master, legend, help, savelegend='OK'):
531 dispose = Frame(master, relief=RAISED, bd=5)
532 Label(dispose, text=legend).pack(side=TOP,pady=10)
533 Button(dispose, text=savelegend, fg='blue',
534 command=master.save).pack(side=LEFT)
535 Button(dispose, text='Quit', fg='blue',
536 command=master.nosave).pack(side=LEFT)
537 Button(dispose, text='Help', fg='blue',
538 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
543 # Common methods for Tkinter widgets -- deals with Tkinter declaration
544 def post(self, widgetclass, field):
545 for x in widgetclass.typemap:
546 if x[1] == 'Boolean':
547 setattr(self, x[0], BooleanVar(self))
548 elif x[1] == 'String':
549 setattr(self, x[0], StringVar(self))
551 setattr(self, x[0], IntVar(self))
552 source = getattr(getattr(self, field), x[0])
554 getattr(self, x[0]).set(source)
556 def fetch(self, widgetclass, field):
557 for x in widgetclass.typemap:
558 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
561 # First, code to set the global fetchmail run controls.
564 configure_novice_help = {
565 'title' : 'Fetchmail novice configurator help',
566 'banner': 'Novice configurator help',
568 In the `Novice Configurator Controls' panel, you can:
570 Press `Save' to save the new fetchmail configuration you have created.
571 Press `Quit' to exit without saving.
572 Press `Help' to bring up this help message.
574 In the `Novice Configuration' panels, you will set up the basic data
575 needed to create a simple fetchmail setup. These include:
577 1. The name of the remote site you want to query.
579 2. Your login name on that site.
581 3. Your password on that site.
583 4. A protocol to use (POP, IMAP, ETRN, etc.)
585 5. A polling interval.
587 6. Options to fetch old messages as well as new, uor to suppress
588 deletion of fetched message.
590 The novice-configuration code will assume that you want to forward mail
591 to a local sendmail listener with no special options.
594 configure_expert_help = {
595 'title' : 'Fetchmail expert configurator help',
596 'banner': 'Expert configurator help',
598 In the `Expert Configurator Controls' panel, you can:
600 Press `Save' to save the new fetchmail configuration you have edited.
601 Press `Quit' to exit without saving.
602 Press `Help' to bring up this help message.
604 In the `Run Controls' panel, you can set the following options that
605 control how fetchmail runs:
608 Number of seconds to wait between polls in the background.
609 If zero, fetchmail will run in foreground.
612 If empty, emit progress and error messages to stderr.
613 Otherwise this gives the name of the files to write to.
614 This field is ignored if the "Log to syslog?" option is on.
617 If empty, store seen-message IDs in .fetchids under user's home
618 directory. If nonempty, use given file name.
621 Who to send multidrop mail to as a last resort if no address can
622 be matched. Normally empty; in this case, fetchmail treats the
623 invoking user as the address of last resort unless that user is
624 root. If that user is root, fetchmail sends to `postmaster'.
627 If this option is on (the default) error mail goes to the sender.
628 Otherwise it goes to the postmaster.
631 If false (the default) fetchmail generates a Received line into
632 each message and generates a HELO from the machine it is running on.
633 If true, fetchmail generates no Received line and HELOs as if it were
636 In the `Remote Mail Configurations' panel, you can:
638 1. Enter the name of a new remote mail server you want fetchmail to query.
640 To do this, simply enter a label for the poll configuration in the
641 `New Server:' box. The label should be a DNS name of the server (unless
642 you are using ssh or some other tunneling method and will fill in the `via'
643 option on the site configuration screen).
645 2. Change the configuration of an existing site.
647 To do this, find the site's label in the listbox and double-click it.
648 This will take you to a site configuration dialogue.
652 class ConfigurationEdit(Frame, MyWidget):
653 def __init__(self, configuration, outfile, master, onexit):
655 self.configuration = configuration
656 self.outfile = outfile
657 self.container = master
659 ConfigurationEdit.mode_to_help = {
660 'novice':configure_novice_help, 'expert':configure_expert_help
663 def server_edit(self, sitename):
664 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
666 def server_delete(self, sitename):
668 del self.configuration[sitename]
672 def edit(self, mode):
674 Frame.__init__(self, self.container)
675 self.master.title('fetchmail ' + self.mode + ' configurator');
676 self.master.iconname('fetchmail ' + self.mode + ' configurator');
677 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
678 self.keepalive = [] # Use this to anchor the PhotoImage object
679 make_icon_window(self, fetchmail_icon)
681 self.post(Configuration, 'configuration')
684 'Configurator ' + self.mode + ' Controls',
685 ConfigurationEdit.mode_to_help[self.mode],
688 gf = Frame(self, relief=RAISED, bd = 5)
690 text='Fetchmail Run Controls',
691 bd=2).pack(side=TOP, pady=10)
696 if self.mode != 'novice':
698 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
699 log.pack(side=RIGHT, anchor=E)
701 # Set the poll interval
702 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
703 de.pack(side=RIGHT, anchor=E)
708 if self.mode != 'novice':
711 {'text':'Bounces to sender?',
712 'variable':self.bouncemail,
713 'relief':GROOVE}).pack(side=LEFT, anchor=W)
718 {'text':'Log to syslog?',
719 'variable':self.syslog,
720 'relief':GROOVE}).pack(side=LEFT, anchor=W)
721 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
722 log.pack(side=RIGHT, anchor=E)
726 {'text':'Invisible mode?',
727 'variable':self.invisible,
728 'relief':GROOVE}).pack(side=LEFT, anchor=W)
730 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
731 log.pack(side=RIGHT, anchor=E)
735 # Expert mode allows us to edit multiple sites
736 lf = Frame(self, relief=RAISED, bd=5)
738 text='Remote Mail Server Configurations',
739 bd=2).pack(side=TOP, pady=10)
740 ListEdit('New Server:',
741 map(lambda x: x.pollname, self.configuration.servers),
742 lambda site, self=self: self.server_edit(site),
743 lambda site, self=self: self.server_delete(site),
748 for sitename in self.subwidgets.keys():
749 self.subwidgets[sitename].destruct()
750 self.master.destroy()
754 if ConfirmQuit(self, self.mode + " configuration editor"):
758 for sitename in self.subwidgets.keys():
759 self.subwidgets[sitename].save()
760 self.fetch(Configuration, 'configuration')
764 elif not os.path.isfile(self.outfile) or Dialog(self,
765 title = 'Overwrite existing run control file?',
766 text = 'Really overwrite existing run control file?',
768 strings = ('Yes', 'No'),
769 default = 1).num == 0:
770 fm = open(self.outfile, 'w')
772 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
773 fm.write(`self.configuration`)
777 os.chmod(self.outfile, 0600)
781 # Server editing stuff.
784 'title' : 'Remote site help',
785 'banner': 'Remote sites',
787 When you add a site name to the list here,
788 you initialize an entry telling fetchmail
789 how to poll a new site.
791 When you select a sitename (by double-
792 clicking it, or by single-clicking to
793 select and then clicking the Edit button),
794 you will open a window to configure that
799 'title' : 'Server options help',
800 'banner': 'Server Options',
802 The server options screen controls fetchmail
803 options that apply to one of your mailservers.
805 Once you have a mailserver configuration set
806 up as you like it, you can select `OK' to
807 store it in the server list maintained in
808 the main configuration window.
810 If you wish to discard changes to a server
811 configuration, select `Quit'.
815 'title' : 'Run Control help',
816 'banner': 'Run Controls',
818 If the `Poll normally' checkbox is on, the host is polled as part of
819 the normal operation of fetchmail when it is run with no arguments.
820 If it is off, fetchmail will only query this host when it is given as
821 a command-line argument.
823 The `True name of server' box should specify the actual DNS name
824 to query. By default this is the same as the poll name.
826 Normally each host described in the file is queried once each
827 poll cycle. If `Cycles to skip between polls' is greater than 0,
828 that's the number of poll cycles that are skipped between the
829 times this post is actually polled.
831 The `Server timeout' is the number of seconds fetchmail will wait
832 for a reply from the mailserver before concluding it is hung and
837 'title' : 'Protocol and Port help',
838 'banner': 'Protocol and Port',
840 These options control the remote-mail protocol
841 and TCP/IP service port used to query this
844 If you click the `Probe for supported protocols'
845 button, fetchmail will try to find you the most
846 capable server on the selected host (this will
847 only work if you're conncted to the Internet).
848 The probe only checks for ordinary IMAP and POP
849 protocols; fortunately these are the most
850 frequently supported.
852 The `Protocol' button bar offers you a choice of
853 all the different protocols available. The `auto'
854 protocol is the default mode; it probes the host
855 ports for POP3 and IMAP to see if either is
858 Normally the TCP/IP service port to use is
859 dictated by the protocol choice. The `Port'
860 field (only present in expert mode) lets you
861 set a non-standard port.
865 'title' : 'Security option help',
866 'banner': 'Security',
868 The `interface' option allows you to specify a range
869 of IP addresses to monitor for activity. If these
870 addresses are not active, fetchmail will not poll.
871 Specifying this may protect you from a spoofing attack
872 if your client machine has more than one IP gateway
873 address and some of the gateways are to insecure nets.
875 The `monitor' option, if given, specifies the only
876 device through which fetchmail is permitted to connect
877 to servers. This option may be used to prevent
878 fetchmail from triggering an expensive dial-out if the
879 interface is not already active.
881 The `interface' and `monitor' options are available
882 only for Linux and freeBSD systems. See the fetchmail
883 manual page for details on these.
885 The ssl option enables SSL communication with a mailserver
886 supporting Secure Sockets Layer. The sslkey and sslcert options
887 declare key and certificate files for use with SSL.
889 The `netsec' option will be configurable only if fetchmail
890 was compiled with IPV6 support. If you need to use it,
891 you probably know what to do.
895 'title' : 'Multidrop option help',
896 'banner': 'Multidrop',
898 These options are only useful with multidrop mode.
899 See the manual page for extended discussion.
903 'title' : 'User list help',
904 'banner': 'User list',
906 When you add a user name to the list here,
907 you initialize an entry telling fetchmail
908 to poll the site on behalf of the new user.
910 When you select a username (by double-
911 clicking it, or by single-clicking to
912 select and then clicking the Edit button),
913 you will open a window to configure the
914 user's options on that site.
917 class ServerEdit(Frame, MyWidget):
918 def __init__(self, host, parent):
922 for site in parent.configuration.servers:
923 if site.pollname == host:
925 if (self.server == None):
926 self.server = Server()
927 self.server.pollname = host
928 self.server.via = None
929 parent.configuration.servers.append(self.server)
931 def edit(self, mode, master=None):
932 Frame.__init__(self, master)
934 self.master.title('Fetchmail host ' + self.server.pollname);
935 self.master.iconname('Fetchmail host ' + self.server.pollname);
936 self.post(Server, 'server')
937 self.makeWidgets(self.server.pollname, mode)
938 self.keepalive = [] # Use this to anchor the PhotoImage object
939 make_icon_window(self, fetchmail_icon)
946 for username in self.subwidgets.keys():
947 self.subwidgets[username].destruct()
948 del self.parent.subwidgets[self.server.pollname]
949 Widget.destroy(self.master)
952 if ConfirmQuit(self, 'server option editing'):
956 self.fetch(Server, 'server')
957 for username in self.subwidgets.keys():
958 self.subwidgets[username].save()
961 def refreshPort(self):
962 proto = self.protocol.get()
963 if self.port.get() == 0:
964 self.port.set(defaultports[proto])
965 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
967 def user_edit(self, username, mode):
968 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
970 def user_delete(self, username):
971 if self.subwidgets.has_key(username):
972 del self.subwidgets[username]
973 del self.server[username]
975 def makeWidgets(self, host, mode):
976 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
978 leftwin = Frame(self);
982 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
983 Label(ctlwin, text="Run Controls").pack(side=TOP)
984 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
985 LabeledEntry(ctlwin, 'True name of ' + host + ':',
986 self.via, leftwidth).pack(side=TOP, fill=X)
987 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
988 self.interval, leftwidth).pack(side=TOP, fill=X)
989 LabeledEntry(ctlwin, 'Server timeout (seconds):',
990 self.timeout, leftwidth).pack(side=TOP, fill=X)
991 Button(ctlwin, text='Help', fg='blue',
992 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
995 # Compute the available protocols from the compile-time options
997 if 'pop2' in feature_options:
998 protolist.append("POP2")
999 if 'pop3' in feature_options:
1000 protolist = protolist + ["POP3", "APOP", "KPOP"]
1001 if 'sdps' in feature_options:
1002 protolist.append("SDPS")
1003 if 'imap' in feature_options:
1004 protolist.append("IMAP")
1005 if 'imap-gss' in feature_options:
1006 protolist.append("IMAP-GSS")
1007 if 'imap-k4' in feature_options:
1008 protolist.append("IMAP-K4")
1009 if 'etrn' in feature_options:
1010 protolist.append("ETRN")
1012 protwin = Frame(leftwin, relief=RAISED, bd=5)
1013 Label(protwin, text="Protocol").pack(side=TOP)
1014 ButtonBar(protwin, '',
1015 self.protocol, protolist, 2,
1017 if mode != 'novice':
1018 LabeledEntry(protwin, 'On server TCP/IP port:',
1019 self.port, leftwidth).pack(side=TOP, fill=X)
1021 Checkbutton(protwin,
1022 text="POP3: track `seen' with client-side UIDLs?",
1023 variable=self.uidl).pack(side=TOP)
1024 Button(protwin, text='Probe for supported protocols', fg='blue',
1025 command=self.autoprobe).pack(side=LEFT)
1026 Button(protwin, text='Help', fg='blue',
1027 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1028 protwin.pack(fill=X)
1030 userwin = Frame(leftwin, relief=RAISED, bd=5)
1031 Label(userwin, text="User entries for " + host).pack(side=TOP)
1032 ListEdit("New user: ",
1033 map(lambda x: x.remote, self.server.users),
1034 lambda u, m=mode, s=self: s.user_edit(u, m),
1035 lambda u, s=self: s.user_delete(u),
1037 userwin.pack(fill=X)
1039 leftwin.pack(side=LEFT, anchor=N, fill=X);
1041 if mode != 'novice':
1042 rightwin = Frame(self);
1044 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1045 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1046 LabeledEntry(mdropwin, 'Envelope address header:',
1047 self.envelope, '22').pack(side=TOP, fill=X)
1048 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1049 self.envskip, '22').pack(side=TOP, fill=X)
1050 LabeledEntry(mdropwin, 'Name prefix to strip:',
1051 self.qvirtual, '22').pack(side=TOP, fill=X)
1052 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1053 variable=self.dns).pack(side=TOP)
1054 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1055 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1056 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1057 ListEdit("New domain: ",
1058 self.server.localdomains, None, None, mdropwin, multihelp)
1059 mdropwin.pack(fill=X)
1061 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1062 secwin = Frame(rightwin, relief=RAISED, bd=5)
1063 Label(secwin, text="Security").pack(side=TOP)
1064 # Don't actually let users set this. KPOP sets it implicitly
1065 # ButtonBar(secwin, 'Preauthorization mode:',
1066 # self.preauth, preauthlist, 1, None).pack(side=TOP)
1067 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1068 LabeledEntry(secwin, 'IP range to check before poll:',
1069 self.interface, leftwidth).pack(side=TOP, fill=X)
1070 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1071 LabeledEntry(secwin, 'Interface to monitor:',
1072 self.monitor, leftwidth).pack(side=TOP, fill=X)
1073 if 'netsec' in feature_options or 'netsec' in dictmembers:
1074 LabeledEntry(secwin, 'IPV6 security options:',
1075 self.netsec, leftwidth).pack(side=TOP, fill=X)
1076 Button(secwin, text='Help', fg='blue',
1077 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1080 rightwin.pack(side=LEFT, anchor=N);
1082 def autoprobe(self):
1083 # Note: this only handles case (1) near fetchmail.c:1032
1084 # We're assuming people smart enough to set up ssh tunneling
1085 # won't need autoprobing.
1087 realhost = self.server.via
1089 realhost = self.server.pollname
1091 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1092 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1094 sock.connect(realhost, port)
1095 greetline = sock.recv(1024)
1101 confwin = Toplevel()
1102 if greetline == None:
1103 title = "Autoprobe of " + realhost + " failed"
1105 Fetchmailconf didn't find any mailservers active.
1106 This could mean the host doesn't support any,
1107 or that your Internet connection is down, or
1108 that the host is so slow that the probe timed
1109 out before getting a response.
1113 # OK, now try to recognize potential problems
1115 if protocol == "POP2":
1116 warnings = warnings + """
1117 It appears you have somehow found a mailserver running only POP2.
1118 Congratulations. Have you considered a career in archaeology?
1120 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1121 Unless the first line of your fetchmail -V output includes the string "POP2",
1122 you'll have to build it from sources yourself with the configure
1123 switch --enable-POP2.
1127 ### POP3 servers start here
1129 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1130 warnings = warnings + """
1131 This appears to be an old version of the UC Davis POP server. These are
1132 dangerously unreliable (among other problems, they may drop your mailbox
1133 on the floor if your connection is interrupted during the session).
1135 It is strongly recommended that you find a better POP3 server. The fetchmail
1136 FAQ includes pointers to good ones.
1139 # Steve VanDevender <stevev@efn.org> writes:
1140 # The only system I have seen this happen with is cucipop-1.31
1141 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1142 # 2.x and probably quite a few other systems. It appears to be a
1143 # bug or bad interaction with the SunOS realloc() -- it turns out
1144 # that internally cucipop does allocate a certain data structure in
1145 # multiples of 16, using realloc() to bump it up to the next
1146 # multiple if it needs more.
1148 # The distinctive symptom is that when there are 16 messages in the
1149 # inbox, you can RETR and DELE all 16 messages successfully, but on
1150 # QUIT cucipop returns something like "-ERR Error locking your
1151 # mailbox" and aborts without updating it.
1153 # The cucipop banner looks like:
1155 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1157 if string.find(greetline, "Cubic Circle") > 0:
1158 warnings = warnings + """
1159 I see your server is running cucipop. Better make sure the server box
1160 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1161 under that version, and doesn't cope with the result gracefully. Newer
1162 SunOS and Solaris machines run cucipop OK.
1165 # The greeting line on the server known to be buggy is:
1166 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1168 if string.find(greetline, "FTGate") > 0:
1169 warnings = warnings + """
1170 This POP server has a weird bug; it says OK twice in response to TOP.
1171 Its response to RETR is normal, so use the `fetchall' option.
1174 if string.find(greetline, "OpenMail") > 0:
1175 warnings = warnings + """
1176 You appear to be using some version of HP OpenMail. Many versions of
1177 OpenMail do not process the "TOP" command correctly; the symptom is that
1178 only the header and first line of each message is retrieved. To work
1179 around this bug, turn on `fetchall' on all user entries associated with
1183 if string.find(greetline, "POP-Max") > 0:
1184 warnings = warnings + """
1185 The Mail Max POP3 server screws up on mail with attachments. It reports
1186 the message size with attachments included, but doesn't download them on a
1187 RETR or TOP (this violates the IMAP RFCs). You should get rid of it --
1188 and the brain-dead NT server it rode in on.
1191 if string.find(greetline, "POP3 Server Ready") > 0:
1192 warnings = warnings + """
1193 Some server that uses this greeting line has been observed to choke on
1194 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1197 if string.find(greetline, "QPOP") > 0:
1198 warnings = warnings + """
1199 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1200 knows all about qpopper. However, be aware that the 2.53 version of
1201 qpopper does something odd that causes fetchmail to hang with a socket
1202 error on very large messages. This is probably not a fetchmail bug, as
1203 it has been observed with fetchpop. The fix is to upgrade to qpopper
1204 3.0beta or a more recent version. Better yet, switch to IMAP.
1207 if string.find(greetline, " sprynet.com") > 0:
1208 warnings = warnings + """
1209 You appear to be using a SpryNet server. In mid-1999 it was reported that
1210 the SpryNet TOP command marks messages seen. Therefore, for proper error
1211 recovery in the event of a line drop, it is strongly recommended that you
1212 turn on `fetchall' on all user entries associated with this server.
1215 if string.find(greetline, "TEMS POP3") > 0:
1216 warnings = warnings + """
1217 Your POP3 server has "TEMS" in its header line. At least one such
1218 server does not process the "TOP" command correctly; the symptom is
1219 that fetchmail hangs when trying to retrieve mail. To work around
1220 this bug, turn on `fetchall' on all user entries associated with this
1224 if string.find(greetline, " usa.net") > 0:
1225 warnings = warnings + """
1226 You appear to be using USA.NET's free mail service. Their POP3 servers
1227 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1228 fetchmail can compensate. They seem to require that fetchall be switched on
1229 (otherwise you won't necessarily see all your mail, not even new mail).
1230 They also botch the TOP command the fetchmail normally uses for retrieval
1231 (it only retrieves about 10 lines rather than the number specified).
1232 Turning on fetchall will disable the use of TOP.
1234 Therefore, it is strongly recommended that you turn on `fetchall' on all
1235 user entries associated with this server.
1239 ### IMAP servers start here
1241 if string.find(greetline, "GroupWise") > 0:
1242 warnings = warnings + """
1243 The Novell GroupWise IMAP server would be better named GroupFoolish;
1244 it is (according to the designer of IMAP) unusably broken. Among
1245 other things, it doesn't include a required content length in its
1246 BODY[TEXT] response.<p>
1248 Fetchmail works around this problem, but we strongly recommend voting
1249 with your dollars for a server that isn't brain-dead. If you stick
1250 with code as shoddy as GroupWise seems to be, you will probably pay
1251 for it with other problems.<p>
1254 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1255 warnings = warnings + """
1256 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1257 set the messages's Seen flag. As a result, if you use the keep option the
1258 same messages will be downloaded over and over.
1261 if string.find(greetline, "InterChange") > 0:
1262 warnings = warnings + """
1263 The InterChange IMAP server screws up on mail with attachments. It
1264 doesn't fetch them if you give it a BODY[TEXT] request, though it
1265 does if you request RFC822.TEXT. According to the IMAP RFCs and their
1266 maintainer these should be equivalent -- and we can't drop the
1267 BODY[TEXT] form because M$ Exchange (quite legally under RFC2062)
1271 if string.find(greetline, "Imail") > 0:
1272 warnings = warnings + """
1273 We've seen a bug report indicating that this IMAP server (at least as of
1274 version 5.0.7) returns an invalid body size for messages with MIME
1275 attachments; the effect is to drop the attachments on the floor. We
1276 recommend you upgrade to a non-broken IMAP server.
1279 if string.find(greetline, "Domino IMAP4") > 0:
1280 warnings = warnings + """
1281 Your IMAP server appears to be Lotus Domino. This server, at least up
1282 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1283 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1284 will see attachments as part of the message text. If your Domino server's
1285 POP3 facility is enabled, we recommend you fall back on it.
1289 ### Checks for protocol variants start here
1291 closebrak = string.find(greetline, ">")
1292 if closebrak > 0 and greetline[closebrak+1] == "\r":
1293 warnings = warnings + """
1294 It looks like you could use APOP on this server and avoid sending it your
1295 password in clear. You should talk to the mailserver administrator about
1299 if string.find(greetline, "IMAP2bis") > 0:
1300 warnings = warnings + """
1301 IMAP2bis servers have a minor problem; they can't peek at messages without
1302 marking them seen. If you take a line hit during the retrieval, the
1303 interrupted message may get left on the server, marked seen.
1305 To work around this, it is recommended that you set the `fetchall'
1306 option on all user entries associated with this server, so any stuck
1307 mail will be retrieved next time around.
1309 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1310 a pointer to an open-source implementation.
1313 if string.find(greetline, "IMAP4rev1") > 0:
1314 warnings = warnings + """
1315 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1316 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1317 has therefore been extremely well tested with this class of server.
1321 warnings = warnings + """
1322 Fetchmail doesn't know anything special about this server type.
1326 # Display success window with warnings
1327 title = "Autoprobe of " + realhost + " succeeded"
1328 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1329 self.protocol.set(protocol)
1330 confwin.title(title)
1331 confwin.iconname(title)
1332 Label(confwin, text=title).pack()
1333 Message(confwin, text=confirm, width=600).pack()
1334 Button(confwin, text='Done',
1335 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1338 # User editing stuff
1342 'title' : 'User option help',
1343 'banner': 'User options',
1345 You may use this panel to set options
1346 that may differ between individual
1349 Once you have a user configuration set
1350 up as you like it, you can select `OK' to
1351 store it in the user list maintained in
1352 the site configuration window.
1354 If you wish to discard the changes you have
1355 made to user options, select `Quit'.
1359 'title' : 'Local name help',
1360 'banner': 'Local names',
1362 The local name(s) in a user entry are the
1363 people on the client machine who should
1364 receive mail from the poll described.
1366 Note: if a user entry has more than one
1367 local name, messages will be retrieved
1368 in multidrop mode. This complicates
1369 the configuration issues; see the manual
1370 page section on multidrop mode.
1373 class UserEdit(Frame, MyWidget):
1374 def __init__(self, username, parent):
1375 self.parent = parent
1377 for user in parent.server.users:
1378 if user.remote == username:
1380 if self.user == None:
1382 self.user.remote = username
1383 self.user.localnames = [username]
1384 parent.server.users.append(self.user)
1386 def edit(self, mode, master=None):
1387 Frame.__init__(self, master)
1389 self.master.title('Fetchmail user ' + self.user.remote
1390 + ' querying ' + self.parent.server.pollname);
1391 self.master.iconname('Fetchmail user ' + self.user.remote);
1392 self.post(User, 'user')
1393 self.makeWidgets(mode, self.parent.server.pollname)
1394 self.keepalive = [] # Use this to anchor the PhotoImage object
1395 make_icon_window(self, fetchmail_icon)
1398 # self.wait_window()
1402 del self.parent.subwidgets[self.user.remote]
1403 Widget.destroy(self.master)
1406 if ConfirmQuit(self, 'user option editing'):
1410 self.fetch(User, 'user')
1413 def makeWidgets(self, mode, servername):
1414 dispose_window(self,
1415 "User options for " + self.user.remote + " querying " + servername,
1418 if mode != 'novice':
1419 leftwin = Frame(self);
1423 secwin = Frame(leftwin, relief=RAISED, bd=5)
1424 Label(secwin, text="Authentication").pack(side=TOP)
1425 LabeledEntry(secwin, 'Password:',
1426 self.password, '12').pack(side=TOP, fill=X)
1427 secwin.pack(fill=X, anchor=N)
1429 if 'ssl' in feature_options or 'ssl' in dictmembers:
1430 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1431 Checkbutton(sslwin, text="Use SSL?",
1432 variable=self.ssl).pack(side=TOP, fill=X)
1433 LabeledEntry(sslwin, 'SSL key:',
1434 self.sslkey, '14').pack(side=TOP, fill=X)
1435 LabeledEntry(sslwin, 'SSL certificate:',
1436 self.sslcert, '14').pack(side=TOP, fill=X)
1437 sslwin.pack(fill=X, anchor=N)
1439 names = Frame(leftwin, relief=RAISED, bd=5)
1440 Label(names, text="Local names").pack(side=TOP)
1441 ListEdit("New name: ",
1442 self.user.localnames, None, None, names, localhelp)
1443 names.pack(fill=X, anchor=N)
1445 if mode != 'novice':
1446 targwin = Frame(leftwin, relief=RAISED, bd=5)
1447 Label(targwin, text="Forwarding Options").pack(side=TOP)
1448 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1449 ListEdit("New listener:",
1450 self.user.smtphunt, None, None, targwin, None)
1451 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1452 self.smtpaddress, '26').pack(side=TOP, fill=X)
1453 LabeledEntry(targwin, 'Connection setup command:',
1454 self.preconnect, '26').pack(side=TOP, fill=X)
1455 LabeledEntry(targwin, 'Connection wrapup command:',
1456 self.postconnect, '26').pack(side=TOP, fill=X)
1457 LabeledEntry(targwin, 'Local delivery agent:',
1458 self.mda, '26').pack(side=TOP, fill=X)
1459 LabeledEntry(targwin, 'BSMTP output file:',
1460 self.bsmtp, '26').pack(side=TOP, fill=X)
1461 LabeledEntry(targwin, 'Listener spam-block codes:',
1462 self.antispam, '26').pack(side=TOP, fill=X)
1463 LabeledEntry(targwin, 'Pass-through properties:',
1464 self.properties, '26').pack(side=TOP, fill=X)
1465 Checkbutton(targwin, text="Use LMTP?",
1466 variable=self.lmtp).pack(side=TOP, fill=X)
1467 targwin.pack(fill=X, anchor=N)
1469 if mode != 'novice':
1470 leftwin.pack(side=LEFT, fill=X, anchor=N)
1471 rightwin = Frame(self)
1475 optwin = Frame(rightwin, relief=RAISED, bd=5)
1476 Label(optwin, text="Processing Options").pack(side=TOP)
1477 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1478 variable=self.keep).pack(side=TOP, anchor=W)
1479 Checkbutton(optwin, text="Fetch old messages as well as new",
1480 variable=self.fetchall).pack(side=TOP, anchor=W)
1481 if mode != 'novice':
1482 Checkbutton(optwin, text="Flush seen messages before retrieval",
1483 variable=self.flush).pack(side=TOP, anchor=W)
1484 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1485 variable=self.rewrite).pack(side=TOP, anchor=W)
1486 Checkbutton(optwin, text="Force CR/LF at end of each line",
1487 variable=self.forcecr).pack(side=TOP, anchor=W)
1488 Checkbutton(optwin, text="Strip CR from end of each line",
1489 variable=self.stripcr).pack(side=TOP, anchor=W)
1490 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1491 variable=self.pass8bits).pack(side=TOP, anchor=W)
1492 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1493 variable=self.mimedecode).pack(side=TOP, anchor=W)
1494 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1495 variable=self.dropstatus).pack(side=TOP, anchor=W)
1498 if mode != 'novice':
1499 limwin = Frame(rightwin, relief=RAISED, bd=5)
1500 Label(limwin, text="Resource Limits").pack(side=TOP)
1501 LabeledEntry(limwin, 'Message size limit:',
1502 self.limit, '30').pack(side=TOP, fill=X)
1503 LabeledEntry(limwin, 'Size warning interval:',
1504 self.warnings, '30').pack(side=TOP, fill=X)
1505 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1506 self.fetchlimit, '30').pack(side=TOP, fill=X)
1507 LabeledEntry(limwin, 'Max messages to forward per poll:',
1508 self.batchlimit, '30').pack(side=TOP, fill=X)
1509 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1510 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1511 self.expunge, '30').pack(side=TOP, fill=X)
1512 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1513 variable=self.idle).pack(side=TOP, anchor=W)
1516 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1517 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1518 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1519 ListEdit("New folder:", self.user.mailboxes,
1520 None, None, foldwin, None)
1521 foldwin.pack(fill=X, anchor=N)
1523 if mode != 'novice':
1524 rightwin.pack(side=LEFT)
1530 # Top-level window that offers either novice or expert mode
1531 # (but not both at once; it disappears when one is selected).
1534 class Configurator(Frame):
1535 def __init__(self, outfile, master, onexit, parent):
1536 Frame.__init__(self, master)
1537 self.outfile = outfile
1538 self.onexit = onexit
1539 self.parent = parent
1540 self.master.title('fetchmail configurator');
1541 self.master.iconname('fetchmail configurator');
1543 self.keepalive = [] # Use this to anchor the PhotoImage object
1544 make_icon_window(self, fetchmail_icon)
1546 Message(self, text="""
1547 Use `Novice Configuration' for basic fetchmail setup;
1548 with this, you can easily set up a single-drop connection
1549 to one remote mail server.
1550 """, width=600).pack(side=TOP)
1551 Button(self, text='Novice Configuration',
1552 fg='blue', command=self.novice).pack()
1554 Message(self, text="""
1555 Use `Expert Configuration' for advanced fetchmail setup,
1556 including multiple-site or multidrop connections.
1557 """, width=600).pack(side=TOP)
1558 Button(self, text='Expert Configuration',
1559 fg='blue', command=self.expert).pack()
1561 Message(self, text="""
1562 Or you can just select `Quit' to leave the configurator now and
1563 return to the main panel.
1564 """, width=600).pack(side=TOP)
1565 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1566 master.protocol("WM_DELETE_WINDOW", self.leave)
1569 self.master.destroy()
1570 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1573 self.master.destroy()
1574 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1577 self.master.destroy()
1580 # Run a command in a scrolling text widget, displaying its output
1582 class RunWindow(Frame):
1583 def __init__(self, command, master, parent):
1584 Frame.__init__(self, master)
1585 self.master = master
1586 self.master.title('fetchmail run window');
1587 self.master.iconname('fetchmail run window');
1590 text="Running "+command,
1591 bd=2).pack(side=TOP, pady=10)
1592 self.keepalive = [] # Use this to anchor the PhotoImage object
1593 make_icon_window(self, fetchmail_icon)
1595 # This is a scrolling text window
1596 textframe = Frame(self)
1597 scroll = Scrollbar(textframe)
1598 self.textwidget = Text(textframe, setgrid=TRUE)
1599 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1600 self.textwidget.config(yscrollcommand=scroll.set)
1601 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1602 scroll.config(command=self.textwidget.yview)
1603 scroll.pack(side=RIGHT, fill=BOTH)
1604 textframe.pack(side=TOP)
1606 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1608 self.update() # Draw widget before executing fetchmail
1610 child_stdout = os.popen(command + " 2>&1", "r")
1612 ch = child_stdout.read(1)
1615 self.textwidget.insert(END, ch)
1616 self.textwidget.insert(END, "Done.")
1617 self.textwidget.see(END);
1620 Widget.destroy(self.master)
1622 # Here's where we choose either configuration or launching
1624 class MainWindow(Frame):
1625 def __init__(self, outfile, master=None):
1626 Frame.__init__(self, master)
1627 self.outfile = outfile
1628 self.master.title('fetchmail launcher');
1629 self.master.iconname('fetchmail launcher');
1632 text='Fetchmailconf ' + version,
1633 bd=2).pack(side=TOP, pady=10)
1634 self.keepalive = [] # Use this to anchor the PhotoImage object
1635 make_icon_window(self, fetchmail_icon)
1638 ## Test icon display with the following:
1639 # icon_image = PhotoImage(data=fetchmail_icon)
1640 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1641 # self.keepalive.append(icon_image)
1643 Message(self, text="""
1644 Use `Configure fetchmail' to tell fetchmail about the remote
1645 servers it should poll (the host name, your username there,
1646 whether to use POP or IMAP, and so forth).
1647 """, width=600).pack(side=TOP)
1648 self.configbutton = Button(self, text='Configure fetchmail',
1649 fg='blue', command=self.configure)
1650 self.configbutton.pack()
1652 Message(self, text="""
1653 Use `Test fetchmail' to run fetchmail with debugging enabled.
1654 This is a good way to test out a new configuration.
1655 """, width=600).pack(side=TOP)
1656 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1658 Message(self, text="""
1659 Use `Run fetchmail' to run fetchmail in foreground.
1660 Progress messages will be shown, but not debug messages.
1661 """, width=600).pack(side=TOP)
1662 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1664 Message(self, text="""
1665 Or you can just select `Quit' to exit the launcher now.
1666 """, width=600).pack(side=TOP)
1667 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1669 sysversion = string.split(sys.version, " ")[0]
1670 if sysversion < "1.5.2":
1671 Message(self, text="""
1672 Warning: it looks like your Python version is older than 1.5.2.
1673 Double-clicking an empty listbox entry may cause fetchmailconf
1674 to abort (this seems to be a tkinter bug). We recommend upgrading.
1675 """, width=600).pack(side=TOP)
1677 def configure(self):
1678 self.configbutton.configure(state=DISABLED)
1679 Configurator(self.outfile, Toplevel(),
1680 lambda self=self: self.configbutton.configure(state=NORMAL),
1683 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1686 RunWindow("fetchmail -d0", Toplevel(), self)
1691 # Functions for turning a dictionary into an instantiated object tree.
1693 def intersect(list1, list2):
1694 # Compute set intersection of lists
1701 def setdiff(list1, list2):
1702 # Compute set difference of lists
1709 def copy_instance(toclass, fromdict):
1710 # Initialize a class object of given type from a conformant dictionary.
1711 for fld in fromdict.keys():
1712 if not fld in dictmembers:
1713 dictmembers.append(fld)
1714 # The `optional' fields are the ones we can ignore for purposes of
1715 # conformability checking; they'll still get copied if they are
1716 # present in the dictionary.
1717 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1718 class_sig = setdiff(toclass.__dict__.keys(), optional)
1720 dict_keys = setdiff(fromdict.keys(), optional)
1722 common = intersect(class_sig, dict_keys)
1723 if 'typemap' in class_sig:
1724 class_sig.remove('typemap')
1725 if tuple(class_sig) != tuple(dict_keys):
1726 print "Fields don't match what fetchmailconf expected:"
1727 # print "Class signature: " + `class_sig`
1728 # print "Dictionary keys: " + `dict_keys`
1729 diff = setdiff(class_sig, common)
1731 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1732 diff = setdiff(dict_keys, common)
1734 print "Not matched in dictionary keys: " + `diff`
1737 for x in fromdict.keys():
1738 setattr(toclass, x, fromdict[x])
1741 # And this is the main sequence. How it works:
1743 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1744 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1745 # Run execfile on the file to pull fetchmailrc into Python global space.
1746 # You don't want static data, though; you want, instead, a tree of objects
1747 # with the same data members and added appropriate methods.
1749 # This is what the copy_instance function() is for. It tries to copy a
1750 # dictionary field by field into a class, aborting if the class and dictionary
1751 # have different data members (except for any typemap member in the class;
1752 # that one is strictly for use by the MyWidget supperclass).
1754 # Once the object tree is set up, require user to choose novice or expert
1755 # mode and instantiate an edit object for the configuration. Class methods
1756 # will take it all from there.
1758 # Options (not documented because they're for fetchmailconf debuggers only):
1759 # -d: Read the configuration and dump it to stdout before editing. Dump
1760 # the edited result to stdout as well.
1761 # -f: specify the run control file to read.
1763 if __name__ == '__main__':
1765 if not os.environ.has_key("DISPLAY"):
1766 print "fetchmailconf must be run under X"
1769 fetchmail_icon = """
1770 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1771 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1772 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1773 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1774 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1775 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1776 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1777 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1778 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1779 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1780 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1781 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1782 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1783 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1784 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1785 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1786 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1787 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1788 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1789 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1790 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1791 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1792 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1793 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1794 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1795 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1796 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1797 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1798 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1799 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1800 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1801 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1803 # The base64 data in the string above was generated by the following procedure:
1806 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1810 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1811 dump = rcfile = None;
1812 for (switch, val) in options:
1813 if (switch == '-d'):
1815 elif (switch == '-f'):
1818 # Get client host's FQDN
1819 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1822 ConfigurationDefaults = Configuration()
1823 ServerDefaults = Server()
1824 UserDefaults = User()
1826 # Read the existing configuration
1827 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1829 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1831 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1836 print "`" + cmd + "' run failure, status " + `s`
1839 print "Unknown error while running fetchmail --configdump"
1846 print "Can't read configuration output of fetchmail --configdump."
1852 # The tricky part -- initializing objects from the configuration global
1853 # `Configuration' is the top level of the object tree we're going to mung.
1854 # The dictmembers list is used to track the set of fields the dictionary
1855 # contains; in particular, we can use it to tell whether things like the
1856 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1858 Fetchmailrc = Configuration()
1859 copy_instance(Fetchmailrc, fetchmailrc)
1860 Fetchmailrc.servers = [];
1861 for server in fetchmailrc['servers']:
1863 copy_instance(Newsite, server)
1864 Fetchmailrc.servers.append(Newsite)
1866 for user in server['users']:
1868 copy_instance(Newuser, user)
1869 Newsite.users.append(Newuser)
1871 # We may want to display the configuration and quit
1873 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1875 # The theory here is that -f alone sets the rcfile location,
1876 # but -d and -f together mean the new configuration should go to stdout.
1877 if not rcfile and not dump:
1878 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1880 # OK, now run the configuration edit
1881 root = MainWindow(rcfile)
1884 # The following sets edit modes for GNU EMACS