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, " usa.net") > 0:
1230 warnings = warnings + """
1231 You appear to be using USA.NET's free mail service. Their POP3 servers
1232 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1233 fetchmail can compensate. They seem to require that fetchall be switched on
1234 (otherwise you won't necessarily see all your mail, not even new mail).
1235 They also botch the TOP command the fetchmail normally uses for retrieval
1236 (it only retrieves about 10 lines rather than the number specified).
1237 Turning on fetchall will disable the use of TOP.
1239 Therefore, it is strongly recommended that you turn on `fetchall' on all
1240 user entries associated with this server.
1244 ### IMAP servers start here
1246 if string.find(greetline, "GroupWise") > 0:
1247 warnings = warnings + """
1248 The Novell GroupWise IMAP server would be better named GroupFoolish;
1249 it is (according to the designer of IMAP) unusably broken. Among
1250 other things, it doesn't include a required content length in its
1251 BODY[TEXT] response.<p>
1253 Fetchmail works around this problem, but we strongly recommend voting
1254 with your dollars for a server that isn't brain-dead. If you stick
1255 with code as shoddy as GroupWise seems to be, you will probably pay
1256 for it with other problems.<p>
1259 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1260 warnings = warnings + """
1261 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1262 set the messages's Seen flag. As a result, if you use the keep option the
1263 same messages will be downloaded over and over.
1266 if string.find(greetline, "InterChange") > 0:
1267 warnings = warnings + """
1268 The InterChange IMAP server screws up on mail with attachments. It
1269 doesn't fetch them if you give it a BODY[TEXT] request, though it
1270 does if you request RFC822.TEXT. According to the IMAP RFCs and their
1271 maintainer these should be equivalent -- and we can't drop the
1272 BODY[TEXT] form because M$ Exchange (quite legally under RFC2062)
1276 if string.find(greetline, "Imail") > 0:
1277 warnings = warnings + """
1278 We've seen a bug report indicating that this IMAP server (at least as of
1279 version 5.0.7) returns an invalid body size for messages with MIME
1280 attachments; the effect is to drop the attachments on the floor. We
1281 recommend you upgrade to a non-broken IMAP server.
1284 if string.find(greetline, "Domino IMAP4") > 0:
1285 warnings = warnings + """
1286 Your IMAP server appears to be Lotus Domino. This server, at least up
1287 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1288 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1289 will see attachments as part of the message text. If your Domino server's
1290 POP3 facility is enabled, we recommend you fall back on it.
1294 ### Checks for protocol variants start here
1296 closebrak = string.find(greetline, ">")
1297 if closebrak > 0 and greetline[closebrak+1] == "\r":
1298 warnings = warnings + """
1299 It looks like you could use APOP on this server and avoid sending it your
1300 password in clear. You should talk to the mailserver administrator about
1304 if string.find(greetline, "IMAP2bis") > 0:
1305 warnings = warnings + """
1306 IMAP2bis servers have a minor problem; they can't peek at messages without
1307 marking them seen. If you take a line hit during the retrieval, the
1308 interrupted message may get left on the server, marked seen.
1310 To work around this, it is recommended that you set the `fetchall'
1311 option on all user entries associated with this server, so any stuck
1312 mail will be retrieved next time around.
1314 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1315 a pointer to an open-source implementation.
1318 if string.find(greetline, "IMAP4rev1") > 0:
1319 warnings = warnings + """
1320 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1321 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1322 has therefore been extremely well tested with this class of server.
1326 warnings = warnings + """
1327 Fetchmail doesn't know anything special about this server type.
1331 # Display success window with warnings
1332 title = "Autoprobe of " + realhost + " succeeded"
1333 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1334 self.protocol.set(protocol)
1335 confwin.title(title)
1336 confwin.iconname(title)
1337 Label(confwin, text=title).pack()
1338 Message(confwin, text=confirm, width=600).pack()
1339 Button(confwin, text='Done',
1340 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1343 # User editing stuff
1347 'title' : 'User option help',
1348 'banner': 'User options',
1350 You may use this panel to set options
1351 that may differ between individual
1354 Once you have a user configuration set
1355 up as you like it, you can select `OK' to
1356 store it in the user list maintained in
1357 the site configuration window.
1359 If you wish to discard the changes you have
1360 made to user options, select `Quit'.
1364 'title' : 'Local name help',
1365 'banner': 'Local names',
1367 The local name(s) in a user entry are the
1368 people on the client machine who should
1369 receive mail from the poll described.
1371 Note: if a user entry has more than one
1372 local name, messages will be retrieved
1373 in multidrop mode. This complicates
1374 the configuration issues; see the manual
1375 page section on multidrop mode.
1378 class UserEdit(Frame, MyWidget):
1379 def __init__(self, username, parent):
1380 self.parent = parent
1382 for user in parent.server.users:
1383 if user.remote == username:
1385 if self.user == None:
1387 self.user.remote = username
1388 self.user.localnames = [username]
1389 parent.server.users.append(self.user)
1391 def edit(self, mode, master=None):
1392 Frame.__init__(self, master)
1394 self.master.title('Fetchmail user ' + self.user.remote
1395 + ' querying ' + self.parent.server.pollname);
1396 self.master.iconname('Fetchmail user ' + self.user.remote);
1397 self.post(User, 'user')
1398 self.makeWidgets(mode, self.parent.server.pollname)
1399 self.keepalive = [] # Use this to anchor the PhotoImage object
1400 make_icon_window(self, fetchmail_icon)
1403 # self.wait_window()
1407 # Yes, this test can fail -- if you delete the parent window.
1408 if self.parent.subwidgets.has_key(self.user.remote):
1409 del self.parent.subwidgets[self.user.remote]
1410 Widget.destroy(self.master)
1413 if ConfirmQuit(self, 'user option editing'):
1417 self.fetch(User, 'user')
1420 def makeWidgets(self, mode, servername):
1421 dispose_window(self,
1422 "User options for " + self.user.remote + " querying " + servername,
1425 if mode != 'novice':
1426 leftwin = Frame(self);
1430 secwin = Frame(leftwin, relief=RAISED, bd=5)
1431 Label(secwin, text="Authentication").pack(side=TOP)
1432 LabeledEntry(secwin, 'Password:',
1433 self.password, '12').pack(side=TOP, fill=X)
1434 secwin.pack(fill=X, anchor=N)
1436 if 'ssl' in feature_options or 'ssl' in dictmembers:
1437 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1438 Checkbutton(sslwin, text="Use SSL?",
1439 variable=self.ssl).pack(side=TOP, fill=X)
1440 LabeledEntry(sslwin, 'SSL key:',
1441 self.sslkey, '14').pack(side=TOP, fill=X)
1442 LabeledEntry(sslwin, 'SSL certificate:',
1443 self.sslcert, '14').pack(side=TOP, fill=X)
1444 sslwin.pack(fill=X, anchor=N)
1446 names = Frame(leftwin, relief=RAISED, bd=5)
1447 Label(names, text="Local names").pack(side=TOP)
1448 ListEdit("New name: ",
1449 self.user.localnames, None, None, names, localhelp)
1450 names.pack(fill=X, anchor=N)
1452 if mode != 'novice':
1453 targwin = Frame(leftwin, relief=RAISED, bd=5)
1454 Label(targwin, text="Forwarding Options").pack(side=TOP)
1455 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1456 ListEdit("New listener:",
1457 self.user.smtphunt, None, None, targwin, None)
1458 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1459 self.smtpaddress, '26').pack(side=TOP, fill=X)
1460 LabeledEntry(targwin, 'Connection setup command:',
1461 self.preconnect, '26').pack(side=TOP, fill=X)
1462 LabeledEntry(targwin, 'Connection wrapup command:',
1463 self.postconnect, '26').pack(side=TOP, fill=X)
1464 LabeledEntry(targwin, 'Local delivery agent:',
1465 self.mda, '26').pack(side=TOP, fill=X)
1466 LabeledEntry(targwin, 'BSMTP output file:',
1467 self.bsmtp, '26').pack(side=TOP, fill=X)
1468 LabeledEntry(targwin, 'Listener spam-block codes:',
1469 self.antispam, '26').pack(side=TOP, fill=X)
1470 LabeledEntry(targwin, 'Pass-through properties:',
1471 self.properties, '26').pack(side=TOP, fill=X)
1472 Checkbutton(targwin, text="Use LMTP?",
1473 variable=self.lmtp).pack(side=TOP, fill=X)
1474 targwin.pack(fill=X, anchor=N)
1476 if mode != 'novice':
1477 leftwin.pack(side=LEFT, fill=X, anchor=N)
1478 rightwin = Frame(self)
1482 optwin = Frame(rightwin, relief=RAISED, bd=5)
1483 Label(optwin, text="Processing Options").pack(side=TOP)
1484 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1485 variable=self.keep).pack(side=TOP, anchor=W)
1486 Checkbutton(optwin, text="Fetch old messages as well as new",
1487 variable=self.fetchall).pack(side=TOP, anchor=W)
1488 if mode != 'novice':
1489 Checkbutton(optwin, text="Flush seen messages before retrieval",
1490 variable=self.flush).pack(side=TOP, anchor=W)
1491 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1492 variable=self.rewrite).pack(side=TOP, anchor=W)
1493 Checkbutton(optwin, text="Force CR/LF at end of each line",
1494 variable=self.forcecr).pack(side=TOP, anchor=W)
1495 Checkbutton(optwin, text="Strip CR from end of each line",
1496 variable=self.stripcr).pack(side=TOP, anchor=W)
1497 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1498 variable=self.pass8bits).pack(side=TOP, anchor=W)
1499 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1500 variable=self.mimedecode).pack(side=TOP, anchor=W)
1501 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1502 variable=self.dropstatus).pack(side=TOP, anchor=W)
1505 if mode != 'novice':
1506 limwin = Frame(rightwin, relief=RAISED, bd=5)
1507 Label(limwin, text="Resource Limits").pack(side=TOP)
1508 LabeledEntry(limwin, 'Message size limit:',
1509 self.limit, '30').pack(side=TOP, fill=X)
1510 LabeledEntry(limwin, 'Size warning interval:',
1511 self.warnings, '30').pack(side=TOP, fill=X)
1512 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1513 self.fetchlimit, '30').pack(side=TOP, fill=X)
1514 LabeledEntry(limwin, 'Max messages to forward per poll:',
1515 self.batchlimit, '30').pack(side=TOP, fill=X)
1516 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1517 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1518 self.expunge, '30').pack(side=TOP, fill=X)
1519 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1520 variable=self.idle).pack(side=TOP, anchor=W)
1523 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1524 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1525 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1526 ListEdit("New folder:", self.user.mailboxes,
1527 None, None, foldwin, None)
1528 foldwin.pack(fill=X, anchor=N)
1530 if mode != 'novice':
1531 rightwin.pack(side=LEFT)
1537 # Top-level window that offers either novice or expert mode
1538 # (but not both at once; it disappears when one is selected).
1541 class Configurator(Frame):
1542 def __init__(self, outfile, master, onexit, parent):
1543 Frame.__init__(self, master)
1544 self.outfile = outfile
1545 self.onexit = onexit
1546 self.parent = parent
1547 self.master.title('fetchmail configurator');
1548 self.master.iconname('fetchmail configurator');
1550 self.keepalive = [] # Use this to anchor the PhotoImage object
1551 make_icon_window(self, fetchmail_icon)
1553 Message(self, text="""
1554 Use `Novice Configuration' for basic fetchmail setup;
1555 with this, you can easily set up a single-drop connection
1556 to one remote mail server.
1557 """, width=600).pack(side=TOP)
1558 Button(self, text='Novice Configuration',
1559 fg='blue', command=self.novice).pack()
1561 Message(self, text="""
1562 Use `Expert Configuration' for advanced fetchmail setup,
1563 including multiple-site or multidrop connections.
1564 """, width=600).pack(side=TOP)
1565 Button(self, text='Expert Configuration',
1566 fg='blue', command=self.expert).pack()
1568 Message(self, text="""
1569 Or you can just select `Quit' to leave the configurator now and
1570 return to the main panel.
1571 """, width=600).pack(side=TOP)
1572 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1573 master.protocol("WM_DELETE_WINDOW", self.leave)
1576 self.master.destroy()
1577 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1580 self.master.destroy()
1581 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1584 self.master.destroy()
1587 # Run a command in a scrolling text widget, displaying its output
1589 class RunWindow(Frame):
1590 def __init__(self, command, master, parent):
1591 Frame.__init__(self, master)
1592 self.master = master
1593 self.master.title('fetchmail run window');
1594 self.master.iconname('fetchmail run window');
1597 text="Running "+command,
1598 bd=2).pack(side=TOP, pady=10)
1599 self.keepalive = [] # Use this to anchor the PhotoImage object
1600 make_icon_window(self, fetchmail_icon)
1602 # This is a scrolling text window
1603 textframe = Frame(self)
1604 scroll = Scrollbar(textframe)
1605 self.textwidget = Text(textframe, setgrid=TRUE)
1606 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1607 self.textwidget.config(yscrollcommand=scroll.set)
1608 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1609 scroll.config(command=self.textwidget.yview)
1610 scroll.pack(side=RIGHT, fill=BOTH)
1611 textframe.pack(side=TOP)
1613 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1615 self.update() # Draw widget before executing fetchmail
1617 child_stdout = os.popen(command + " 2>&1", "r")
1619 ch = child_stdout.read(1)
1622 self.textwidget.insert(END, ch)
1623 self.textwidget.insert(END, "Done.")
1624 self.textwidget.see(END);
1627 Widget.destroy(self.master)
1629 # Here's where we choose either configuration or launching
1631 class MainWindow(Frame):
1632 def __init__(self, outfile, master=None):
1633 Frame.__init__(self, master)
1634 self.outfile = outfile
1635 self.master.title('fetchmail launcher');
1636 self.master.iconname('fetchmail launcher');
1639 text='Fetchmailconf ' + version,
1640 bd=2).pack(side=TOP, pady=10)
1641 self.keepalive = [] # Use this to anchor the PhotoImage object
1642 make_icon_window(self, fetchmail_icon)
1645 ## Test icon display with the following:
1646 # icon_image = PhotoImage(data=fetchmail_icon)
1647 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1648 # self.keepalive.append(icon_image)
1650 Message(self, text="""
1651 Use `Configure fetchmail' to tell fetchmail about the remote
1652 servers it should poll (the host name, your username there,
1653 whether to use POP or IMAP, and so forth).
1654 """, width=600).pack(side=TOP)
1655 self.configbutton = Button(self, text='Configure fetchmail',
1656 fg='blue', command=self.configure)
1657 self.configbutton.pack()
1659 Message(self, text="""
1660 Use `Test fetchmail' to run fetchmail with debugging enabled.
1661 This is a good way to test out a new configuration.
1662 """, width=600).pack(side=TOP)
1663 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1665 Message(self, text="""
1666 Use `Run fetchmail' to run fetchmail in foreground.
1667 Progress messages will be shown, but not debug messages.
1668 """, width=600).pack(side=TOP)
1669 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1671 Message(self, text="""
1672 Or you can just select `Quit' to exit the launcher now.
1673 """, width=600).pack(side=TOP)
1674 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1676 def configure(self):
1677 self.configbutton.configure(state=DISABLED)
1678 Configurator(self.outfile, Toplevel(),
1679 lambda self=self: self.configbutton.configure(state=NORMAL),
1682 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1685 RunWindow("fetchmail -d0", Toplevel(), self)
1690 # Functions for turning a dictionary into an instantiated object tree.
1692 def intersect(list1, list2):
1693 # Compute set intersection of lists
1700 def setdiff(list1, list2):
1701 # Compute set difference of lists
1708 def copy_instance(toclass, fromdict):
1709 # Initialize a class object of given type from a conformant dictionary.
1710 for fld in fromdict.keys():
1711 if not fld in dictmembers:
1712 dictmembers.append(fld)
1713 # The `optional' fields are the ones we can ignore for purposes of
1714 # conformability checking; they'll still get copied if they are
1715 # present in the dictionary.
1716 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1717 class_sig = setdiff(toclass.__dict__.keys(), optional)
1719 dict_keys = setdiff(fromdict.keys(), optional)
1721 common = intersect(class_sig, dict_keys)
1722 if 'typemap' in class_sig:
1723 class_sig.remove('typemap')
1724 if tuple(class_sig) != tuple(dict_keys):
1725 print "Fields don't match what fetchmailconf expected:"
1726 # print "Class signature: " + `class_sig`
1727 # print "Dictionary keys: " + `dict_keys`
1728 diff = setdiff(class_sig, common)
1730 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1731 diff = setdiff(dict_keys, common)
1733 print "Not matched in dictionary keys: " + `diff`
1736 for x in fromdict.keys():
1737 setattr(toclass, x, fromdict[x])
1740 # And this is the main sequence. How it works:
1742 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1743 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1744 # Run execfile on the file to pull fetchmailrc into Python global space.
1745 # You don't want static data, though; you want, instead, a tree of objects
1746 # with the same data members and added appropriate methods.
1748 # This is what the copy_instance function() is for. It tries to copy a
1749 # dictionary field by field into a class, aborting if the class and dictionary
1750 # have different data members (except for any typemap member in the class;
1751 # that one is strictly for use by the MyWidget supperclass).
1753 # Once the object tree is set up, require user to choose novice or expert
1754 # mode and instantiate an edit object for the configuration. Class methods
1755 # will take it all from there.
1757 # Options (not documented because they're for fetchmailconf debuggers only):
1758 # -d: Read the configuration and dump it to stdout before editing. Dump
1759 # the edited result to stdout as well.
1760 # -f: specify the run control file to read.
1762 if __name__ == '__main__':
1764 if not os.environ.has_key("DISPLAY"):
1765 print "fetchmailconf must be run under X"
1768 fetchmail_icon = """
1769 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1770 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1771 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1772 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1773 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1774 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1775 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1776 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1777 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1778 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1779 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1780 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1781 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1782 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1783 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1784 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1785 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1786 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1787 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1788 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1789 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1790 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1791 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1792 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1793 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1794 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1795 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1796 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1797 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1798 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1799 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1800 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1802 # The base64 data in the string above was generated by the following procedure:
1805 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1809 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1810 dump = rcfile = None;
1811 for (switch, val) in options:
1812 if (switch == '-d'):
1814 elif (switch == '-f'):
1817 # Get client host's FQDN
1818 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1821 ConfigurationDefaults = Configuration()
1822 ServerDefaults = Server()
1823 UserDefaults = User()
1825 # Read the existing configuration
1826 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1828 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1830 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1835 print "`" + cmd + "' run failure, status " + `s`
1838 print "Unknown error while running fetchmail --configdump"
1845 print "Can't read configuration output of fetchmail --configdump."
1851 # The tricky part -- initializing objects from the configuration global
1852 # `Configuration' is the top level of the object tree we're going to mung.
1853 # The dictmembers list is used to track the set of fields the dictionary
1854 # contains; in particular, we can use it to tell whether things like the
1855 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1857 Fetchmailrc = Configuration()
1858 copy_instance(Fetchmailrc, fetchmailrc)
1859 Fetchmailrc.servers = [];
1860 for server in fetchmailrc['servers']:
1862 copy_instance(Newsite, server)
1863 Fetchmailrc.servers.append(Newsite)
1865 for user in server['users']:
1867 copy_instance(Newuser, user)
1868 Newsite.users.append(Newuser)
1870 # We may want to display the configuration and quit
1872 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1874 # The theory here is that -f alone sets the rcfile location,
1875 # but -d and -f together mean the new configuration should go to stdout.
1876 if not rcfile and not dump:
1877 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1879 # OK, now run the configuration edit
1880 root = MainWindow(rcfile)
1883 # The following sets edit modes for GNU EMACS