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
91 self.principal = None # Kerberos principal
93 ('pollname', 'String'),
95 ('active', 'Boolean'),
97 ('protocol', 'String'),
100 ('preauth', 'String'),
102 ('envelope', 'String'),
104 ('qvirtual', 'String'),
107 # leave localdomains out
108 ('interface', 'String'),
109 ('monitor', 'String'),
110 ('plugin', 'String'),
111 ('plugout', 'String'),
112 ('netsec', 'String'),
113 ('principal', 'String'))
115 def dump(self, folded):
117 if self.active: res = res + "poll"
118 else: res = res + "skip"
119 res = res + (" " + self.pollname)
121 res = res + (" via " + str(self.via) + "\n");
122 if self.protocol != ServerDefaults.protocol:
123 res = res + " with proto " + self.protocol
124 if self.port != defaultports[self.protocol] and self.port != 0:
125 res = res + " port " + `self.port`
126 if self.timeout != ServerDefaults.timeout:
127 res = res + " timeout " + `self.timeout`
128 if self.interval != ServerDefaults.interval:
129 res = res + " interval " + `self.interval`
130 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
132 res = res + " envelope " + `self.envskip` + " " + self.envelope
134 res = res + " envelope " + self.envelope
136 res = res + (" qvirtual " + str(self.qvirtual) + "\n");
137 if self.preauth != ServerDefaults.preauth:
138 res = res + " preauth " + self.preauth
139 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
140 res = res + " and options"
141 if self.dns != ServerDefaults.dns:
142 res = res + flag2str(self.dns, 'dns')
143 if self.uidl != ServerDefaults.uidl:
144 res = res + flag2str(self.uidl, 'uidl')
145 if folded: res = res + "\n "
146 else: res = res + " "
152 if self.aka and self.localdomains: res = res + " "
153 if self.localdomains:
154 res = res + ("localdomains")
155 for x in self.localdomains:
157 if (self.aka or self.localdomains):
164 res = res + "interface " + str(self.interface)
166 res = res + "monitor " + str(self.monitor)
168 res = res + "netsec " + str(self.netsec)
169 if self.interface or self.monitor or self.netsec:
173 if res[-1] == " ": res = res[0:-1]
175 for user in self.users:
176 res = res + repr(user)
180 def __delitem__(self, name):
181 for ui in range(len(self.users)):
182 if self.users[ui].remote == name:
187 return self.dump(TRUE)
190 return "[Server: " + self.dump(FALSE) + "]"
194 if os.environ.has_key("USER"):
195 self.remote = os.environ["USER"] # Remote username
196 elif os.environ.has_key("LOGNAME"):
197 self.remote = os.environ["LOGNAME"]
199 print "Can't get your username!"
201 self.localnames = [self.remote,]# Local names
202 self.password = None # Password for mail account access
203 self.mailboxes = [] # Remote folders to retrieve from
204 self.smtphunt = [] # Hosts to forward to
205 self.smtpaddress = None # Append this to MAIL FROM line
206 self.smtpname = None # Use this for RCPT TO
207 self.preconnect = None # Connection setup
208 self.postconnect = None # Connection wrapup
209 self.mda = None # Mail Delivery Agent
210 self.bsmtp = None # BSMTP output file
211 self.lmtp = FALSE # Use LMTP rather than SMTP?
212 self.antispam = "571 550 501" # Listener's spam-block code
213 self.keep = FALSE # Keep messages
214 self.flush = FALSE # Flush messages
215 self.fetchall = FALSE # Fetch old messages
216 self.rewrite = TRUE # Rewrite message headers
217 self.forcecr = FALSE # Force LF -> CR/LF
218 self.stripcr = FALSE # Strip CR
219 self.pass8bits = FALSE # Force BODY=7BIT
220 self.mimedecode = FALSE # Undo MIME armoring
221 self.dropstatus = FALSE # Drop incoming Status lines
222 self.dropdelivered = FALSE # Drop incoming Delivered-To lines
223 self.idle = FALSE # IDLE after poll
224 self.limit = 0 # Message size limit
225 self.warnings = 0 # Size warning interval
226 self.fetchlimit = 0 # Max messages fetched per batch
227 self.batchlimit = 0 # Max message forwarded per batch
228 self.expunge = 0 # Interval between expunges (IMAP)
229 self.ssl = 0 # Enable Seccure Socket Layer
230 self.sslkey = None # SSL key filename
231 self.sslcert = None # SSL certificate filename
232 self.properties = None # Extension properties
234 ('remote', 'String'),
235 # leave out mailboxes and localnames
236 ('password', 'String'),
238 ('smtpaddress', 'String'),
239 ('smtpname', 'String'),
240 ('preconnect', 'String'),
241 ('postconnect', 'String'),
245 ('antispam', 'String'),
247 ('flush', 'Boolean'),
248 ('fetchall', 'Boolean'),
249 ('rewrite', 'Boolean'),
250 ('forcecr', 'Boolean'),
251 ('stripcr', 'Boolean'),
252 ('pass8bits', 'Boolean'),
253 ('mimedecode', 'Boolean'),
254 ('dropstatus', 'Boolean'),
255 ('dropdelivered', 'Boolean'),
259 ('fetchlimit', 'Int'),
260 ('batchlimit', 'Int'),
263 ('sslkey', 'String'),
264 ('sslcert', 'String'),
265 ('properties', 'String'))
269 res = res + "user " + `self.remote` + " there ";
271 res = res + "with password " + `self.password` + " "
274 for x in self.localnames:
277 if (self.keep != UserDefaults.keep
278 or self.flush != UserDefaults.flush
279 or self.fetchall != UserDefaults.fetchall
280 or self.rewrite != UserDefaults.rewrite
281 or self.forcecr != UserDefaults.forcecr
282 or self.stripcr != UserDefaults.stripcr
283 or self.pass8bits != UserDefaults.pass8bits
284 or self.mimedecode != UserDefaults.mimedecode
285 or self.dropstatus != UserDefaults.dropstatus
286 or self.dropdelivered != UserDefaults.dropdelivered
287 or self.idle != UserDefaults.idle):
288 res = res + " options"
289 if self.keep != UserDefaults.keep:
290 res = res + flag2str(self.keep, 'keep')
291 if self.flush != UserDefaults.flush:
292 res = res + flag2str(self.flush, 'flush')
293 if self.fetchall != UserDefaults.fetchall:
294 res = res + flag2str(self.fetchall, 'fetchall')
295 if self.rewrite != UserDefaults.rewrite:
296 res = res + flag2str(self.rewrite, 'rewrite')
297 if self.forcecr != UserDefaults.forcecr:
298 res = res + flag2str(self.forcecr, 'forcecr')
299 if self.stripcr != UserDefaults.stripcr:
300 res = res + flag2str(self.stripcr, 'stripcr')
301 if self.pass8bits != UserDefaults.pass8bits:
302 res = res + flag2str(self.pass8bits, 'pass8bits')
303 if self.mimedecode != UserDefaults.mimedecode:
304 res = res + flag2str(self.mimedecode, 'mimedecode')
305 if self.dropstatus != UserDefaults.dropstatus:
306 res = res + flag2str(self.dropstatus, 'dropstatus')
307 if self.dropdelivered != UserDefaults.dropdelivered:
308 res = res + flag2str(self.dropdelivered, 'dropdelivered')
309 if self.idle != UserDefaults.idle:
310 res = res + flag2str(self.idle, 'idle')
311 if self.limit != UserDefaults.limit:
312 res = res + " limit " + `self.limit`
313 if self.warnings != UserDefaults.warnings:
314 res = res + " warnings " + `self.warnings`
315 if self.fetchlimit != UserDefaults.fetchlimit:
316 res = res + " fetchlimit " + `self.fetchlimit`
317 if self.batchlimit != UserDefaults.batchlimit:
318 res = res + " batchlimit " + `self.batchlimit`
319 if self.ssl and self.ssl != UserDefaults.ssl:
320 res = res + flag2str(self.ssl, 'ssl')
321 if self.sslkey and self.sslkey != UserDefaults.sslkey:
322 res = res + " sslkey " + `self.sslkey`
323 if self.sslcert and self.sslcert != UserDefaults.sslcert:
324 res = res + " sslcert " + `self.sslcert`
325 if self.principal and self.principal != UserDefaults.principal:
326 res = res + " principal " + `self.principal`
327 if self.expunge != UserDefaults.expunge:
328 res = res + " expunge " + `self.expunge`
330 trimmed = self.smtphunt;
331 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
332 trimmed = trimmed[0:len(trimmed) - 1]
333 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
334 trimmed = trimmed[0:len(trimmed) - 1]
336 res = res + " smtphost "
341 res = res + " folder"
342 for x in self.mailboxes:
345 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
346 if getattr(self, fld):
347 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
348 if self.lmtp != UserDefaults.lmtp:
349 res = res + flag2str(self.lmtp, 'lmtp')
350 if self.antispam != UserDefaults.antispam:
351 res = res + " antispam " + self.antispam + "\n"
355 return "[User: " + repr(self) + "]"
361 defaultports = {"auto":0,
371 preauthlist = ("password", "kerberos", "ssh")
374 'title' : 'List Selection Help',
375 'banner': 'List Selection',
377 You must select an item in the list box (by clicking on it).
380 def flag2str(value, string):
381 # make a string representation of a .fetchmailrc flag or negated flag
385 if value == FALSE: str = str + ("no ")
389 class LabeledEntry(Frame):
390 # widget consisting of entry field with caption to left
391 def bind(self, key, action):
392 self.E.bind(key, action)
395 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
396 Frame.__init__(self, Master)
397 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
398 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
399 self.L.pack({'side':'left'})
400 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
402 def ButtonBar(frame, legend, ref, alternatives, depth, command):
403 # array of radio buttons, caption to left, picking from a string list
405 width = len(alternatives) / depth;
406 Label(bar, text=legend).pack(side=LEFT)
407 for column in range(width):
408 subframe = Frame(bar)
409 for row in range(depth):
410 ind = width * row + column
411 Radiobutton(subframe,
412 {'text':alternatives[ind],
414 'value':alternatives[ind],
415 'command':command}).pack(side=TOP, anchor=W)
416 subframe.pack(side=LEFT)
420 def helpwin(helpdict):
421 # help message window with a self-destruct button
423 helpwin.title(helpdict['title'])
424 helpwin.iconname(helpdict['title'])
425 Label(helpwin, text=helpdict['banner']).pack()
426 textframe = Frame(helpwin)
427 scroll = Scrollbar(textframe)
428 helpwin.textwidget = Text(textframe, setgrid=TRUE)
429 textframe.pack(side=TOP, expand=YES, fill=BOTH)
430 helpwin.textwidget.config(yscrollcommand=scroll.set)
431 helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
432 scroll.config(command=helpwin.textwidget.yview)
433 scroll.pack(side=RIGHT, fill=BOTH)
434 helpwin.textwidget.insert(END, helpdict['text']);
435 Button(helpwin, text='Done',
436 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
437 textframe.pack(side=TOP)
439 def make_icon_window(base, image):
441 # Some older pythons will error out on this
442 icon_image = PhotoImage(data=image)
443 icon_window = Toplevel()
444 Label(icon_window, image=icon_image, bg='black').pack()
445 base.master.iconwindow(icon_window)
446 # Avoid TkInter brain death. PhotoImage objects go out of
447 # scope when the enclosing function returns. Therefore
448 # we have to explicitly link them to something.
449 base.keepalive.append(icon_image)
453 class ListEdit(Frame):
454 # edit a list of values (duplicates not allowed) with a supplied editor hook
455 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
457 self.deletor = deletor
460 # Set up a widget to accept new elements
461 self.newval = StringVar(master)
462 newwin = LabeledEntry(master, newlegend, self.newval, '12')
463 newwin.bind('<Double-1>', self.handleNew)
464 newwin.bind('<Return>', self.handleNew)
465 newwin.pack(side=TOP, fill=X, anchor=E)
467 # Edit the existing list
468 listframe = Frame(master)
469 scroll = Scrollbar(listframe)
470 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
473 self.listwidget.insert(END, x)
474 listframe.pack(side=TOP, expand=YES, fill=BOTH)
475 self.listwidget.config(yscrollcommand=scroll.set)
476 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
477 scroll.config(command=self.listwidget.yview)
478 scroll.pack(side=RIGHT, fill=BOTH)
479 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
480 self.listwidget.bind('<Double-1>', self.handleList);
481 self.listwidget.bind('<Return>', self.handleList);
485 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
486 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
488 self.helptxt = helptxt
489 Button(bf, text='Help', fg='blue',
490 command=self.help).pack(side=RIGHT)
494 helpwin(self.helptxt)
496 def handleList(self, event):
499 def handleNew(self, event):
500 item = self.newval.get()
502 entire = self.listwidget.get(0, self.listwidget.index('end'));
503 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
504 self.listwidget.insert('end', item)
505 if self.list != None: self.list.append(item)
507 apply(self.editor, (item,))
511 select = self.listwidget.curselection()
516 if index and self.editor:
517 label = self.listwidget.get(index);
519 apply(self.editor, (label,))
521 def deleteItem(self):
522 select = self.listwidget.curselection()
526 index = string.atoi(select[0])
527 label = self.listwidget.get(index);
528 self.listwidget.delete(index)
529 if self.list != None:
531 if self.deletor != None:
532 apply(self.deletor, (label,))
534 def ConfirmQuit(frame, context):
537 text = 'Really quit ' + context + ' without saving?',
539 strings = ('Yes', 'No'),
543 def dispose_window(master, legend, help, savelegend='OK'):
544 dispose = Frame(master, relief=RAISED, bd=5)
545 Label(dispose, text=legend).pack(side=TOP,pady=10)
546 Button(dispose, text=savelegend, fg='blue',
547 command=master.save).pack(side=LEFT)
548 Button(dispose, text='Quit', fg='blue',
549 command=master.nosave).pack(side=LEFT)
550 Button(dispose, text='Help', fg='blue',
551 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
556 # Common methods for Tkinter widgets -- deals with Tkinter declaration
557 def post(self, widgetclass, field):
558 for x in widgetclass.typemap:
559 if x[1] == 'Boolean':
560 setattr(self, x[0], BooleanVar(self))
561 elif x[1] == 'String':
562 setattr(self, x[0], StringVar(self))
564 setattr(self, x[0], IntVar(self))
565 source = getattr(getattr(self, field), x[0])
567 getattr(self, x[0]).set(source)
569 def fetch(self, widgetclass, field):
570 for x in widgetclass.typemap:
571 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
574 # First, code to set the global fetchmail run controls.
577 configure_novice_help = {
578 'title' : 'Fetchmail novice configurator help',
579 'banner': 'Novice configurator help',
581 In the `Novice Configurator Controls' panel, you can:
583 Press `Save' to save the new fetchmail configuration you have created.
584 Press `Quit' to exit without saving.
585 Press `Help' to bring up this help message.
587 In the `Novice Configuration' panels, you will set up the basic data
588 needed to create a simple fetchmail setup. These include:
590 1. The name of the remote site you want to query.
592 2. Your login name on that site.
594 3. Your password on that site.
596 4. A protocol to use (POP, IMAP, ETRN, etc.)
598 5. A polling interval.
600 6. Options to fetch old messages as well as new, uor to suppress
601 deletion of fetched message.
603 The novice-configuration code will assume that you want to forward mail
604 to a local sendmail listener with no special options.
607 configure_expert_help = {
608 'title' : 'Fetchmail expert configurator help',
609 'banner': 'Expert configurator help',
611 In the `Expert Configurator Controls' panel, you can:
613 Press `Save' to save the new fetchmail configuration you have edited.
614 Press `Quit' to exit without saving.
615 Press `Help' to bring up this help message.
617 In the `Run Controls' panel, you can set the following options that
618 control how fetchmail runs:
621 Number of seconds to wait between polls in the background.
622 If zero, fetchmail will run in foreground.
625 If empty, emit progress and error messages to stderr.
626 Otherwise this gives the name of the files to write to.
627 This field is ignored if the "Log to syslog?" option is on.
630 If empty, store seen-message IDs in .fetchids under user's home
631 directory. If nonempty, use given file name.
634 Who to send multidrop mail to as a last resort if no address can
635 be matched. Normally empty; in this case, fetchmail treats the
636 invoking user as the address of last resort unless that user is
637 root. If that user is root, fetchmail sends to `postmaster'.
640 If this option is on (the default) error mail goes to the sender.
641 Otherwise it goes to the postmaster.
644 If false (the default) fetchmail generates a Received line into
645 each message and generates a HELO from the machine it is running on.
646 If true, fetchmail generates no Received line and HELOs as if it were
649 In the `Remote Mail Configurations' panel, you can:
651 1. Enter the name of a new remote mail server you want fetchmail to query.
653 To do this, simply enter a label for the poll configuration in the
654 `New Server:' box. The label should be a DNS name of the server (unless
655 you are using ssh or some other tunneling method and will fill in the `via'
656 option on the site configuration screen).
658 2. Change the configuration of an existing site.
660 To do this, find the site's label in the listbox and double-click it.
661 This will take you to a site configuration dialogue.
665 class ConfigurationEdit(Frame, MyWidget):
666 def __init__(self, configuration, outfile, master, onexit):
668 self.configuration = configuration
669 self.outfile = outfile
670 self.container = master
672 ConfigurationEdit.mode_to_help = {
673 'novice':configure_novice_help, 'expert':configure_expert_help
676 def server_edit(self, sitename):
677 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
679 def server_delete(self, sitename):
681 for user in self.subwidgets.keys():
683 del self.configuration[sitename]
687 def edit(self, mode):
689 Frame.__init__(self, self.container)
690 self.master.title('fetchmail ' + self.mode + ' configurator');
691 self.master.iconname('fetchmail ' + self.mode + ' configurator');
692 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
693 self.keepalive = [] # Use this to anchor the PhotoImage object
694 make_icon_window(self, fetchmail_icon)
696 self.post(Configuration, 'configuration')
699 'Configurator ' + self.mode + ' Controls',
700 ConfigurationEdit.mode_to_help[self.mode],
703 gf = Frame(self, relief=RAISED, bd = 5)
705 text='Fetchmail Run Controls',
706 bd=2).pack(side=TOP, pady=10)
711 if self.mode != 'novice':
713 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
714 log.pack(side=RIGHT, anchor=E)
716 # Set the poll interval
717 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
718 de.pack(side=RIGHT, anchor=E)
723 if self.mode != 'novice':
726 {'text':'Bounces to sender?',
727 'variable':self.bouncemail,
728 'relief':GROOVE}).pack(side=LEFT, anchor=W)
733 {'text':'Log to syslog?',
734 'variable':self.syslog,
735 'relief':GROOVE}).pack(side=LEFT, anchor=W)
736 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
737 log.pack(side=RIGHT, anchor=E)
741 {'text':'Invisible mode?',
742 'variable':self.invisible,
743 'relief':GROOVE}).pack(side=LEFT, anchor=W)
745 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
746 log.pack(side=RIGHT, anchor=E)
750 # Expert mode allows us to edit multiple sites
751 lf = Frame(self, relief=RAISED, bd=5)
753 text='Remote Mail Server Configurations',
754 bd=2).pack(side=TOP, pady=10)
755 ListEdit('New Server:',
756 map(lambda x: x.pollname, self.configuration.servers),
757 lambda site, self=self: self.server_edit(site),
758 lambda site, self=self: self.server_delete(site),
763 for sitename in self.subwidgets.keys():
764 self.subwidgets[sitename].destruct()
765 self.master.destroy()
769 if ConfirmQuit(self, self.mode + " configuration editor"):
773 for sitename in self.subwidgets.keys():
774 self.subwidgets[sitename].save()
775 self.fetch(Configuration, 'configuration')
779 elif not os.path.isfile(self.outfile) or Dialog(self,
780 title = 'Overwrite existing run control file?',
781 text = 'Really overwrite existing run control file?',
783 strings = ('Yes', 'No'),
784 default = 1).num == 0:
785 fm = open(self.outfile, 'w')
787 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
788 fm.write(`self.configuration`)
792 os.chmod(self.outfile, 0600)
796 # Server editing stuff.
799 'title' : 'Remote site help',
800 'banner': 'Remote sites',
802 When you add a site name to the list here,
803 you initialize an entry telling fetchmail
804 how to poll a new site.
806 When you select a sitename (by double-
807 clicking it, or by single-clicking to
808 select and then clicking the Edit button),
809 you will open a window to configure that
814 'title' : 'Server options help',
815 'banner': 'Server Options',
817 The server options screen controls fetchmail
818 options that apply to one of your mailservers.
820 Once you have a mailserver configuration set
821 up as you like it, you can select `OK' to
822 store it in the server list maintained in
823 the main configuration window.
825 If you wish to discard changes to a server
826 configuration, select `Quit'.
830 'title' : 'Run Control help',
831 'banner': 'Run Controls',
833 If the `Poll normally' checkbox is on, the host is polled as part of
834 the normal operation of fetchmail when it is run with no arguments.
835 If it is off, fetchmail will only query this host when it is given as
836 a command-line argument.
838 The `True name of server' box should specify the actual DNS name
839 to query. By default this is the same as the poll name.
841 Normally each host described in the file is queried once each
842 poll cycle. If `Cycles to skip between polls' is greater than 0,
843 that's the number of poll cycles that are skipped between the
844 times this post is actually polled.
846 The `Server timeout' is the number of seconds fetchmail will wait
847 for a reply from the mailserver before concluding it is hung and
852 'title' : 'Protocol and Port help',
853 'banner': 'Protocol and Port',
855 These options control the remote-mail protocol
856 and TCP/IP service port used to query this
859 If you click the `Probe for supported protocols'
860 button, fetchmail will try to find you the most
861 capable server on the selected host (this will
862 only work if you're conncted to the Internet).
863 The probe only checks for ordinary IMAP and POP
864 protocols; fortunately these are the most
865 frequently supported.
867 The `Protocol' button bar offers you a choice of
868 all the different protocols available. The `auto'
869 protocol is the default mode; it probes the host
870 ports for POP3 and IMAP to see if either is
873 Normally the TCP/IP service port to use is
874 dictated by the protocol choice. The `Port'
875 field (only present in expert mode) lets you
876 set a non-standard port.
880 'title' : 'Security option help',
881 'banner': 'Security',
883 The `interface' option allows you to specify a range
884 of IP addresses to monitor for activity. If these
885 addresses are not active, fetchmail will not poll.
886 Specifying this may protect you from a spoofing attack
887 if your client machine has more than one IP gateway
888 address and some of the gateways are to insecure nets.
890 The `monitor' option, if given, specifies the only
891 device through which fetchmail is permitted to connect
892 to servers. This option may be used to prevent
893 fetchmail from triggering an expensive dial-out if the
894 interface is not already active.
896 The `interface' and `monitor' options are available
897 only for Linux and freeBSD systems. See the fetchmail
898 manual page for details on these.
900 The ssl option enables SSL communication with a mailserver
901 supporting Secure Sockets Layer. The sslkey and sslcert options
902 declare key and certificate files for use with SSL.
904 The `netsec' option will be configurable only if fetchmail
905 was compiled with IPV6 support. If you need to use it,
906 you probably know what to do.
910 'title' : 'Multidrop option help',
911 'banner': 'Multidrop',
913 These options are only useful with multidrop mode.
914 See the manual page for extended discussion.
918 'title' : 'User list help',
919 'banner': 'User list',
921 When you add a user name to the list here,
922 you initialize an entry telling fetchmail
923 to poll the site on behalf of the new user.
925 When you select a username (by double-
926 clicking it, or by single-clicking to
927 select and then clicking the Edit button),
928 you will open a window to configure the
929 user's options on that site.
932 class ServerEdit(Frame, MyWidget):
933 def __init__(self, host, parent):
937 for site in parent.configuration.servers:
938 if site.pollname == host:
940 if (self.server == None):
941 self.server = Server()
942 self.server.pollname = host
943 self.server.via = None
944 parent.configuration.servers.append(self.server)
946 def edit(self, mode, master=None):
947 Frame.__init__(self, master)
949 self.master.title('Fetchmail host ' + self.server.pollname);
950 self.master.iconname('Fetchmail host ' + self.server.pollname);
951 self.post(Server, 'server')
952 self.makeWidgets(self.server.pollname, mode)
953 self.keepalive = [] # Use this to anchor the PhotoImage object
954 make_icon_window(self, fetchmail_icon)
961 for username in self.subwidgets.keys():
962 self.subwidgets[username].destruct()
963 del self.parent.subwidgets[self.server.pollname]
964 Widget.destroy(self.master)
967 if ConfirmQuit(self, 'server option editing'):
971 self.fetch(Server, 'server')
972 for username in self.subwidgets.keys():
973 self.subwidgets[username].save()
976 def refreshPort(self):
977 proto = self.protocol.get()
978 # We used to only reset the port if it had a default (zero) value.
979 # This turns out to be a bad idea especially in Novice mode -- if
980 # you set POP3 and then set IMAP, the port invisibly remained 110.
981 # Now we reset unconditionally on the theory that if you're setting
982 # a custom port number you should be in expert mode and playing
983 # close enough attention to notice this...
984 self.port.set(defaultports[proto])
985 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
987 def user_edit(self, username, mode):
988 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
990 def user_delete(self, username):
991 if self.subwidgets.has_key(username):
992 self.subwidgets[username].destruct()
993 del self.server[username]
995 def makeWidgets(self, host, mode):
996 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
998 leftwin = Frame(self);
1001 if mode != 'novice':
1002 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
1003 Label(ctlwin, text="Run Controls").pack(side=TOP)
1004 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
1005 LabeledEntry(ctlwin, 'True name of ' + host + ':',
1006 self.via, leftwidth).pack(side=TOP, fill=X)
1007 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
1008 self.interval, leftwidth).pack(side=TOP, fill=X)
1009 LabeledEntry(ctlwin, 'Server timeout (seconds):',
1010 self.timeout, leftwidth).pack(side=TOP, fill=X)
1011 Button(ctlwin, text='Help', fg='blue',
1012 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
1015 # Compute the available protocols from the compile-time options
1016 protolist = ['auto']
1017 if 'pop2' in feature_options:
1018 protolist.append("POP2")
1019 if 'pop3' in feature_options:
1020 protolist = protolist + ["POP3", "APOP", "KPOP"]
1021 if 'sdps' in feature_options:
1022 protolist.append("SDPS")
1023 if 'imap' in feature_options:
1024 protolist.append("IMAP")
1025 if 'imap-gss' in feature_options:
1026 protolist.append("IMAP-GSS")
1027 if 'imap-k4' in feature_options:
1028 protolist.append("IMAP-K4")
1029 if 'etrn' in feature_options:
1030 protolist.append("ETRN")
1032 protwin = Frame(leftwin, relief=RAISED, bd=5)
1033 Label(protwin, text="Protocol").pack(side=TOP)
1034 ButtonBar(protwin, '',
1035 self.protocol, protolist, 2,
1037 if mode != 'novice':
1038 LabeledEntry(protwin, 'On server TCP/IP port:',
1039 self.port, leftwidth).pack(side=TOP, fill=X)
1041 Checkbutton(protwin,
1042 text="POP3: track `seen' with client-side UIDLs?",
1043 variable=self.uidl).pack(side=TOP)
1044 Button(protwin, text='Probe for supported protocols', fg='blue',
1045 command=self.autoprobe).pack(side=LEFT)
1046 Button(protwin, text='Help', fg='blue',
1047 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1048 protwin.pack(fill=X)
1050 userwin = Frame(leftwin, relief=RAISED, bd=5)
1051 Label(userwin, text="User entries for " + host).pack(side=TOP)
1052 ListEdit("New user: ",
1053 map(lambda x: x.remote, self.server.users),
1054 lambda u, m=mode, s=self: s.user_edit(u, m),
1055 lambda u, s=self: s.user_delete(u),
1057 userwin.pack(fill=X)
1059 leftwin.pack(side=LEFT, anchor=N, fill=X);
1061 if mode != 'novice':
1062 rightwin = Frame(self);
1064 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1065 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1066 LabeledEntry(mdropwin, 'Envelope address header:',
1067 self.envelope, '22').pack(side=TOP, fill=X)
1068 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1069 self.envskip, '22').pack(side=TOP, fill=X)
1070 LabeledEntry(mdropwin, 'Name prefix to strip:',
1071 self.qvirtual, '22').pack(side=TOP, fill=X)
1072 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1073 variable=self.dns).pack(side=TOP)
1074 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1075 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1076 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1077 ListEdit("New domain: ",
1078 self.server.localdomains, None, None, mdropwin, multihelp)
1079 mdropwin.pack(fill=X)
1081 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1082 secwin = Frame(rightwin, relief=RAISED, bd=5)
1083 Label(secwin, text="Security").pack(side=TOP)
1084 # Don't actually let users set this. KPOP sets it implicitly
1085 # ButtonBar(secwin, 'Preauthorization mode:',
1086 # self.preauth, preauthlist, 1, None).pack(side=TOP)
1087 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1088 LabeledEntry(secwin, 'IP range to check before poll:',
1089 self.interface, leftwidth).pack(side=TOP, fill=X)
1090 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1091 LabeledEntry(secwin, 'Interface to monitor:',
1092 self.monitor, leftwidth).pack(side=TOP, fill=X)
1093 if 'netsec' in feature_options or 'netsec' in dictmembers:
1094 LabeledEntry(secwin, 'IPV6 security options:',
1095 self.netsec, leftwidth).pack(side=TOP, fill=X)
1096 Button(secwin, text='Help', fg='blue',
1097 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1100 rightwin.pack(side=LEFT, anchor=N);
1102 def autoprobe(self):
1103 # Note: this only handles case (1) near fetchmail.c:1032
1104 # We're assuming people smart enough to set up ssh tunneling
1105 # won't need autoprobing.
1107 realhost = self.server.via
1109 realhost = self.server.pollname
1111 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1112 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1114 sock.connect(realhost, port)
1115 greetline = sock.recv(1024)
1121 confwin = Toplevel()
1122 if greetline == None:
1123 title = "Autoprobe of " + realhost + " failed"
1125 Fetchmailconf didn't find any mailservers active.
1126 This could mean the host doesn't support any,
1127 or that your Internet connection is down, or
1128 that the host is so slow that the probe timed
1129 out before getting a response.
1133 # OK, now try to recognize potential problems
1135 if protocol == "POP2":
1136 warnings = warnings + """
1137 It appears you have somehow found a mailserver running only POP2.
1138 Congratulations. Have you considered a career in archaeology?
1140 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1141 Unless the first line of your fetchmail -V output includes the string "POP2",
1142 you'll have to build it from sources yourself with the configure
1143 switch --enable-POP2.
1147 ### POP3 servers start here
1149 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1150 warnings = warnings + """
1151 This appears to be an old version of the UC Davis POP server. These are
1152 dangerously unreliable (among other problems, they may drop your mailbox
1153 on the floor if your connection is interrupted during the session).
1155 It is strongly recommended that you find a better POP3 server. The fetchmail
1156 FAQ includes pointers to good ones.
1159 # Steve VanDevender <stevev@efn.org> writes:
1160 # The only system I have seen this happen with is cucipop-1.31
1161 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1162 # 2.x and probably quite a few other systems. It appears to be a
1163 # bug or bad interaction with the SunOS realloc() -- it turns out
1164 # that internally cucipop does allocate a certain data structure in
1165 # multiples of 16, using realloc() to bump it up to the next
1166 # multiple if it needs more.
1168 # The distinctive symptom is that when there are 16 messages in the
1169 # inbox, you can RETR and DELE all 16 messages successfully, but on
1170 # QUIT cucipop returns something like "-ERR Error locking your
1171 # mailbox" and aborts without updating it.
1173 # The cucipop banner looks like:
1175 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1177 if string.find(greetline, "Cubic Circle") > 0:
1178 warnings = warnings + """
1179 I see your server is running cucipop. Better make sure the server box
1180 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1181 under that version, and doesn't cope with the result gracefully. Newer
1182 SunOS and Solaris machines run cucipop OK.
1185 if string.find(greetline, "David POP3 Server") > 0:
1186 warnings = warnings + """
1187 This POP3 serrver is badly broken. You should get rid of it -- and the
1188 brain-dead NT operating system it rode in on.
1191 # The greeting line on the server known to be buggy is:
1192 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1194 if string.find(greetline, "FTGate") > 0:
1195 warnings = warnings + """
1196 This POP server has a weird bug; it says OK twice in response to TOP.
1197 Its response to RETR is normal, so use the `fetchall' option.
1200 if string.find(greetline, "OpenMail") > 0:
1201 warnings = warnings + """
1202 You appear to be using some version of HP OpenMail. Many versions of
1203 OpenMail do not process the "TOP" command correctly; the symptom is that
1204 only the header and first line of each message is retrieved. To work
1205 around this bug, turn on `fetchall' on all user entries associated with
1209 if string.find(greetline, "POP-Max") > 0:
1210 warnings = warnings + """
1211 The Mail Max POP3 server screws up on mail with attachments. It
1212 reports the message size with attachments included, but doesn't
1213 download them on a RETR or TOP (this violates the IMAP RFCs). It also
1214 doesn't implement TOP correctly. You should get rid of it -- and the
1215 brain-dead NT server it rode in on.
1218 if string.find(greetline, "POP3 Server Ready") > 0:
1219 warnings = warnings + """
1220 Some server that uses this greeting line has been observed to choke on
1221 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1224 if string.find(greetline, "QPOP") > 0:
1225 warnings = warnings + """
1226 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1227 knows all about qpopper. However, be aware that the 2.53 version of
1228 qpopper does something odd that causes fetchmail to hang with a socket
1229 error on very large messages. This is probably not a fetchmail bug, as
1230 it has been observed with fetchpop. The fix is to upgrade to qpopper
1231 3.0beta or a more recent version. Better yet, switch to IMAP.
1234 if string.find(greetline, " sprynet.com") > 0:
1235 warnings = warnings + """
1236 You appear to be using a SpryNet server. In mid-1999 it was reported that
1237 the SpryNet TOP command marks messages seen. Therefore, for proper error
1238 recovery in the event of a line drop, it is strongly recommended that you
1239 turn on `fetchall' on all user entries associated with this server.
1242 if string.find(greetline, "TEMS POP3") > 0:
1243 warnings = warnings + """
1244 Your POP3 server has "TEMS" in its header line. At least one such
1245 server does not process the "TOP" command correctly; the symptom is
1246 that fetchmail hangs when trying to retrieve mail. To work around
1247 this bug, turn on `fetchall' on all user entries associated with this
1251 if string.find(greetline, " spray.se") > 0:
1252 warnings = warnings + """
1253 Your POP3 server has "spray.se" in its header line. In May 2000 at
1254 least one such server did not process the "TOP" command correctly; the
1255 symptom is that messages are treated as headerless. To work around
1256 this bug, turn on `fetchall' on all user entries associated with this
1260 if string.find(greetline, " usa.net") > 0:
1261 warnings = warnings + """
1262 You appear to be using USA.NET's free mail service. Their POP3 servers
1263 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1264 fetchmail can compensate. They seem to require that fetchall be switched on
1265 (otherwise you won't necessarily see all your mail, not even new mail).
1266 They also botch the TOP command the fetchmail normally uses for retrieval
1267 (it only retrieves about 10 lines rather than the number specified).
1268 Turning on fetchall will disable the use of TOP.
1270 Therefore, it is strongly recommended that you turn on `fetchall' on all
1271 user entries associated with this server.
1274 if string.find(greetline, " Novonyx POP3") > 0:
1275 warnings = warnings + """
1276 Your mailserver is running Novonyx POP3. This server, at least as of
1277 version 2.17, seems to have problems handling and reporting seen bits.
1278 You may have to use the fetchall option.
1282 ### IMAP servers start here
1284 if string.find(greetline, "GroupWise") > 0:
1285 warnings = warnings + """
1286 The Novell GroupWise IMAP server would be better named GroupFoolish;
1287 it is (according to the designer of IMAP) unusably broken. Among
1288 other things, it doesn't include a required content length in its
1289 BODY[TEXT] response.<p>
1291 Fetchmail works around this problem, but we strongly recommend voting
1292 with your dollars for a server that isn't brain-dead. If you stick
1293 with code as shoddy as GroupWise seems to be, you will probably pay
1294 for it with other problems.<p>
1297 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1298 warnings = warnings + """
1299 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1300 set the messages's Seen flag. As a result, if you use the keep option the
1301 same messages will be downloaded over and over.
1304 if string.find(greetline, "InterChange") > 0:
1305 warnings = warnings + """
1306 The InterChange IMAP server screws up on mail with attachments. It
1307 doesn't fetch them if you give it a BODY[TEXT] request, though it
1308 does if you request RFC822.TEXT. According to the IMAP RFCs and their
1309 maintainer these should be equivalent -- and we can't drop the
1310 BODY[TEXT] form because M$ Exchange (quite legally under RFC2062)
1314 if string.find(greetline, "Imail") > 0:
1315 warnings = warnings + """
1316 We've seen a bug report indicating that this IMAP server (at least as of
1317 version 5.0.7) returns an invalid body size for messages with MIME
1318 attachments; the effect is to drop the attachments on the floor. We
1319 recommend you upgrade to a non-broken IMAP server.
1322 if string.find(greetline, "Domino IMAP4") > 0:
1323 warnings = warnings + """
1324 Your IMAP server appears to be Lotus Domino. This server, at least up
1325 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1326 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1327 will see attachments as part of the message text. If your Domino server's
1328 POP3 facility is enabled, we recommend you fall back on it.
1332 ### Checks for protocol variants start here
1334 closebrak = string.find(greetline, ">")
1335 if closebrak > 0 and greetline[closebrak+1] == "\r":
1336 warnings = warnings + """
1337 It looks like you could use APOP on this server and avoid sending it your
1338 password in clear. You should talk to the mailserver administrator about
1342 if string.find(greetline, "IMAP2bis") > 0:
1343 warnings = warnings + """
1344 IMAP2bis servers have a minor problem; they can't peek at messages without
1345 marking them seen. If you take a line hit during the retrieval, the
1346 interrupted message may get left on the server, marked seen.
1348 To work around this, it is recommended that you set the `fetchall'
1349 option on all user entries associated with this server, so any stuck
1350 mail will be retrieved next time around.
1352 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1353 a pointer to an open-source implementation.
1356 if string.find(greetline, "IMAP4rev1") > 0:
1357 warnings = warnings + """
1358 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1359 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1360 has therefore been extremely well tested with this class of server.
1364 warnings = warnings + """
1365 Fetchmail doesn't know anything special about this server type.
1369 # Display success window with warnings
1370 title = "Autoprobe of " + realhost + " succeeded"
1371 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1372 self.protocol.set(protocol)
1373 confwin.title(title)
1374 confwin.iconname(title)
1375 Label(confwin, text=title).pack()
1376 Message(confwin, text=confirm, width=600).pack()
1377 Button(confwin, text='Done',
1378 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1381 # User editing stuff
1385 'title' : 'User option help',
1386 'banner': 'User options',
1388 You may use this panel to set options
1389 that may differ between individual
1392 Once you have a user configuration set
1393 up as you like it, you can select `OK' to
1394 store it in the user list maintained in
1395 the site configuration window.
1397 If you wish to discard the changes you have
1398 made to user options, select `Quit'.
1402 'title' : 'Local name help',
1403 'banner': 'Local names',
1405 The local name(s) in a user entry are the
1406 people on the client machine who should
1407 receive mail from the poll described.
1409 Note: if a user entry has more than one
1410 local name, messages will be retrieved
1411 in multidrop mode. This complicates
1412 the configuration issues; see the manual
1413 page section on multidrop mode.
1416 class UserEdit(Frame, MyWidget):
1417 def __init__(self, username, parent):
1418 self.parent = parent
1420 for user in parent.server.users:
1421 if user.remote == username:
1423 if self.user == None:
1425 self.user.remote = username
1426 self.user.localnames = [username]
1427 parent.server.users.append(self.user)
1429 def edit(self, mode, master=None):
1430 Frame.__init__(self, master)
1432 self.master.title('Fetchmail user ' + self.user.remote
1433 + ' querying ' + self.parent.server.pollname);
1434 self.master.iconname('Fetchmail user ' + self.user.remote);
1435 self.post(User, 'user')
1436 self.makeWidgets(mode, self.parent.server.pollname)
1437 self.keepalive = [] # Use this to anchor the PhotoImage object
1438 make_icon_window(self, fetchmail_icon)
1441 # self.wait_window()
1445 # Yes, this test can fail -- if you delete the parent window.
1446 if self.parent.subwidgets.has_key(self.user.remote):
1447 del self.parent.subwidgets[self.user.remote]
1448 Widget.destroy(self.master)
1451 if ConfirmQuit(self, 'user option editing'):
1455 self.fetch(User, 'user')
1458 def makeWidgets(self, mode, servername):
1459 dispose_window(self,
1460 "User options for " + self.user.remote + " querying " + servername,
1463 if mode != 'novice':
1464 leftwin = Frame(self);
1468 secwin = Frame(leftwin, relief=RAISED, bd=5)
1469 Label(secwin, text="Authentication").pack(side=TOP)
1470 LabeledEntry(secwin, 'Password:',
1471 self.password, '12').pack(side=TOP, fill=X)
1472 secwin.pack(fill=X, anchor=N)
1474 if 'ssl' in feature_options or 'ssl' in dictmembers:
1475 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1476 Checkbutton(sslwin, text="Use SSL?",
1477 variable=self.ssl).pack(side=TOP, fill=X)
1478 LabeledEntry(sslwin, 'SSL key:',
1479 self.sslkey, '14').pack(side=TOP, fill=X)
1480 LabeledEntry(sslwin, 'SSL certificate:',
1481 self.sslcert, '14').pack(side=TOP, fill=X)
1482 sslwin.pack(fill=X, anchor=N)
1484 # Someday this should handle Kerberos 5 too
1486 LabeledEntry(secwin, 'Principal:',
1487 self.principal, '12').pack(side=TOP, fill=X)
1489 names = Frame(leftwin, relief=RAISED, bd=5)
1490 Label(names, text="Local names").pack(side=TOP)
1491 ListEdit("New name: ",
1492 self.user.localnames, None, None, names, localhelp)
1493 names.pack(fill=X, anchor=N)
1495 if mode != 'novice':
1496 targwin = Frame(leftwin, relief=RAISED, bd=5)
1497 Label(targwin, text="Forwarding Options").pack(side=TOP)
1498 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1499 ListEdit("New listener:",
1500 self.user.smtphunt, None, None, targwin, None)
1501 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1502 self.smtpaddress, '26').pack(side=TOP, fill=X)
1503 LabeledEntry(targwin, 'Set RCPT To address:',
1504 self.smtpname, '26').pack(side=TOP, fill=X)
1505 LabeledEntry(targwin, 'Connection setup command:',
1506 self.preconnect, '26').pack(side=TOP, fill=X)
1507 LabeledEntry(targwin, 'Connection wrapup command:',
1508 self.postconnect, '26').pack(side=TOP, fill=X)
1509 LabeledEntry(targwin, 'Local delivery agent:',
1510 self.mda, '26').pack(side=TOP, fill=X)
1511 LabeledEntry(targwin, 'BSMTP output file:',
1512 self.bsmtp, '26').pack(side=TOP, fill=X)
1513 LabeledEntry(targwin, 'Listener spam-block codes:',
1514 self.antispam, '26').pack(side=TOP, fill=X)
1515 LabeledEntry(targwin, 'Pass-through properties:',
1516 self.properties, '26').pack(side=TOP, fill=X)
1517 Checkbutton(targwin, text="Use LMTP?",
1518 variable=self.lmtp).pack(side=TOP, fill=X)
1519 targwin.pack(fill=X, anchor=N)
1521 if mode != 'novice':
1522 leftwin.pack(side=LEFT, fill=X, anchor=N)
1523 rightwin = Frame(self)
1527 optwin = Frame(rightwin, relief=RAISED, bd=5)
1528 Label(optwin, text="Processing Options").pack(side=TOP)
1529 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1530 variable=self.keep).pack(side=TOP, anchor=W)
1531 Checkbutton(optwin, text="Fetch old messages as well as new",
1532 variable=self.fetchall).pack(side=TOP, anchor=W)
1533 if mode != 'novice':
1534 Checkbutton(optwin, text="Flush seen messages before retrieval",
1535 variable=self.flush).pack(side=TOP, anchor=W)
1536 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1537 variable=self.rewrite).pack(side=TOP, anchor=W)
1538 Checkbutton(optwin, text="Force CR/LF at end of each line",
1539 variable=self.forcecr).pack(side=TOP, anchor=W)
1540 Checkbutton(optwin, text="Strip CR from end of each line",
1541 variable=self.stripcr).pack(side=TOP, anchor=W)
1542 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1543 variable=self.pass8bits).pack(side=TOP, anchor=W)
1544 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1545 variable=self.mimedecode).pack(side=TOP, anchor=W)
1546 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1547 variable=self.dropstatus).pack(side=TOP, anchor=W)
1548 Checkbutton(optwin, text="Drop Delivered-To lines from forwarded messages",
1549 variable=self.dropdelivered).pack(side=TOP, anchor=W)
1552 if mode != 'novice':
1553 limwin = Frame(rightwin, relief=RAISED, bd=5)
1554 Label(limwin, text="Resource Limits").pack(side=TOP)
1555 LabeledEntry(limwin, 'Message size limit:',
1556 self.limit, '30').pack(side=TOP, fill=X)
1557 LabeledEntry(limwin, 'Size warning interval:',
1558 self.warnings, '30').pack(side=TOP, fill=X)
1559 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1560 self.fetchlimit, '30').pack(side=TOP, fill=X)
1561 LabeledEntry(limwin, 'Max messages to forward per poll:',
1562 self.batchlimit, '30').pack(side=TOP, fill=X)
1563 if self.parent.server.protocol != 'ETRN':
1564 LabeledEntry(limwin, 'Interval between expunges:',
1565 self.expunge, '30').pack(side=TOP, fill=X)
1566 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1567 variable=self.idle).pack(side=TOP, anchor=W)
1570 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1571 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1572 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1573 ListEdit("New folder:", self.user.mailboxes,
1574 None, None, foldwin, None)
1575 foldwin.pack(fill=X, anchor=N)
1577 if mode != 'novice':
1578 rightwin.pack(side=LEFT)
1584 # Top-level window that offers either novice or expert mode
1585 # (but not both at once; it disappears when one is selected).
1588 class Configurator(Frame):
1589 def __init__(self, outfile, master, onexit, parent):
1590 Frame.__init__(self, master)
1591 self.outfile = outfile
1592 self.onexit = onexit
1593 self.parent = parent
1594 self.master.title('fetchmail configurator');
1595 self.master.iconname('fetchmail configurator');
1597 self.keepalive = [] # Use this to anchor the PhotoImage object
1598 make_icon_window(self, fetchmail_icon)
1600 Message(self, text="""
1601 Use `Novice Configuration' for basic fetchmail setup;
1602 with this, you can easily set up a single-drop connection
1603 to one remote mail server.
1604 """, width=600).pack(side=TOP)
1605 Button(self, text='Novice Configuration',
1606 fg='blue', command=self.novice).pack()
1608 Message(self, text="""
1609 Use `Expert Configuration' for advanced fetchmail setup,
1610 including multiple-site or multidrop connections.
1611 """, width=600).pack(side=TOP)
1612 Button(self, text='Expert Configuration',
1613 fg='blue', command=self.expert).pack()
1615 Message(self, text="""
1616 Or you can just select `Quit' to leave the configurator now and
1617 return to the main panel.
1618 """, width=600).pack(side=TOP)
1619 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1620 master.protocol("WM_DELETE_WINDOW", self.leave)
1623 self.master.destroy()
1624 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1627 self.master.destroy()
1628 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1631 self.master.destroy()
1634 # Run a command in a scrolling text widget, displaying its output
1636 class RunWindow(Frame):
1637 def __init__(self, command, master, parent):
1638 Frame.__init__(self, master)
1639 self.master = master
1640 self.master.title('fetchmail run window');
1641 self.master.iconname('fetchmail run window');
1644 text="Running "+command,
1645 bd=2).pack(side=TOP, pady=10)
1646 self.keepalive = [] # Use this to anchor the PhotoImage object
1647 make_icon_window(self, fetchmail_icon)
1649 # This is a scrolling text window
1650 textframe = Frame(self)
1651 scroll = Scrollbar(textframe)
1652 self.textwidget = Text(textframe, setgrid=TRUE)
1653 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1654 self.textwidget.config(yscrollcommand=scroll.set)
1655 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1656 scroll.config(command=self.textwidget.yview)
1657 scroll.pack(side=RIGHT, fill=BOTH)
1658 textframe.pack(side=TOP)
1660 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1662 self.update() # Draw widget before executing fetchmail
1664 child_stdout = os.popen(command + " 2>&1", "r")
1666 ch = child_stdout.read(1)
1669 self.textwidget.insert(END, ch)
1670 self.textwidget.insert(END, "Done.")
1671 self.textwidget.see(END);
1674 Widget.destroy(self.master)
1676 # Here's where we choose either configuration or launching
1678 class MainWindow(Frame):
1679 def __init__(self, outfile, master=None):
1680 Frame.__init__(self, master)
1681 self.outfile = outfile
1682 self.master.title('fetchmail launcher');
1683 self.master.iconname('fetchmail launcher');
1686 text='Fetchmailconf ' + version,
1687 bd=2).pack(side=TOP, pady=10)
1688 self.keepalive = [] # Use this to anchor the PhotoImage object
1689 make_icon_window(self, fetchmail_icon)
1692 ## Test icon display with the following:
1693 # icon_image = PhotoImage(data=fetchmail_icon)
1694 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1695 # self.keepalive.append(icon_image)
1697 Message(self, text="""
1698 Use `Configure fetchmail' to tell fetchmail about the remote
1699 servers it should poll (the host name, your username there,
1700 whether to use POP or IMAP, and so forth).
1701 """, width=600).pack(side=TOP)
1702 self.configbutton = Button(self, text='Configure fetchmail',
1703 fg='blue', command=self.configure)
1704 self.configbutton.pack()
1706 Message(self, text="""
1707 Use `Test fetchmail' to run fetchmail with debugging enabled.
1708 This is a good way to test out a new configuration.
1709 """, width=600).pack(side=TOP)
1710 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1712 Message(self, text="""
1713 Use `Run fetchmail' to run fetchmail in foreground.
1714 Progress messages will be shown, but not debug messages.
1715 """, width=600).pack(side=TOP)
1716 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1718 Message(self, text="""
1719 Or you can just select `Quit' to exit the launcher now.
1720 """, width=600).pack(side=TOP)
1721 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1723 def configure(self):
1724 self.configbutton.configure(state=DISABLED)
1725 Configurator(self.outfile, Toplevel(),
1726 lambda self=self: self.configbutton.configure(state=NORMAL),
1729 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1732 RunWindow("fetchmail -d0", Toplevel(), self)
1737 # Functions for turning a dictionary into an instantiated object tree.
1739 def intersect(list1, list2):
1740 # Compute set intersection of lists
1747 def setdiff(list1, list2):
1748 # Compute set difference of lists
1755 def copy_instance(toclass, fromdict):
1756 # Initialize a class object of given type from a conformant dictionary.
1757 for fld in fromdict.keys():
1758 if not fld in dictmembers:
1759 dictmembers.append(fld)
1760 # The `optional' fields are the ones we can ignore for purposes of
1761 # conformability checking; they'll still get copied if they are
1762 # present in the dictionary.
1763 optional = ('interface', 'monitor',
1765 'ssl', 'sslkey', 'sslcert',
1767 class_sig = setdiff(toclass.__dict__.keys(), optional)
1769 dict_keys = setdiff(fromdict.keys(), optional)
1771 common = intersect(class_sig, dict_keys)
1772 if 'typemap' in class_sig:
1773 class_sig.remove('typemap')
1774 if tuple(class_sig) != tuple(dict_keys):
1775 print "Fields don't match what fetchmailconf expected:"
1776 # print "Class signature: " + `class_sig`
1777 # print "Dictionary keys: " + `dict_keys`
1778 diff = setdiff(class_sig, common)
1780 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1781 diff = setdiff(dict_keys, common)
1783 print "Not matched in dictionary keys: " + `diff`
1786 for x in fromdict.keys():
1787 setattr(toclass, x, fromdict[x])
1790 # And this is the main sequence. How it works:
1792 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1793 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1794 # Run execfile on the file to pull fetchmailrc into Python global space.
1795 # You don't want static data, though; you want, instead, a tree of objects
1796 # with the same data members and added appropriate methods.
1798 # This is what the copy_instance function() is for. It tries to copy a
1799 # dictionary field by field into a class, aborting if the class and dictionary
1800 # have different data members (except for any typemap member in the class;
1801 # that one is strictly for use by the MyWidget supperclass).
1803 # Once the object tree is set up, require user to choose novice or expert
1804 # mode and instantiate an edit object for the configuration. Class methods
1805 # will take it all from there.
1807 # Options (not documented because they're for fetchmailconf debuggers only):
1808 # -d: Read the configuration and dump it to stdout before editing. Dump
1809 # the edited result to stdout as well.
1810 # -f: specify the run control file to read.
1812 if __name__ == '__main__':
1814 if not os.environ.has_key("DISPLAY"):
1815 print "fetchmailconf must be run under X"
1818 fetchmail_icon = """
1819 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1820 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1821 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1822 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1823 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1824 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1825 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1826 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1827 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1828 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1829 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1830 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1831 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1832 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1833 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1834 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1835 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1836 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1837 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1838 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1839 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1840 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1841 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1842 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1843 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1844 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1845 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1846 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1847 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1848 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1849 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1850 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1852 # The base64 data in the string above was generated by the following procedure:
1855 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1859 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1860 dump = rcfile = None;
1861 for (switch, val) in options:
1862 if (switch == '-d'):
1864 elif (switch == '-f'):
1867 # Get client host's FQDN
1868 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1871 ConfigurationDefaults = Configuration()
1872 ServerDefaults = Server()
1873 UserDefaults = User()
1875 # Read the existing configuration. We set the umask to 077 to make sure
1876 # that group & other read/write permissions are shut off -- we wouldn't
1877 # want crackers to snoop password information out of the tempfile.
1878 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1880 cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1882 cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
1887 print "`" + cmd + "' run failure, status " + `s`
1890 print "Unknown error while running fetchmail --configdump"
1897 print "Can't read configuration output of fetchmail --configdump."
1903 # The tricky part -- initializing objects from the configuration global
1904 # `Configuration' is the top level of the object tree we're going to mung.
1905 # The dictmembers list is used to track the set of fields the dictionary
1906 # contains; in particular, we can use it to tell whether things like the
1907 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1909 Fetchmailrc = Configuration()
1910 copy_instance(Fetchmailrc, fetchmailrc)
1911 Fetchmailrc.servers = [];
1912 for server in fetchmailrc['servers']:
1914 copy_instance(Newsite, server)
1915 Fetchmailrc.servers.append(Newsite)
1917 for user in server['users']:
1919 copy_instance(Newuser, user)
1920 Newsite.users.append(Newuser)
1922 # We may want to display the configuration and quit
1924 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1926 # The theory here is that -f alone sets the rcfile location,
1927 # but -d and -f together mean the new configuration should go to stdout.
1928 if not rcfile and not dump:
1929 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1931 # OK, now run the configuration edit
1932 root = MainWindow(rcfile)
1935 # The following sets edit modes for GNU EMACS