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 and self.ssl != UserDefaults.ssl:
306 res = res + flag2str(self.ssl, 'ssl')
307 if self.sslkey and self.sslkey != UserDefaults.sslkey:
308 res = res + " sslkey " + `self.sslkey`
309 if self.sslcert and 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' option.
1130 if string.find(greetline, "POP-Max") > 0:
1131 warnings = warnings + """
1132 The Mail Max POP3 server screws up on mail with attachments. It reports
1133 the message size with attachments included, but doesn't download them on a
1134 RETR or TOP. You should get rid of it -- and the brain-dead NT server
1139 if string.find(greetline, "InterChange") > 0:
1140 warnings = warnings + """
1141 The InterChange IMAP server screws up on mail with attachments. It reports
1142 the message size with attachments included, but doesn't download them on a
1143 RETR or TOP. You should get rid of it.
1147 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1148 warnings = warnings + """
1149 This appears to be an old version of the UC Davis POP server. These are
1150 dangerously unreliable (among other problems, they may drop your mailbox
1151 on the floor if your connection is interrupted during the session).
1153 It is strongly recommended that you find a better POP3 server. The fetchmail
1154 FAQ includes pointers to good ones.
1157 if string.find(greetline, "usa.net") > 0:
1158 warnings = warnings + """
1159 You appear to be using USA.NET's free mail service. Their POP3 servers
1160 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1161 fetchmail can compensate. They seem to require that fetchall be switched on
1162 (otherwise you won't necessarily see all your mail, not even new mail).
1163 They also botch the TOP command the fetchmail normally uses for retrieval
1164 (it only retrieves about 10 lines rather than the number specified).
1165 Turning on fetchall will disable the use of TOP.
1167 Therefore, it is strongly recommended that you turn on `fetchall' on all
1168 user entries associated with this server.
1171 if string.find(greetline, "OpenMail") > 0:
1172 warnings = warnings + """
1173 You appear to be using some version of HP OpenMail. Many versions of
1174 OpenMail do not process the "TOP" command correctly; the symptom is that
1175 only the header and first line of each message is retrieved. To work
1176 around this bug, turn on `fetchall' on all user entries associated with
1180 if string.find(greetline, "TEMS POP3") > 0:
1181 warnings = warnings + """
1182 Your POP3 server has "TEMS" in its header line. At least one such
1183 server does not process the "TOP" command correctly; the symptom is
1184 that fetchmail hangs when trying to retrieve mail. To work around
1185 this bug, turn on `fetchall' on all user entries associated with this
1189 if string.find(greetline, "GroupWise") > 0:
1190 warnings = warnings + """
1191 The Novell GroupWise IMAP server would be better named GroupFoolish;
1192 it is (according to the designer of IMAP) unusably broken. Among
1193 other things, it doesn't include a required content length in its
1194 BODY[TEXT] response.<p>
1196 Fetchmail works around this problem, but we strongly recommend voting
1197 with your dollars for a server that isn't brain-dead. If you stick
1198 with code as shoddy as GroupWise seems to be, you will probably pay
1199 for it with other problems.<p>
1202 if string.find(greetline, "sprynet.com") > 0:
1203 warnings = warnings + """
1204 You appear to be using a SpryNet server. In mid-1999 it was reported that
1205 the SpryNet TOP command marks messages seen. Therefore, for proper error
1206 recovery in the event of a line drop, it is strongly recommended that you
1207 turn on `fetchall' on all user entries associated with this server.
1211 # Steve VanDevender <stevev@efn.org> writes:
1212 # The only system I have seen this happen with is cucipop-1.31
1213 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1214 # 2.x and probably quite a few other systems. It appears to be a
1215 # bug or bad interaction with the SunOS realloc() -- it turns out
1216 # that internally cucipop does allocate a certain data structure in
1217 # multiples of 16, using realloc() to bump it up to the next
1218 # multiple if it needs more.
1220 # The distinctive symptom is that when there are 16 messages in the
1221 # inbox, you can RETR and DELE all 16 messages successfully, but on
1222 # QUIT cucipop returns something like "-ERR Error locking your
1223 # mailbox" and aborts without updating it.
1225 # The cucipop banner looks like:
1227 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1229 if string.find(greetline, "Cubic Circle") > 0:
1230 warnings = warnings + """
1231 I see your server is running cucipop. Better make sure the server box
1232 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1233 under that version, and doesn't cope with the result gracefully. Newer
1234 SunOS and Solaris machines run cucipop OK.
1237 if string.find(greetline, "QPOP") > 0:
1238 warnings = warnings + """
1239 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1240 knows all about qpopper. However, be aware that the 2.53 version of
1241 qpopper does something odd that causes fetchmail to hang with a socket
1242 error on very large messages. This is probably not a fetchmail bug, as
1243 it has been observed with fetchpop. The fix is to upgrade to qpopper
1244 3.0beta or a more recent version. Better yet, switch to IMAP.
1247 if string.find(greetline, "Imail") > 0:
1248 warnings = warnings + """
1249 We've seen a bug report indicating that this IMAP server (at least as of
1250 version 5.0.7) returns an invalid body size for messages with MIME
1251 attachments; the effect is to drop the attachments on the floor. We
1252 recommend you upgrade to a non-broken IMAP server.
1255 closebrak = string.find(greetline, ">")
1256 if closebrak > 0 and greetline[closebrak+1] == "\r":
1257 warnings = warnings + """
1258 It looks like you could use APOP on this server and avoid sending it your
1259 password in clear. You should talk to the mailserver administrator about
1263 if string.find(greetline, "IMAP2bis") > 0:
1264 warnings = warnings + """
1265 IMAP2bis servers have a minor problem; they can't peek at messages without
1266 marking them seen. If you take a line hit during the retrieval, the
1267 interrupted message may get left on the server, marked seen.
1269 To work around this, it is recommended that you set the `fetchall'
1270 option on all user entries associated with this server, so any stuck
1271 mail will be retrieved next time around.
1273 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1274 a pointer to an open-source implementation.
1277 if string.find(greetline, "POP3 Server Ready") > 0:
1278 warnings = warnings + """
1279 Some server that uses this greeting line has been observed to choke on
1280 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1284 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1285 warnings = warnings + """
1286 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1287 set the messages's Seen flag. As a result, if you use the keep option the
1288 same messages will be downloaded over and over.
1292 if string.find(greetline, "IMAP4rev1") > 0:
1293 warnings = warnings + """
1294 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1295 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1296 has therefore been extremely well tested with this class of server.
1300 warnings = warnings + """
1301 Fetchmail doesn't know anything special about this server type.
1305 # Display success window with warnings
1306 title = "Autoprobe of " + realhost + " succeeded"
1307 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1308 self.protocol.set(protocol)
1309 confwin.title(title)
1310 confwin.iconname(title)
1311 Label(confwin, text=title).pack()
1312 Message(confwin, text=confirm, width=600).pack()
1313 Button(confwin, text='Done',
1314 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1317 # User editing stuff
1321 'title' : 'User option help',
1322 'banner': 'User options',
1324 You may use this panel to set options
1325 that may differ between individual
1328 Once you have a user configuration set
1329 up as you like it, you can select `OK' to
1330 store it in the user list maintained in
1331 the site configuration window.
1333 If you wish to discard the changes you have
1334 made to user options, select `Quit'.
1338 'title' : 'Local name help',
1339 'banner': 'Local names',
1341 The local name(s) in a user entry are the
1342 people on the client machine who should
1343 receive mail from the poll described.
1345 Note: if a user entry has more than one
1346 local name, messages will be retrieved
1347 in multidrop mode. This complicates
1348 the configuration issues; see the manual
1349 page section on multidrop mode.
1352 class UserEdit(Frame, MyWidget):
1353 def __init__(self, username, parent):
1354 self.parent = parent
1356 for user in parent.server.users:
1357 if user.remote == username:
1359 if self.user == None:
1361 self.user.remote = username
1362 self.user.localnames = [username]
1363 parent.server.users.append(self.user)
1365 def edit(self, mode, master=None):
1366 Frame.__init__(self, master)
1368 self.master.title('Fetchmail user ' + self.user.remote
1369 + ' querying ' + self.parent.server.pollname);
1370 self.master.iconname('Fetchmail user ' + self.user.remote);
1371 self.post(User, 'user')
1372 self.makeWidgets(mode, self.parent.server.pollname)
1373 self.keepalive = [] # Use this to anchor the PhotoImage object
1374 make_icon_window(self, fetchmail_gif)
1377 # self.wait_window()
1381 del self.parent.subwidgets[self.user.remote]
1382 Widget.destroy(self.master)
1385 if ConfirmQuit(self, 'user option editing'):
1389 self.fetch(User, 'user')
1392 def makeWidgets(self, mode, servername):
1393 dispose_window(self,
1394 "User options for " + self.user.remote + " querying " + servername,
1397 if mode != 'novice':
1398 leftwin = Frame(self);
1402 secwin = Frame(leftwin, relief=RAISED, bd=5)
1403 Label(secwin, text="Authentication").pack(side=TOP)
1404 LabeledEntry(secwin, 'Password:',
1405 self.password, '12').pack(side=TOP, fill=X)
1406 secwin.pack(fill=X, anchor=N)
1408 if 'ssl' in feature_options or 'ssl' in dictmembers:
1409 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1410 Checkbutton(sslwin, text="Use SSL?",
1411 variable=self.ssl).pack(side=TOP, fill=X)
1412 LabeledEntry(sslwin, 'SSL key:',
1413 self.sslkey, '14').pack(side=TOP, fill=X)
1414 LabeledEntry(sslwin, 'SSL certificate:',
1415 self.sslcert, '14').pack(side=TOP, fill=X)
1416 sslwin.pack(fill=X, anchor=N)
1418 names = Frame(leftwin, relief=RAISED, bd=5)
1419 Label(names, text="Local names").pack(side=TOP)
1420 ListEdit("New name: ",
1421 self.user.localnames, None, None, names, localhelp)
1422 names.pack(fill=X, anchor=N)
1424 if mode != 'novice':
1425 targwin = Frame(leftwin, relief=RAISED, bd=5)
1426 Label(targwin, text="Forwarding Options").pack(side=TOP)
1427 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1428 ListEdit("New listener:",
1429 self.user.smtphunt, None, None, targwin, None)
1430 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1431 self.smtpaddress, '26').pack(side=TOP, fill=X)
1432 LabeledEntry(targwin, 'Connection setup command:',
1433 self.preconnect, '26').pack(side=TOP, fill=X)
1434 LabeledEntry(targwin, 'Connection wrapup command:',
1435 self.postconnect, '26').pack(side=TOP, fill=X)
1436 LabeledEntry(targwin, 'Local delivery agent:',
1437 self.mda, '26').pack(side=TOP, fill=X)
1438 LabeledEntry(targwin, 'BSMTP output file:',
1439 self.bsmtp, '26').pack(side=TOP, fill=X)
1440 LabeledEntry(targwin, 'Listener spam-block codes:',
1441 self.antispam, '26').pack(side=TOP, fill=X)
1442 LabeledEntry(targwin, 'Pass-through properties:',
1443 self.properties, '26').pack(side=TOP, fill=X)
1444 Checkbutton(targwin, text="Use LMTP?",
1445 variable=self.lmtp).pack(side=TOP, fill=X)
1446 targwin.pack(fill=X, anchor=N)
1448 if mode != 'novice':
1449 leftwin.pack(side=LEFT, fill=X, anchor=N)
1450 rightwin = Frame(self)
1454 optwin = Frame(rightwin, relief=RAISED, bd=5)
1455 Label(optwin, text="Processing Options").pack(side=TOP)
1456 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1457 variable=self.keep).pack(side=TOP, anchor=W)
1458 Checkbutton(optwin, text="Fetch old messages as well as new",
1459 variable=self.fetchall).pack(side=TOP, anchor=W)
1460 if mode != 'novice':
1461 Checkbutton(optwin, text="Flush seen messages before retrieval",
1462 variable=self.flush).pack(side=TOP, anchor=W)
1463 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1464 variable=self.rewrite).pack(side=TOP, anchor=W)
1465 Checkbutton(optwin, text="Force CR/LF at end of each line",
1466 variable=self.forcecr).pack(side=TOP, anchor=W)
1467 Checkbutton(optwin, text="Strip CR from end of each line",
1468 variable=self.stripcr).pack(side=TOP, anchor=W)
1469 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1470 variable=self.pass8bits).pack(side=TOP, anchor=W)
1471 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1472 variable=self.mimedecode).pack(side=TOP, anchor=W)
1473 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1474 variable=self.dropstatus).pack(side=TOP, anchor=W)
1477 if mode != 'novice':
1478 limwin = Frame(rightwin, relief=RAISED, bd=5)
1479 Label(limwin, text="Resource Limits").pack(side=TOP)
1480 LabeledEntry(limwin, 'Message size limit:',
1481 self.limit, '30').pack(side=TOP, fill=X)
1482 LabeledEntry(limwin, 'Size warning interval:',
1483 self.warnings, '30').pack(side=TOP, fill=X)
1484 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1485 self.fetchlimit, '30').pack(side=TOP, fill=X)
1486 LabeledEntry(limwin, 'Max messages to forward per poll:',
1487 self.batchlimit, '30').pack(side=TOP, fill=X)
1488 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1489 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1490 self.expunge, '30').pack(side=TOP, fill=X)
1493 if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1494 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1495 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1496 ListEdit("New folder:", self.user.mailboxes,
1497 None, None, foldwin, None)
1498 foldwin.pack(fill=X, anchor=N)
1500 if mode != 'novice':
1501 rightwin.pack(side=LEFT)
1507 # Top-level window that offers either novice or expert mode
1508 # (but not both at once; it disappears when one is selected).
1511 class Configurator(Frame):
1512 def __init__(self, outfile, master, onexit, parent):
1513 Frame.__init__(self, master)
1514 self.outfile = outfile
1515 self.onexit = onexit
1516 self.parent = parent
1517 self.master.title('fetchmail configurator');
1518 self.master.iconname('fetchmail configurator');
1520 self.keepalive = [] # Use this to anchor the PhotoImage object
1521 make_icon_window(self, fetchmail_gif)
1523 Message(self, text="""
1524 Use `Novice Configuration' for basic fetchmail setup;
1525 with this, you can easily set up a single-drop connection
1526 to one remote mail server.
1527 """, width=600).pack(side=TOP)
1528 Button(self, text='Novice Configuration',
1529 fg='blue', command=self.novice).pack()
1531 Message(self, text="""
1532 Use `Expert Configuration' for advanced fetchmail setup,
1533 including multiple-site or multidrop connections.
1534 """, width=600).pack(side=TOP)
1535 Button(self, text='Expert Configuration',
1536 fg='blue', command=self.expert).pack()
1538 Message(self, text="""
1539 Or you can just select `Quit' to leave the configurator now and
1540 return to the main panel.
1541 """, width=600).pack(side=TOP)
1542 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1543 master.protocol("WM_DELETE_WINDOW", self.leave)
1546 self.master.destroy()
1547 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1550 self.master.destroy()
1551 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1554 self.master.destroy()
1557 # Run a command in a scrolling text widget, displaying its output
1559 class RunWindow(Frame):
1560 def __init__(self, command, master, parent):
1561 Frame.__init__(self, master)
1562 self.master = master
1563 self.master.title('fetchmail run window');
1564 self.master.iconname('fetchmail run window');
1567 text="Running "+command,
1568 bd=2).pack(side=TOP, pady=10)
1569 self.keepalive = [] # Use this to anchor the PhotoImage object
1570 make_icon_window(self, fetchmail_gif)
1572 # This is a scrolling text window
1573 textframe = Frame(self)
1574 scroll = Scrollbar(textframe)
1575 self.textwidget = Text(textframe, setgrid=TRUE)
1576 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1577 self.textwidget.config(yscrollcommand=scroll.set)
1578 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1579 scroll.config(command=self.textwidget.yview)
1580 scroll.pack(side=RIGHT, fill=BOTH)
1581 textframe.pack(side=TOP)
1583 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1585 self.update() # Draw widget before executing fetchmail
1587 child_stdout = os.popen(command + " 2>&1", "r")
1589 ch = child_stdout.read(1)
1592 self.textwidget.insert(END, ch)
1593 self.textwidget.insert(END, "Done.")
1594 self.textwidget.see(END);
1597 Widget.destroy(self.master)
1599 # Here's where we choose either configuration or launching
1601 class MainWindow(Frame):
1602 def __init__(self, outfile, master=None):
1603 Frame.__init__(self, master)
1604 self.outfile = outfile
1605 self.master.title('fetchmail launcher');
1606 self.master.iconname('fetchmail launcher');
1609 text='Fetchmailconf ' + version,
1610 bd=2).pack(side=TOP, pady=10)
1611 self.keepalive = [] # Use this to anchor the PhotoImage object
1612 make_icon_window(self, fetchmail_gif)
1615 Message(self, text="""
1616 Use `Configure fetchmail' to tell fetchmail about the remote
1617 servers it should poll (the host name, your username there,
1618 whether to use POP or IMAP, and so forth).
1619 """, width=600).pack(side=TOP)
1620 self.configbutton = Button(self, text='Configure fetchmail',
1621 fg='blue', command=self.configure)
1622 self.configbutton.pack()
1624 Message(self, text="""
1625 Use `Test fetchmail' to run fetchmail with debugging enabled.
1626 This is a good way to test out a new configuration.
1627 """, width=600).pack(side=TOP)
1628 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1630 Message(self, text="""
1631 Use `Run fetchmail' to run fetchmail in foreground.
1632 Progress messages will be shown, but not debug messages.
1633 """, width=600).pack(side=TOP)
1634 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1636 Message(self, text="""
1637 Or you can just select `Quit' to exit the launcher now.
1638 """, width=600).pack(side=TOP)
1639 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1641 def configure(self):
1642 self.configbutton.configure(state=DISABLED)
1643 Configurator(self.outfile, Toplevel(),
1644 lambda self=self: self.configbutton.configure(state=NORMAL),
1648 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1651 RunWindow("fetchmail -d0", Toplevel(), self)
1656 # Functions for turning a dictionary into an instantiated object tree.
1658 def intersect(list1, list2):
1659 # Compute set intersection of lists
1666 def setdiff(list1, list2):
1667 # Compute set difference of lists
1674 def copy_instance(toclass, fromdict):
1675 # Initialize a class object of given type from a conformant dictionary.
1676 for fld in fromdict.keys():
1677 if not fld in dictmembers:
1678 dictmembers.append(fld)
1679 # The `optional' fields are the ones we can ignore for purposes of
1680 # conformability checking; they'll still get copied if they are
1681 # present in the dictionary.
1682 optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1683 class_sig = setdiff(toclass.__dict__.keys(), optional)
1685 dict_keys = setdiff(fromdict.keys(), optional)
1687 common = intersect(class_sig, dict_keys)
1688 if 'typemap' in class_sig:
1689 class_sig.remove('typemap')
1690 if tuple(class_sig) != tuple(dict_keys):
1691 print "Fields don't match what fetchmailconf expected:"
1692 # print "Class signature: " + `class_sig`
1693 # print "Dictionary keys: " + `dict_keys`
1694 diff = setdiff(class_sig, common)
1696 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1697 diff = setdiff(dict_keys, common)
1699 print "Not matched in dictionary keys: " + `diff`
1702 for x in fromdict.keys():
1703 setattr(toclass, x, fromdict[x])
1706 # And this is the main sequence. How it works:
1708 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1709 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1710 # Run execfile on the file to pull fetchmailrc into Python global space.
1711 # You don't want static data, though; you want, instead, a tree of objects
1712 # with the same data members and added appropriate methods.
1714 # This is what the copy_instance function() is for. It tries to copy a
1715 # dictionary field by field into a class, aborting if the class and dictionary
1716 # have different data members (except for any typemap member in the class;
1717 # that one is strictly for use by the MyWidget supperclass).
1719 # Once the object tree is set up, require user to choose novice or expert
1720 # mode and instantiate an edit object for the configuration. Class methods
1721 # will take it all from there.
1723 # Options (not documented because they're for fetchmailconf debuggers only):
1724 # -d: Read the configuration and dump it to stdout before editing. Dump
1725 # the edited result to stdout as well.
1726 # -f: specify the run control file to read.
1728 if __name__ == '__main__':
1730 if not os.environ.has_key("DISPLAY"):
1731 print "fetchmailconf must be run under X"
1735 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1736 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1737 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1738 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1739 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1740 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1741 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1742 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1743 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1744 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1745 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1746 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1747 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1748 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1749 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1750 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1751 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1752 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1753 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1754 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1755 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1756 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1757 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1758 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1759 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1760 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1761 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1762 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1763 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1764 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1765 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1766 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1768 # Note on making icons: the above was generated by the following procedure:
1771 # data = open("fetchmail.gif", "rb").read()
1772 # print "fetchmail_gif =\\"
1773 # print repr(base64.encodestring(data))
1777 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1778 dump = rcfile = None;
1779 for (switch, val) in options:
1780 if (switch == '-d'):
1782 elif (switch == '-f'):
1785 # Get client host's FQDN
1786 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1789 ConfigurationDefaults = Configuration()
1790 ServerDefaults = Server()
1791 UserDefaults = User()
1793 # Read the existing configuration
1794 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1796 cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1798 cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1803 print "`" + cmd + "' run failure, status " + `s`
1806 print "Unknown error while running fetchmail --configdump"
1813 print "Can't read configuration output of fetchmail --configdump."
1819 # The tricky part -- initializing objects from the configuration global
1820 # `Configuration' is the top level of the object tree we're going to mung.
1821 # The dictmembers list is used to track the set of fields the dictionary
1822 # contains; in particular, we can use it to tell whether things like the
1823 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1825 Fetchmailrc = Configuration()
1826 copy_instance(Fetchmailrc, fetchmailrc)
1827 Fetchmailrc.servers = [];
1828 for server in fetchmailrc['servers']:
1830 copy_instance(Newsite, server)
1831 Fetchmailrc.servers.append(Newsite)
1833 for user in server['users']:
1835 copy_instance(Newuser, user)
1836 Newsite.users.append(Newuser)
1838 # We may want to display the configuration and quit
1840 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1842 # The theory here is that -f alone sets the rcfile location,
1843 # but -d and -f together mean the new configuration should go to stdout.
1844 if not rcfile and not dump:
1845 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1847 # OK, now run the configuration edit
1848 root = MainWindow(rcfile)
1851 # The following sets edit modes for GNU EMACS