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: str = str + "poll"
116 else: str = str + "skip"
117 str = str + (" " + self.pollname)
119 str = str + (" via " + str(self.via) + "\n");
120 if self.protocol != ServerDefaults.protocol:
121 str = str + " with proto " + self.protocol
122 if self.port != defaultports[self.protocol] and self.port != 0:
123 str = str + " port " + `self.port`
124 if self.timeout != ServerDefaults.timeout:
125 str = str + " timeout " + `self.timeout`
126 if self.interval != ServerDefaults.interval:
127 str = str + " interval " + `self.interval`
128 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
130 str = str + " envelope " + self.envskip + " " + self.envelope
132 str = str + " envelope " + self.envelope
134 str = str + (" qvirtual " + str(self.qvirtual) + "\n");
135 if self.preauth != ServerDefaults.preauth:
136 str = str + " preauth " + self.preauth
137 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
138 str = str + " and options"
139 if self.dns != ServerDefaults.dns:
140 str = str + flag2str(self.dns, 'dns')
141 if self.uidl != ServerDefaults.uidl:
142 str = str + flag2str(self.uidl, 'uidl')
143 if folded: str = str + "\n "
144 else: str = str + " "
150 if self.aka and self.localdomains: str = str + " "
151 if self.localdomains:
152 str = str + ("localdomains")
153 for x in self.localdomains:
155 if (self.aka or self.localdomains):
162 str = str + "interface " + str(self.interface)
164 str = str + "monitor " + str(self.monitor)
166 str = str + "netsec " + str(self.netsec)
167 if self.interface or self.monitor or self.netsec:
171 if str[-1] == " ": str = str[0:-1]
173 for user in self.users:
174 str = str + 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.limit = 0 # Message size limit
220 self.warnings = 0 # Size warning interval
221 self.fetchlimit = 0 # Max messages fetched per batch
222 self.batchlimit = 0 # Max message forwarded per batch
223 self.expunge = 0 # Interval between expunges (IMAP)
224 self.ssl = 0 # Enable Seccure Socket Layer
225 self.sslkey = None # SSL key filename
226 self.sslcert = None # SSL certificate filename
227 self.properties = None # Extension properties
229 ('remote', 'String'),
230 # leave out mailboxes and localnames
231 ('password', 'String'),
233 ('smtpaddress', 'String'),
234 ('preconnect', 'String'),
235 ('postconnect', 'String'),
239 ('antispam', 'String'),
241 ('flush', 'Boolean'),
242 ('fetchall', 'Boolean'),
243 ('rewrite', 'Boolean'),
244 ('forcecr', 'Boolean'),
245 ('stripcr', 'Boolean'),
246 ('pass8bits', 'Boolean'),
247 ('mimedecode', 'Boolean'),
248 ('dropstatus', 'Boolean'),
251 ('fetchlimit', 'Int'),
252 ('batchlimit', 'Int'),
255 ('sslkey', 'String'),
256 ('sslcert', 'String'),
257 ('properties', 'String'))
261 res = res + "user " + str(self.remote) + " there ";
263 res = res + "with password " + str(self.password) + " "
266 for x in self.localnames:
269 if (self.keep != UserDefaults.keep
270 or self.flush != UserDefaults.flush
271 or self.fetchall != UserDefaults.fetchall
272 or self.rewrite != UserDefaults.rewrite
273 or self.forcecr != UserDefaults.forcecr
274 or self.stripcr != UserDefaults.stripcr
275 or self.pass8bits != UserDefaults.pass8bits
276 or self.mimedecode != UserDefaults.mimedecode
277 or self.dropstatus != UserDefaults.dropstatus):
278 res = res + " options"
279 if self.keep != UserDefaults.keep:
280 res = res + flag2str(self.keep, 'keep')
281 if self.flush != UserDefaults.flush:
282 res = res + flag2str(self.flush, 'flush')
283 if self.fetchall != UserDefaults.fetchall:
284 res = res + flag2str(self.fetchall, 'fetchall')
285 if self.rewrite != UserDefaults.rewrite:
286 res = res + flag2str(self.rewrite, 'rewrite')
287 if self.forcecr != UserDefaults.forcecr:
288 res = res + flag2str(self.forcecr, 'forcecr')
289 if self.stripcr != UserDefaults.stripcr:
290 res = res + flag2str(self.stripcr, 'stripcr')
291 if self.pass8bits != UserDefaults.pass8bits:
292 res = res + flag2str(self.pass8bits, 'pass8bits')
293 if self.mimedecode != UserDefaults.mimedecode:
294 res = res + flag2str(self.mimedecode, 'mimedecode')
295 if self.dropstatus != UserDefaults.dropstatus:
296 res = res + flag2str(self.dropstatus, 'dropstatus')
297 if self.limit != UserDefaults.limit:
298 res = res + " limit " + `self.limit`
299 if self.warnings != UserDefaults.warnings:
300 res = res + " warnings " + `self.warnings`
301 if self.fetchlimit != UserDefaults.fetchlimit:
302 res = res + " fetchlimit " + `self.fetchlimit`
303 if self.batchlimit != UserDefaults.batchlimit:
304 res = res + " batchlimit " + `self.batchlimit`
305 if self.ssl != UserDefaults.ssl:
306 res = res + flag2str(self.ssl, 'ssl')
307 if self.sslkey != UserDefaults.sslkey:
308 res = res + " sslkey " + `self.sslkey`
309 if self.sslcert != UserDefaults.sslcert:
310 res = res + " ssl " + `self.sslcert`
311 if self.expunge != UserDefaults.expunge:
312 res = res + " expunge " + `self.expunge`
314 trimmed = self.smtphunt;
315 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
316 trimmed = trimmed[0:len(trimmed) - 1]
317 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
318 trimmed = trimmed[0:len(trimmed) - 1]
320 res = res + " smtphost "
325 res = res + " folder"
326 for x in self.mailboxes:
329 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
330 if getattr(self, fld):
331 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
332 if self.lmtp != UserDefaults.lmtp:
333 res = res + flag2str(self.lmtp, 'lmtp')
334 if self.antispam != UserDefaults.antispam:
335 res = res + " antispam " + self.antispam + "\n"
339 return "[User: " + repr(self) + "]"
345 defaultports = {"auto":0,
355 preauthlist = ("password", "kerberos", "ssh")
358 'title' : 'List Selection Help',
359 'banner': 'List Selection',
361 You must select an item in the list box (by clicking on it).
364 def flag2str(value, string):
365 # make a string representation of a .fetchmailrc flag or negated flag
369 if value == FALSE: str = str + ("no ")
373 class LabeledEntry(Frame):
374 # widget consisting of entry field with caption to left
375 def bind(self, key, action):
376 self.E.bind(key, action)
379 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
380 Frame.__init__(self, Master)
381 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
382 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
383 self.L.pack({'side':'left'})
384 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
386 def ButtonBar(frame, legend, ref, alternatives, depth, command):
387 # array of radio buttons, caption to left, picking from a string list
389 width = len(alternatives) / depth;
390 Label(bar, text=legend).pack(side=LEFT)
391 for column in range(width):
392 subframe = Frame(bar)
393 for row in range(depth):
394 ind = width * row + column
395 Radiobutton(subframe,
396 {'text':alternatives[ind],
398 'value':alternatives[ind],
399 'command':command}).pack(side=TOP, anchor=W)
400 subframe.pack(side=LEFT)
404 def helpwin(helpdict):
405 # help message window with a self-destruct button
407 helpwin.title(helpdict['title'])
408 helpwin.iconname(helpdict['title'])
409 Label(helpwin, text=helpdict['banner']).pack()
410 textframe = Frame(helpwin)
411 scroll = Scrollbar(textframe)
412 helpwin.textwidget = Text(textframe, setgrid=TRUE)
413 textframe.pack(side=TOP, expand=YES, fill=BOTH)
414 helpwin.textwidget.config(yscrollcommand=scroll.set)
415 helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
416 scroll.config(command=helpwin.textwidget.yview)
417 scroll.pack(side=RIGHT, fill=BOTH)
418 helpwin.textwidget.insert(END, helpdict['text']);
419 Button(helpwin, text='Done',
420 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
421 textframe.pack(side=TOP)
423 def make_icon_window(base, image):
425 # Some older pythons will error out on this
426 icon_image = PhotoImage(data=image)
427 icon_window = Toplevel()
428 Label(icon_window, image=icon_image, bg='black').pack()
429 base.master.iconwindow(icon_window)
430 # Avoid TkInter brain death. PhotoImage objects go out of
431 # scope when the enclosing function returns. Therefore
432 # we have to explicitly link them to something.
433 base.keepalive.append(icon_image)
437 class ListEdit(Frame):
438 # edit a list of values (duplicates not allowed) with a supplied editor hook
439 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
441 self.deletor = deletor
444 # Set up a widget to accept new elements
445 self.newval = StringVar(master)
446 newwin = LabeledEntry(master, newlegend, self.newval, '12')
447 newwin.bind('<Double-1>', self.handleNew)
448 newwin.bind('<Return>', self.handleNew)
449 newwin.pack(side=TOP, fill=X, anchor=E)
451 # Edit the existing list
452 listframe = Frame(master)
453 scroll = Scrollbar(listframe)
454 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
457 self.listwidget.insert(END, x)
458 listframe.pack(side=TOP, expand=YES, fill=BOTH)
459 self.listwidget.config(yscrollcommand=scroll.set)
460 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
461 scroll.config(command=self.listwidget.yview)
462 scroll.pack(side=RIGHT, fill=BOTH)
463 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
464 self.listwidget.bind('<Double-1>', self.handleList);
465 self.listwidget.bind('<Return>', self.handleList);
469 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
470 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
472 self.helptxt = helptxt
473 Button(bf, text='Help', fg='blue',
474 command=self.help).pack(side=RIGHT)
478 helpwin(self.helptxt)
480 def handleList(self, event):
483 def handleNew(self, event):
484 item = self.newval.get()
485 entire = self.listwidget.get(0, self.listwidget.index('end'));
486 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
487 self.listwidget.insert('end', item)
488 if self.list != None: self.list.append(item)
492 select = self.listwidget.curselection()
497 if index and self.editor:
498 label = self.listwidget.get(index);
499 apply(self.editor, (label,))
501 def deleteItem(self):
502 select = self.listwidget.curselection()
506 index = string.atoi(select[0])
507 label = self.listwidget.get(index);
508 self.listwidget.delete(index)
509 if self.list != None:
511 if self.deletor != None:
512 apply(self.deletor, (label,))
514 def ConfirmQuit(frame, context):
517 text = 'Really quit ' + context + ' without saving?',
519 strings = ('Yes', 'No'),
523 def dispose_window(master, legend, help, savelegend='OK'):
524 dispose = Frame(master, relief=RAISED, bd=5)
525 Label(dispose, text=legend).pack(side=TOP,pady=10)
526 Button(dispose, text=savelegend, fg='blue',
527 command=master.save).pack(side=LEFT)
528 Button(dispose, text='Quit', fg='blue',
529 command=master.nosave).pack(side=LEFT)
530 Button(dispose, text='Help', fg='blue',
531 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
536 # Common methods for Tkinter widgets -- deals with Tkinter declaration
537 def post(self, widgetclass, field):
538 for x in widgetclass.typemap:
539 if x[1] == 'Boolean':
540 setattr(self, x[0], BooleanVar(self))
541 elif x[1] == 'String':
542 setattr(self, x[0], StringVar(self))
544 setattr(self, x[0], IntVar(self))
545 source = getattr(getattr(self, field), x[0])
547 getattr(self, x[0]).set(source)
549 def fetch(self, widgetclass, field):
550 for x in widgetclass.typemap:
551 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
554 # First, code to set the global fetchmail run controls.
557 configure_novice_help = {
558 'title' : 'Fetchmail novice configurator help',
559 'banner': 'Novice configurator help',
561 In the `Novice Configurator Controls' panel, you can:
563 Press `Save' to save the new fetchmail configuration you have created.
564 Press `Quit' to exit without saving.
565 Press `Help' to bring up this help message.
567 In the `Novice Configuration' panels, you will set up the basic data
568 needed to create a simple fetchmail setup. These include:
570 1. The name of the remote site you want to query.
572 2. Your login name on that site.
574 3. Your password on that site.
576 4. A protocol to use (POP, IMAP, ETRN, etc.)
578 5. A polling interval.
580 6. Options to fetch old messages as well as new, uor to suppress
581 deletion of fetched message.
583 The novice-configuration code will assume that you want to forward mail
584 to a local sendmail listener with no special options.
587 configure_expert_help = {
588 'title' : 'Fetchmail expert configurator help',
589 'banner': 'Expert configurator help',
591 In the `Expert Configurator Controls' panel, you can:
593 Press `Save' to save the new fetchmail configuration you have edited.
594 Press `Quit' to exit without saving.
595 Press `Help' to bring up this help message.
597 In the `Run Controls' panel, you can set the following options that
598 control how fetchmail runs:
601 Number of seconds to wait between polls in the background.
602 If zero, fetchmail will run in foreground.
605 If empty, emit progress and error messages to stderr.
606 Otherwise this gives the name of the files to write to.
607 This field is ignored if the "Log to syslog?" option is on.
610 If empty, store seen-message IDs in .fetchids under user's home
611 directory. If nonempty, use given file name.
614 Who to send multidrop mail to as a last resort if no address can
615 be matched. Normally empty; in this case, fetchmail treats the
616 invoking user as the address of last resort unless that user is
617 root. If that user is root, fetchmail sends to `postmaster'.
620 If this option is on (the default) error mail goes to the sender.
621 Otherwise it goes to the postmaster.
624 If false (the default) fetchmail generates a Received line into
625 each message and generates a HELO from the machine it is running on.
626 If true, fetchmail generates no Received line and HELOs as if it were
629 In the `Remote Mail Configurations' panel, you can:
631 1. Enter the name of a new remote mail server you want fetchmail to query.
633 To do this, simply enter a label for the poll configuration in the
634 `New Server:' box. The label should be a DNS name of the server (unless
635 you are using ssh or some other tunneling method and will fill in the `via'
636 option on the site configuration screen).
638 2. Change the configuration of an existing site.
640 To do this, find the site's label in the listbox and double-click it.
641 This will take you to a site configuration dialogue.
645 class ConfigurationEdit(Frame, MyWidget):
646 def __init__(self, configuration, outfile, master, onexit):
648 self.configuration = configuration
649 self.outfile = outfile
650 self.container = master
652 ConfigurationEdit.mode_to_help = {
653 'novice':configure_novice_help, 'expert':configure_expert_help
656 def server_edit(self, sitename):
657 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
659 def server_delete(self, sitename):
661 del self.configuration[sitename]
665 def edit(self, mode):
667 Frame.__init__(self, self.container)
668 self.master.title('fetchmail ' + self.mode + ' configurator');
669 self.master.iconname('fetchmail ' + self.mode + ' configurator');
670 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
671 self.keepalive = [] # Use this to anchor the PhotoImage object
672 make_icon_window(self, fetchmail_gif)
674 self.post(Configuration, 'configuration')
677 'Configurator ' + self.mode + ' Controls',
678 ConfigurationEdit.mode_to_help[self.mode],
681 gf = Frame(self, relief=RAISED, bd = 5)
683 text='Fetchmail Run Controls',
684 bd=2).pack(side=TOP, pady=10)
689 if self.mode != 'novice':
691 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
692 log.pack(side=RIGHT, anchor=E)
694 # Set the poll interval
695 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
696 de.pack(side=RIGHT, anchor=E)
701 if self.mode != 'novice':
704 {'text':'Bounces to sender?',
705 'variable':self.bouncemail,
706 'relief':GROOVE}).pack(side=LEFT, anchor=W)
711 {'text':'Log to syslog?',
712 'variable':self.syslog,
713 'relief':GROOVE}).pack(side=LEFT, anchor=W)
714 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
715 log.pack(side=RIGHT, anchor=E)
719 {'text':'Invisible mode?',
720 'variable':self.invisible,
721 'relief':GROOVE}).pack(side=LEFT, anchor=W)
723 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
724 log.pack(side=RIGHT, anchor=E)
728 # Expert mode allows us to edit multiple sites
729 lf = Frame(self, relief=RAISED, bd=5)
731 text='Remote Mail Server Configurations',
732 bd=2).pack(side=TOP, pady=10)
733 ListEdit('New Server:',
734 map(lambda x: x.pollname, self.configuration.servers),
735 lambda site, self=self: self.server_edit(site),
736 lambda site, self=self: self.server_delete(site),
741 for sitename in self.subwidgets.keys():
742 self.subwidgets[sitename].destruct()
743 self.master.destroy()
747 if ConfirmQuit(self, self.mode + " configuration editor"):
751 for sitename in self.subwidgets.keys():
752 self.subwidgets[sitename].save()
753 self.fetch(Configuration, 'configuration')
757 elif not os.path.isfile(self.outfile) or Dialog(self,
758 title = 'Overwrite existing run control file?',
759 text = 'Really overwrite existing run control file?',
761 strings = ('Yes', 'No'),
762 default = 1).num == 0:
763 fm = open(self.outfile, 'w')
765 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
766 fm.write(`self.configuration`)
770 os.chmod(self.outfile, 0600)
774 # Server editing stuff.
777 'title' : 'Remote site help',
778 'banner': 'Remote sites',
780 When you add a site name to the list here,
781 you initialize an entry telling fetchmail
782 how to poll a new site.
784 When you select a sitename (by double-
785 clicking it, or by single-clicking to
786 select and then clicking the Edit button),
787 you will open a window to configure that
792 'title' : 'Server options help',
793 'banner': 'Server Options',
795 The server options screen controls fetchmail
796 options that apply to one of your mailservers.
798 Once you have a mailserver configuration set
799 up as you like it, you can select `OK' to
800 store it in the server list maintained in
801 the main configuration window.
803 If you wish to discard changes to a server
804 configuration, select `Quit'.
808 'title' : 'Run Control help',
809 'banner': 'Run Controls',
811 If the `Poll normally' checkbox is on, the host is polled as part of
812 the normal operation of fetchmail when it is run with no arguments.
813 If it is off, fetchmail will only query this host when it is given as
814 a command-line argument.
816 The `True name of server' box should specify the actual DNS name
817 to query. By default this is the same as the poll name.
819 Normally each host described in the file is queried once each
820 poll cycle. If `Cycles to skip between polls' is greater than 0,
821 that's the number of poll cycles that are skipped between the
822 times this post is actually polled.
824 The `Server timeout' is the number of seconds fetchmail will wait
825 for a reply from the mailserver before concluding it is hung and
830 'title' : 'Protocol and Port help',
831 'banner': 'Protocol and Port',
833 These options control the remote-mail protocol
834 and TCP/IP service port used to query this
837 If you click the `Probe for supported protocols'
838 button, fetchmail will try to find you the most
839 capable server on the selected host (this will
840 only work if you're conncted to the Internet).
841 The probe only checks for ordinary IMAP and POP
842 protocols; fortunately these are the most
843 frequently supported.
845 The `Protocol' button bar offers you a choice of
846 all the different protocols available. The `auto'
847 protocol is the default mode; it probes the host
848 ports for POP3 and IMAP to see if either is
851 Normally the TCP/IP service port to use is
852 dictated by the protocol choice. The `Port'
853 field (only present in expert mode) lets you
854 set a non-standard port.
858 'title' : 'Security option help',
859 'banner': 'Security',
861 The `interface' option allows you to specify a range
862 of IP addresses to monitor for activity. If these
863 addresses are not active, fetchmail will not poll.
864 Specifying this may protect you from a spoofing attack
865 if your client machine has more than one IP gateway
866 address and some of the gateways are to insecure nets.
868 The `monitor' option, if given, specifies the only
869 device through which fetchmail is permitted to connect
870 to servers. This option may be used to prevent
871 fetchmail from triggering an expensive dial-out if the
872 interface is not already active.
874 The `interface' and `monitor' options are available
875 only for Linux and freeBSD systems. See the fetchmail
876 manual page for details on these.
878 The ssl option enables SSL communication with a mailserver
879 supporting Secure Sockets Layer. The sslkey and sslcert options
880 declare key and certificate files for use with SSL.
882 The `netsec' option will be configurable only if fetchmail
883 was compiled with IPV6 support. If you need to use it,
884 you probably know what to do.
888 'title' : 'Multidrop option help',
889 'banner': 'Multidrop',
891 These options are only useful with multidrop mode.
892 See the manual page for extended discussion.
896 'title' : 'User list help',
897 'banner': 'User list',
899 When you add a user name to the list here,
900 you initialize an entry telling fetchmail
901 to poll the site on behalf of the new user.
903 When you select a username (by double-
904 clicking it, or by single-clicking to
905 select and then clicking the Edit button),
906 you will open a window to configure the
907 user's options on that site.
910 class ServerEdit(Frame, MyWidget):
911 def __init__(self, host, parent):
915 for site in parent.configuration.servers:
916 if site.pollname == host:
918 if (self.server == None):
919 self.server = Server()
920 self.server.pollname = host
921 self.server.via = None
922 parent.configuration.servers.append(self.server)
924 def edit(self, mode, master=None):
925 Frame.__init__(self, master)
927 self.master.title('Fetchmail host ' + self.server.pollname);
928 self.master.iconname('Fetchmail host ' + self.server.pollname);
929 self.post(Server, 'server')
930 self.makeWidgets(self.server.pollname, mode)
931 self.keepalive = [] # Use this to anchor the PhotoImage object
932 make_icon_window(self, fetchmail_gif)
939 for username in self.subwidgets.keys():
940 self.subwidgets[username].destruct()
941 del self.parent.subwidgets[self.server.pollname]
942 Widget.destroy(self.master)
945 if ConfirmQuit(self, 'server option editing'):
949 self.fetch(Server, 'server')
950 for username in self.subwidgets.keys():
951 self.subwidgets[username].save()
954 def refreshPort(self):
955 proto = self.protocol.get()
956 if self.port.get() == 0:
957 self.port.set(defaultports[proto])
958 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
960 def user_edit(self, username, mode):
961 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
963 def user_delete(self, username):
964 if self.subwidgets.has_key(username):
965 del self.subwidgets[username]
966 del self.server[username]
968 def makeWidgets(self, host, mode):
969 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
971 leftwin = Frame(self);
975 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
976 Label(ctlwin, text="Run Controls").pack(side=TOP)
977 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
978 LabeledEntry(ctlwin, 'True name of ' + host + ':',
979 self.via, leftwidth).pack(side=TOP, fill=X)
980 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
981 self.interval, leftwidth).pack(side=TOP, fill=X)
982 LabeledEntry(ctlwin, 'Server timeout (seconds):',
983 self.timeout, leftwidth).pack(side=TOP, fill=X)
984 Button(ctlwin, text='Help', fg='blue',
985 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
988 # Compute the available protocols from the compile-time options
990 if 'pop2' in feature_options:
991 protolist.append("POP2")
992 if 'pop3' in feature_options:
993 protolist = protolist + ["POP3", "APOP", "KPOP"]
994 if 'sdps' in feature_options:
995 protolist.append("SDPS")
996 if 'imap' in feature_options:
997 protolist.append("IMAP")
998 if 'imap-gss' in feature_options:
999 protolist.append("IMAP-GSS")
1000 if 'imap-k4' in feature_options:
1001 protolist.append("IMAP-K4")
1002 if 'etrn' in feature_options:
1003 protolist.append("ETRN")
1005 protwin = Frame(leftwin, relief=RAISED, bd=5)
1006 Label(protwin, text="Protocol").pack(side=TOP)
1007 ButtonBar(protwin, '',
1008 self.protocol, protolist, 2,
1010 if mode != 'novice':
1011 LabeledEntry(protwin, 'On server TCP/IP port:',
1012 self.port, leftwidth).pack(side=TOP, fill=X)
1014 Checkbutton(protwin,
1015 text="POP3: track `seen' with client-side UIDLs?",
1016 variable=self.uidl).pack(side=TOP)
1017 Button(protwin, text='Probe for supported protocols', fg='blue',
1018 command=self.autoprobe).pack(side=LEFT)
1019 Button(protwin, text='Help', fg='blue',
1020 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1021 protwin.pack(fill=X)
1023 userwin = Frame(leftwin, relief=RAISED, bd=5)
1024 Label(userwin, text="User entries for " + host).pack(side=TOP)
1025 ListEdit("New user: ",
1026 map(lambda x: x.remote, self.server.users),
1027 lambda u, m=mode, s=self: s.user_edit(u, m),
1028 lambda u, s=self: s.user_delete(u),
1030 userwin.pack(fill=X)
1032 leftwin.pack(side=LEFT, anchor=N, fill=X);
1034 if mode != 'novice':
1035 rightwin = Frame(self);
1037 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1038 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1039 LabeledEntry(mdropwin, 'Envelope address header:',
1040 self.envelope, '22').pack(side=TOP, fill=X)
1041 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1042 self.envskip, '22').pack(side=TOP, fill=X)
1043 LabeledEntry(mdropwin, 'Name prefix to strip:',
1044 self.qvirtual, '22').pack(side=TOP, fill=X)
1045 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1046 variable=self.dns).pack(side=TOP)
1047 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1048 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1049 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1050 ListEdit("New domain: ",
1051 self.server.localdomains, None, None, mdropwin, multihelp)
1052 mdropwin.pack(fill=X)
1054 if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1055 secwin = Frame(rightwin, relief=RAISED, bd=5)
1056 Label(secwin, text="Security").pack(side=TOP)
1057 # Don't actually let users set this. KPOP sets it implicitly
1058 # ButtonBar(secwin, 'Preauthorization mode:',
1059 # self.preauth, preauthlist, 1, None).pack(side=TOP)
1060 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1061 LabeledEntry(secwin, 'IP range to check before poll:',
1062 self.interface, leftwidth).pack(side=TOP, fill=X)
1063 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1064 LabeledEntry(secwin, 'Interface to monitor:',
1065 self.monitor, leftwidth).pack(side=TOP, fill=X)
1066 if 'netsec' in feature_options or 'netsec' in dictmembers:
1067 LabeledEntry(secwin, 'IPV6 security options:',
1068 self.netsec, leftwidth).pack(side=TOP, fill=X)
1069 Button(secwin, text='Help', fg='blue',
1070 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1073 rightwin.pack(side=LEFT, anchor=N);
1075 def autoprobe(self):
1076 # Note: this only handles case (1) near fetchmail.c:1032
1077 # We're assuming people smart enough to set up ssh tunneling
1078 # won't need autoprobing.
1080 realhost = self.server.via
1082 realhost = self.server.pollname
1084 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1085 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1087 sock.connect(realhost, port)
1088 greetline = sock.recv(1024)
1094 confwin = Toplevel()
1095 if greetline == None:
1096 title = "Autoprobe of " + realhost + " failed"
1098 Fetchmailconf didn't find any mailservers active.
1099 This could mean the host doesn't support any,
1100 or that your Internet connection is down, or
1101 that the host is so slow that the probe timed
1102 out before getting a response.
1106 # OK, now try to recognize potential problems
1108 if protocol == "POP2":
1109 warnings = warnings + """
1110 It appears you have somehow found a mailserver running only POP2.
1111 Congratulations. Have you considered a career in archaeology?
1113 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1114 Unless the first line of your fetchmail -V output includes the string "POP2",
1115 you'll have to build it from sources yourself with the configure
1116 switch --enable-POP2.
1120 # The greeting line on the server known to be buggy is:
1121 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1123 if string.find(greetline, "FTGate") > 0:
1124 warnings = warnings + """
1125 This POP server has a weird bug; it says OK twice in response to TOP.
1126 Its response to RETR is normal, so use the `fetchall'.
1130 if string.find(greetline, "POP-Max") > 0:
1131 warnings = warnings + """
1132 The Mail Max server screws up on mail with attachments. It reports the
1133 message size with attachments included, but doesn't downloasd them on a
1134 RETR or TOP. You should get rid of it -- and the brain-dead NT server
1139 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1140 warnings = warnings + """
1141 This appears to be an old version of the UC Davis POP server. These are
1142 dangerously unreliable (among other problems, they may drop your mailbox
1143 on the floor if your connection is interrupted during the session).
1145 It is strongly recommended that you find a better POP3 server. The fetchmail
1146 FAQ includes pointers to good ones.
1149 if string.find(greetline, "usa.net") > 0:
1150 warnings = warnings + """
1151 You appear to be using USA.NET's free mail service. Their POP3 servers
1152 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1153 fetchmail can compensate. They seem to require that fetchall be switched on
1154 (otherwise you won't necessarily see all your mail, not even new mail).
1155 They also botch the TOP command the fetchmail normally uses for retrieval
1156 (it only retrieves about 10 lines rather than the number specified).
1157 Turning on fetchall will disable the use of TOP.
1159 Therefore, it is strongly recommended that you turn on `fetchall' on all
1160 user entries associated with this server.
1163 if string.find(greetline, "OpenMail") > 0:
1164 warnings = warnings + """
1165 You appear to be using some version of HP OpenMail. Many versions of
1166 OpenMail do not process the "TOP" command correctly; the symptom is that
1167 only the header and first line of each message is retrieved. To work
1168 around this bug, turn on `fetchall' on all user entries associated with
1172 if string.find(greetline, "TEMS POP3") > 0:
1173 warnings = warnings + """
1174 Your POP3 server has "TEMS" in its header line. At least one such
1175 server does not process the "TOP" command correctly; the symptom is
1176 that fetchmail hangs when trying to retrieve mail. To work around
1177 this bug, turn on `fetchall' on all user entries associated with this
1181 if string.find(greetline, "GroupWise") > 0:
1182 warnings = warnings + """
1183 The Novell GroupWise IMAP server would be better named GroupFoolish;
1184 it is (according to the designer of IMAP) unusably broken. Among
1185 other things, it doesn't include a required content length in its
1186 BODY[TEXT] response.<p>
1188 Fetchmail works around this problem, but we strongly recommend voting
1189 with your dollars for a server that isn't brain-dead. If you stick
1190 with code as shoddy as GroupWise seems to be, you will probably pay
1191 for it with other problems.<p>
1194 if string.find(greetline, "sprynet.com") > 0:
1195 warnings = warnings + """
1196 You appear to be using a SpryNet server. In mid-1999 it was reported that
1197 the SpryNet TOP command marks messages seen. Therefore, for proper error
1198 recovery in the event of a line drop, it is strongly recommended that you
1199 turn on `fetchall' on all user entries associated with this server.
1203 # Steve VanDevender <stevev@efn.org> writes:
1204 # The only system I have seen this happen with is cucipop-1.31
1205 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1206 # 2.x and probably quite a few other systems. It appears to be a
1207 # bug or bad interaction with the SunOS realloc() -- it turns out
1208 # that internally cucipop does allocate a certain data structure in
1209 # multiples of 16, using realloc() to bump it up to the next
1210 # multiple if it needs more.
1212 # The distinctive symptom is that when there are 16 messages in the
1213 # inbox, you can RETR and DELE all 16 messages successfully, but on
1214 # QUIT cucipop returns something like "-ERR Error locking your
1215 # mailbox" and aborts without updating it.
1217 # The cucipop banner looks like:
1219 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1221 if string.find(greetline, "Cubic Circle") > 0:
1222 warnings = warnings + """
1223 I see your server is running cucipop. Better make sure the server box
1224 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1225 under that version, and doesn't cope with the result gracefully. Newer
1226 SunOS and Solaris machines run cucipop OK.
1229 if string.find(greetline, "QPOP") > 0:
1230 warnings = warnings + """
1231 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1232 knows all about qpopper. However, be aware that the 2.53 version of
1233 qpopper does something odd that causes fetchmail to hang with a socket
1234 error on very large messages. This is probably not a fetchmail bug, as
1235 it has been observed with fetchpop. The fix is to upgrade to qpopper
1236 3.0beta or a more recent version. Better yet, switch to IMAP.
1239 if string.find(greetline, "Imail") > 0:
1240 warnings = warnings + """
1241 We've seen a bug report indicating that this IMAP server (at least as of
1242 version 5.0.7) returns an invalid body size for messages with MIME
1243 attachments; the effect is to drop the attachments on the floor. We
1244 recommend you upgrade to a non-broken IMAP server.
1247 closebrak = string.find(greetline, ">")
1248 if closebrak > 0 and greetline[closebrak+1] == "\r":
1249 warnings = warnings + """
1250 It looks like you could use APOP on this server and avoid sending it your
1251 password in clear. You should talk to the mailserver administrator about
1255 if string.find(greetline, "IMAP2bis") > 0:
1256 warnings = warnings + """
1257 IMAP2bis servers have a minor problem; they can't peek at messages without
1258 marking them seen. If you take a line hit during the retrieval, the
1259 interrupted message may get left on the server, marked seen.
1261 To work around this, it is recommended that you set the `fetchall'
1262 option on all user entries associated with this server, so any stuck
1263 mail will be retrieved next time around.
1266 if string.find(greetline, "POP3 Server Ready") > 0:
1267 warnings = warnings + """
1268 Some server that uses this greeting line has been observed to choke on
1269 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1272 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1273 warnings = warnings + """
1274 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1275 set the messages's Seen flag. As a result, if you use the keep option the
1276 same messages will be downloaded over and over.
1279 if string.find(greetline, "IMAP4rev1") > 0:
1280 warnings = warnings + """
1281 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1282 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1283 has therefore been extremely well tested with this class of server.
1286 warnings = warnings + """
1287 Fetchmail doesn't know anything special about this server type.
1290 # Display success window with warnings
1291 title = "Autoprobe of " + realhost + " succeeded"
1292 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1293 self.protocol.set(protocol)
1294 confwin.title(title)
1295 confwin.iconname(title)
1296 Label(confwin, text=title).pack()
1297 Message(confwin, text=confirm, width=600).pack()
1298 Button(confwin, text='Done',
1299 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1302 # User editing stuff
1306 'title' : 'User option help',
1307 'banner': 'User options',
1309 You may use this panel to set options
1310 that may differ between individual
1313 Once you have a user configuration set
1314 up as you like it, you can select `OK' to
1315 store it in the user list maintained in
1316 the site configuration window.
1318 If you wish to discard the changes you have
1319 made to user options, select `Quit'.
1323 'title' : 'Local name help',
1324 'banner': 'Local names',
1326 The local name(s) in a user entry are the
1327 people on the client machine who should
1328 receive mail from the poll described.
1330 Note: if a user entry has more than one
1331 local name, messages will be retrieved
1332 in multidrop mode. This complicates
1333 the configuration issues; see the manual
1334 page section on multidrop mode.
1337 class UserEdit(Frame, MyWidget):
1338 def __init__(self, username, parent):
1339 self.parent = parent
1341 for user in parent.server.users:
1342 if user.remote == username:
1344 if self.user == None:
1346 self.user.remote = username
1347 self.user.localnames = [username]
1348 parent.server.users.append(self.user)
1350 def edit(self, mode, master=None):
1351 Frame.__init__(self, master)
1353 self.master.title('Fetchmail user ' + self.user.remote
1354 + ' querying ' + self.parent.server.pollname);
1355 self.master.iconname('Fetchmail user ' + self.user.remote);
1356 self.post(User, 'user')
1357 self.makeWidgets(mode, self.parent.server.pollname)
1358 self.keepalive = [] # Use this to anchor the PhotoImage object
1359 make_icon_window(self, fetchmail_gif)
1362 # self.wait_window()
1366 del self.parent.subwidgets[self.user.remote]
1367 Widget.destroy(self.master)
1370 if ConfirmQuit(self, 'user option editing'):
1374 self.fetch(User, 'user')
1377 def makeWidgets(self, mode, servername):
1378 dispose_window(self,
1379 "User options for " + self.user.remote + " querying " + servername,
1382 if mode != 'novice':
1383 leftwin = Frame(self);
1387 secwin = Frame(leftwin, relief=RAISED, bd=5)
1388 Label(secwin, text="Authentication").pack(side=TOP)
1389 LabeledEntry(secwin, 'Password:',
1390 self.password, '12').pack(side=TOP, fill=X)
1391 secwin.pack(fill=X, anchor=N)
1393 if 'ssl' in feature_options or 'ssl' in dictmembers:
1394 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1395 Checkbutton(sslwin, text="Use SSL?",
1396 variable=self.ssl).pack(side=TOP, fill=X)
1397 LabeledEntry(sslwin, 'SSL key:',
1398 self.sslkey, '14').pack(side=TOP, fill=X)
1399 LabeledEntry(sslwin, 'SSL certificate:',
1400 self.sslcert, '14').pack(side=TOP, fill=X)
1401 sslwin.pack(fill=X, anchor=N)
1403 names = Frame(leftwin, relief=RAISED, bd=5)
1404 Label(names, text="Local names").pack(side=TOP)
1405 ListEdit("New name: ",
1406 self.user.localnames, None, None, names, localhelp)
1407 names.pack(fill=X, anchor=N)
1409 if mode != 'novice':
1410 targwin = Frame(leftwin, relief=RAISED, bd=5)
1411 Label(targwin, text="Forwarding Options").pack(side=TOP)
1412 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1413 ListEdit("New listener:",
1414 self.user.smtphunt, None, None, targwin, None)
1415 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1416 self.smtpaddress, '26').pack(side=TOP, fill=X)
1417 LabeledEntry(targwin, 'Connection setup command:',
1418 self.preconnect, '26').pack(side=TOP, fill=X)
1419 LabeledEntry(targwin, 'Connection wrapup command:',
1420 self.postconnect, '26').pack(side=TOP, fill=X)
1421 LabeledEntry(targwin, 'Local delivery agent:',
1422 self.mda, '26').pack(side=TOP, fill=X)
1423 LabeledEntry(targwin, 'BSMTP output file:',
1424 self.bsmtp, '26').pack(side=TOP, fill=X)
1425 LabeledEntry(targwin, 'Listener spam-block codes:',
1426 self.antispam, '26').pack(side=TOP, fill=X)
1427 LabeledEntry(targwin, 'Pass-through properties:',
1428 self.properties, '26').pack(side=TOP, fill=X)
1429 Checkbutton(targwin, text="Use LMTP?",
1430 variable=self.lmtp).pack(side=TOP, fill=X)
1431 targwin.pack(fill=X, anchor=N)
1433 if mode != 'novice':
1434 leftwin.pack(side=LEFT, fill=X, anchor=N)
1435 rightwin = Frame(self)
1439 optwin = Frame(rightwin, relief=RAISED, bd=5)
1440 Label(optwin, text="Processing Options").pack(side=TOP)
1441 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1442 variable=self.keep).pack(side=TOP, anchor=W)
1443 Checkbutton(optwin, text="Fetch old messages as well as new",
1444 variable=self.fetchall).pack(side=TOP, anchor=W)
1445 if mode != 'novice':
1446 Checkbutton(optwin, text="Flush seen messages before retrieval",
1447 variable=self.flush).pack(side=TOP, anchor=W)
1448 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1449 variable=self.rewrite).pack(side=TOP, anchor=W)
1450 Checkbutton(optwin, text="Force CR/LF at end of each line",
1451 variable=self.forcecr).pack(side=TOP, anchor=W)
1452 Checkbutton(optwin, text="Strip CR from end of each line",
1453 variable=self.stripcr).pack(side=TOP, anchor=W)
1454 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1455 variable=self.pass8bits).pack(side=TOP, anchor=W)
1456 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1457 variable=self.mimedecode).pack(side=TOP, anchor=W)
1458 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1459 variable=self.dropstatus).pack(side=TOP, anchor=W)
1462 if mode != 'novice':
1463 limwin = Frame(rightwin, relief=RAISED, bd=5)
1464 Label(limwin, text="Resource Limits").pack(side=TOP)
1465 LabeledEntry(limwin, 'Message size limit:',
1466 self.limit, '30').pack(side=TOP, fill=X)
1467 LabeledEntry(limwin, 'Size warning interval:',
1468 self.warnings, '30').pack(side=TOP, fill=X)
1469 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1470 self.fetchlimit, '30').pack(side=TOP, fill=X)
1471 LabeledEntry(limwin, 'Max messages to forward per poll:',
1472 self.batchlimit, '30').pack(side=TOP, fill=X)
1473 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1474 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1475 self.expunge, '30').pack(side=TOP, fill=X)
1478 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1479 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1480 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1481 ListEdit("New folder:", self.user.mailboxes,
1482 None, None, foldwin, None)
1483 foldwin.pack(fill=X, anchor=N)
1485 if mode != 'novice':
1486 rightwin.pack(side=LEFT)
1492 # Top-level window that offers either novice or expert mode
1493 # (but not both at once; it disappears when one is selected).
1496 class Configurator(Frame):
1497 def __init__(self, outfile, master, onexit, parent):
1498 Frame.__init__(self, master)
1499 self.outfile = outfile
1500 self.onexit = onexit
1501 self.parent = parent
1502 self.master.title('fetchmail configurator');
1503 self.master.iconname('fetchmail configurator');
1505 self.keepalive = [] # Use this to anchor the PhotoImage object
1506 make_icon_window(self, fetchmail_gif)
1508 Message(self, text="""
1509 Use `Novice Configuration' for basic fetchmail setup;
1510 with this, you can easily set up a single-drop connection
1511 to one remote mail server.
1512 """, width=600).pack(side=TOP)
1513 Button(self, text='Novice Configuration',
1514 fg='blue', command=self.novice).pack()
1516 Message(self, text="""
1517 Use `Expert Configuration' for advanced fetchmail setup,
1518 including multiple-site or multidrop connections.
1519 """, width=600).pack(side=TOP)
1520 Button(self, text='Expert Configuration',
1521 fg='blue', command=self.expert).pack()
1523 Message(self, text="""
1524 Or you can just select `Quit' to leave the configurator now and
1525 return to the main panel.
1526 """, width=600).pack(side=TOP)
1527 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1528 master.protocol("WM_DELETE_WINDOW", self.leave)
1531 self.master.destroy()
1532 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1535 self.master.destroy()
1536 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1539 self.master.destroy()
1542 # Run a command in a scrolling text widget, displaying its output
1544 class RunWindow(Frame):
1545 def __init__(self, command, master, parent):
1546 Frame.__init__(self, master)
1547 self.master = master
1548 self.master.title('fetchmail run window');
1549 self.master.iconname('fetchmail run window');
1552 text="Running "+command,
1553 bd=2).pack(side=TOP, pady=10)
1554 self.keepalive = [] # Use this to anchor the PhotoImage object
1555 make_icon_window(self, fetchmail_gif)
1557 # This is a scrolling text window
1558 textframe = Frame(self)
1559 scroll = Scrollbar(textframe)
1560 self.textwidget = Text(textframe, setgrid=TRUE)
1561 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1562 self.textwidget.config(yscrollcommand=scroll.set)
1563 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1564 scroll.config(command=self.textwidget.yview)
1565 scroll.pack(side=RIGHT, fill=BOTH)
1566 textframe.pack(side=TOP)
1568 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1570 self.update() # Draw widget before executing fetchmail
1572 child_stdout = os.popen(command + " 2>&1", "r")
1574 ch = child_stdout.read(1)
1577 self.textwidget.insert(END, ch)
1578 self.textwidget.insert(END, "Done.")
1579 self.textwidget.see(END);
1582 Widget.destroy(self.master)
1584 # Here's where we choose either configuration or launching
1586 class MainWindow(Frame):
1587 def __init__(self, outfile, master=None):
1588 Frame.__init__(self, master)
1589 self.outfile = outfile
1590 self.master.title('fetchmail launcher');
1591 self.master.iconname('fetchmail launcher');
1594 text='Fetchmailconf ' + version,
1595 bd=2).pack(side=TOP, pady=10)
1596 self.keepalive = [] # Use this to anchor the PhotoImage object
1597 make_icon_window(self, fetchmail_gif)
1600 Message(self, text="""
1601 Use `Configure fetchmail' to tell fetchmail about the remote
1602 servers it should poll (the host name, your username there,
1603 whether to use POP or IMAP, and so forth).
1604 """, width=600).pack(side=TOP)
1605 self.configbutton = Button(self, text='Configure fetchmail',
1606 fg='blue', command=self.configure)
1607 self.configbutton.pack()
1609 Message(self, text="""
1610 Use `Test fetchmail' to run fetchmail with debugging enabled.
1611 This is a good way to test out a new configuration.
1612 """, width=600).pack(side=TOP)
1613 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1615 Message(self, text="""
1616 Use `Run fetchmail' to run fetchmail in foreground.
1617 Progress messages will be shown, but not debug messages.
1618 """, width=600).pack(side=TOP)
1619 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1621 Message(self, text="""
1622 Or you can just select `Quit' to exit the launcher now.
1623 """, width=600).pack(side=TOP)
1624 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1626 def configure(self):
1627 self.configbutton.configure(state=DISABLED)
1628 Configurator(self.outfile, Toplevel(),
1629 lambda self=self: self.configbutton.configure(state=NORMAL),
1633 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1636 RunWindow("fetchmail -d0", Toplevel(), self)
1641 # Functions for turning a dictionary into an instantiated object tree.
1643 def intersect(list1, list2):
1644 # Compute set intersection of lists
1651 def setdiff(list1, list2):
1652 # Compute set difference of lists
1659 def copy_instance(toclass, fromdict):
1660 # Initialize a class object of given type from a conformant dictionary.
1661 for fld in fromdict.keys():
1662 if not fld in dictmembers:
1663 dictmembers.append(fld)
1664 # The `optional' fields are the ones we can ignore for purposes of
1665 # conformability checking; they'll still get copied if they are
1666 # present in the dictionary.
1667 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1668 class_sig = setdiff(toclass.__dict__.keys(), optional)
1670 dict_keys = setdiff(fromdict.keys(), optional)
1672 common = intersect(class_sig, dict_keys)
1673 if 'typemap' in class_sig:
1674 class_sig.remove('typemap')
1675 if tuple(class_sig) != tuple(dict_keys):
1676 print "Fields don't match what fetchmailconf expected:"
1677 # print "Class signature: " + `class_sig`
1678 # print "Dictionary keys: " + `dict_keys`
1679 diff = setdiff(class_sig, common)
1681 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1682 diff = setdiff(dict_keys, common)
1684 print "Not matched in dictionary keys: " + `diff`
1687 for x in fromdict.keys():
1688 setattr(toclass, x, fromdict[x])
1691 # And this is the main sequence. How it works:
1693 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1694 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1695 # Run execfile on the file to pull fetchmailrc into Python global space.
1696 # You don't want static data, though; you want, instead, a tree of objects
1697 # with the same data members and added appropriate methods.
1699 # This is what the copy_instance function() is for. It tries to copy a
1700 # dictionary field by field into a class, aborting if the class and dictionary
1701 # have different data members (except for any typemap member in the class;
1702 # that one is strictly for use by the MyWidget supperclass).
1704 # Once the object tree is set up, require user to choose novice or expert
1705 # mode and instantiate an edit object for the configuration. Class methods
1706 # will take it all from there.
1708 # Options (not documented because they're for fetchmailconf debuggers only):
1709 # -d: Read the configuration and dump it to stdout before editing. Dump
1710 # the edited result to stdout as well.
1711 # -f: specify the run control file to read.
1713 if __name__ == '__main__':
1715 if not os.environ.has_key("DISPLAY"):
1716 print "fetchmailconf must be run under X"
1720 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1721 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1722 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1723 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1724 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1725 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1726 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1727 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1728 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1729 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1730 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1731 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1732 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1733 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1734 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1735 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1736 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1737 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1738 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1739 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1740 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1741 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1742 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1743 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1744 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1745 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1746 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1747 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1748 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1749 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1750 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1751 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1753 # Note on making icons: the above was generated by the following procedure:
1756 # data = open("fetchmail.gif", "rb").read()
1757 # print "fetchmail_gif =\\"
1758 # print repr(base64.encodestring(data))
1762 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1763 dump = rcfile = None;
1764 for (switch, val) in options:
1765 if (switch == '-d'):
1767 elif (switch == '-f'):
1770 # Get client host's FQDN
1771 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1774 ConfigurationDefaults = Configuration()
1775 ServerDefaults = Server()
1776 UserDefaults = User()
1778 # Read the existing configuration
1779 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1781 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1783 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1788 print "`" + cmd + "' run failure, status " + `s`
1791 print "Unknown error while running fetchmail --configdump"
1798 print "Can't read configuration output of fetchmail --configdump."
1804 # The tricky part -- initializing objects from the configuration global
1805 # `Configuration' is the top level of the object tree we're going to mung.
1806 # The dictmembers list is used to track the set of fields the dictionary
1807 # contains; in particular, we can use it to tell whether things like the
1808 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1810 Fetchmailrc = Configuration()
1811 copy_instance(Fetchmailrc, fetchmailrc)
1812 Fetchmailrc.servers = [];
1813 for server in fetchmailrc['servers']:
1815 copy_instance(Newsite, server)
1816 Fetchmailrc.servers.append(Newsite)
1818 for user in server['users']:
1820 copy_instance(Newuser, user)
1821 Newsite.users.append(Newuser)
1823 # We may want to display the configuration and quit
1825 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1827 # The theory here is that -f alone sets the rcfile location,
1828 # but -d and -f together mean the new configuration should go to stdout.
1829 if not rcfile and not dump:
1830 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1832 # OK, now run the configuration edit
1833 root = MainWindow(rcfile)
1836 # The following sets edit modes for GNU EMACS