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, tempfile
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.spambounce = FALSE # Bounce spam errors
24 self.properties = None # No exiguous properties
25 self.invisible = FALSE # Suppress Received line & spoof?
26 self.syslog = FALSE # Use syslogd for logging?
27 self.servers = [] # List of included sites
28 Configuration.typemap = (
29 ('poll_interval', 'Int'),
30 ('logfile', 'String'),
32 ('postmaster', 'String'),
33 ('bouncemail', 'Boolean'),
34 ('spambounce', 'Boolean'),
35 ('properties', 'String'),
36 ('syslog', 'Boolean'),
37 ('invisible', 'Boolean'))
41 if self.syslog != ConfigurationDefaults.syslog:
42 str = str + ("set syslog\n")
44 str = str + ("set logfile \"%s\"\n" % (self.logfile,));
45 if self.idfile != ConfigurationDefaults.idfile:
46 str = str + ("set idfile \"%s\"\n" % (self.idfile,));
47 if self.postmaster != ConfigurationDefaults.postmaster:
48 str = str + ("set postmaster \"%s\"\n" % (self.postmaster,));
50 str = str + ("set bouncemail\n")
52 str = str + ("set nobouncemail\n")
54 str = str + ("set spambounce\n")
56 str = str + ("set no spambounce\n")
57 if self.properties != ConfigurationDefaults.properties:
58 str = str + ("set properties \"%s\"\n" % (self.properties,));
59 if self.poll_interval > 0:
60 str = str + "set daemon " + `self.poll_interval` + "\n"
61 for site in self.servers:
62 str = str + repr(site)
65 def __delitem__(self, name):
66 for si in range(len(self.servers)):
67 if self.servers[si].pollname == name:
72 return "[Configuration: " + repr(self) + "]"
76 self.pollname = None # Poll label
77 self.via = None # True name of host
78 self.active = TRUE # Poll status
79 self.interval = 0 # Skip interval
80 self.protocol = 'auto' # Default to auto protocol
81 self.port = 0 # Port number to use
82 self.uidl = FALSE # Don't use RFC1725 UIDLs by default
83 self.auth = 'any' # Default to password authentication
84 self.timeout = 300 # 5-minute timeout
85 self.envelope = 'Received' # Envelope-address header
86 self.envskip = 0 # Number of envelope headers to skip
87 self.qvirtual = None # Name prefix to strip
88 self.aka = [] # List of DNS aka names
89 self.dns = TRUE # Enable DNS lookup on multidrop
90 self.localdomains = [] # Domains to be considered local
91 self.interface = None # IP address and range
92 self.monitor = None # IP address and range
93 self.plugin = None # Plugin command for going to server
94 self.plugout = None # Plugin command for going to listener
95 self.netsec = None # IPV6 security options
96 self.principal = None # Kerberos principal
97 self.esmtpname = None # ESMTP 2554 name
98 self.esmtppassword = None # ESMTP 2554 password
99 self.tracepolls = FALSE # Add trace-poll info to headers
100 self.users = [] # List of user entries for site
102 ('pollname', 'String'),
104 ('active', 'Boolean'),
106 ('protocol', 'String'),
111 ('envelope', 'String'),
113 ('qvirtual', 'String'),
116 # leave localdomains out
117 ('interface', 'String'),
118 ('monitor', 'String'),
119 ('plugin', 'String'),
120 ('plugout', 'String'),
121 ('esmtpname', 'String'),
122 ('esmtppassword', 'String'),
123 ('netsec', 'String'),
124 ('principal', 'String'),
125 ('tracepolls','Boolean'))
127 def dump(self, folded):
129 if self.active: res = res + "poll"
130 else: res = res + "skip"
131 res = res + (" " + self.pollname)
133 res = res + (" via " + str(self.via) + "\n");
134 if self.protocol != ServerDefaults.protocol:
135 res = res + " with proto " + self.protocol
136 if self.port != defaultports[self.protocol] and self.port != 0:
137 res = res + " port " + `self.port`
138 if self.timeout != ServerDefaults.timeout:
139 res = res + " timeout " + `self.timeout`
140 if self.interval != ServerDefaults.interval:
141 res = res + " interval " + `self.interval`
142 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
144 res = res + " envelope " + `self.envskip` + " " + self.envelope
146 res = res + " envelope " + self.envelope
148 res = res + (" qvirtual " + str(self.qvirtual) + "\n");
149 if self.auth != ServerDefaults.auth:
150 res = res + " auth " + self.auth
151 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
152 res = res + " and options"
153 if self.dns != ServerDefaults.dns:
154 res = res + flag2str(self.dns, 'dns')
155 if self.uidl != ServerDefaults.uidl:
156 res = res + flag2str(self.uidl, 'uidl')
157 if folded: res = res + "\n "
158 else: res = res + " "
164 if self.aka and self.localdomains: res = res + " "
165 if self.localdomains:
166 res = res + ("localdomains")
167 for x in self.localdomains:
169 if (self.aka or self.localdomains):
176 res = res + "tracepolls\n"
179 res = res + " interface " + str(self.interface)
181 res = res + " monitor " + str(self.monitor)
183 res = res + " plugin " + `self.plugin`
185 res = res + " plugout " + `self.plugout`
187 res = res + " netsec " + str(self.netsec)
189 res = res + " principal " + `self.principal`
191 res = res + " esmtpname " + `self.esmtpname`
192 if self.esmtppassword:
193 res = res + " esmtppassword " + `self.esmtppassword`
194 if self.interface or self.monitor or self.netsec or self.principal or self.plugin or self.plugout:
198 if res[-1] == " ": res = res[0:-1]
200 for user in self.users:
201 res = res + repr(user)
205 def __delitem__(self, name):
206 for ui in range(len(self.users)):
207 if self.users[ui].remote == name:
212 return self.dump(TRUE)
215 return "[Server: " + self.dump(FALSE) + "]"
219 if os.environ.has_key("USER"):
220 self.remote = os.environ["USER"] # Remote username
221 elif os.environ.has_key("LOGNAME"):
222 self.remote = os.environ["LOGNAME"]
224 print "Can't get your username!"
226 self.localnames = [self.remote,]# Local names
227 self.password = None # Password for mail account access
228 self.mailboxes = [] # Remote folders to retrieve from
229 self.smtphunt = [] # Hosts to forward to
230 self.fetchdomains = [] # Domains to fetch from
231 self.smtpaddress = None # Append this to MAIL FROM line
232 self.smtpname = None # Use this for RCPT TO
233 self.preconnect = None # Connection setup
234 self.postconnect = None # Connection wrapup
235 self.mda = None # Mail Delivery Agent
236 self.bsmtp = None # BSMTP output file
237 self.lmtp = FALSE # Use LMTP rather than SMTP?
238 self.antispam = "571 550 501 554" # Listener's spam-block code
239 self.keep = FALSE # Keep messages
240 self.flush = FALSE # Flush messages
241 self.fetchall = FALSE # Fetch old messages
242 self.rewrite = TRUE # Rewrite message headers
243 self.forcecr = FALSE # Force LF -> CR/LF
244 self.stripcr = FALSE # Strip CR
245 self.pass8bits = FALSE # Force BODY=7BIT
246 self.mimedecode = FALSE # Undo MIME armoring
247 self.dropstatus = FALSE # Drop incoming Status lines
248 self.dropdelivered = FALSE # Drop incoming Delivered-To lines
249 self.idle = FALSE # IDLE after poll
250 self.limit = 0 # Message size limit
251 self.warnings = 3600 # Size warning interval (see tunable.h)
252 self.fetchlimit = 0 # Max messages fetched per batch
253 self.batchlimit = 0 # Max message forwarded per batch
254 self.expunge = 0 # Interval between expunges (IMAP)
255 self.ssl = 0 # Enable Seccure Socket Layer
256 self.sslkey = None # SSL key filename
257 self.sslcert = None # SSL certificate filename
258 self.sslproto = None # Force SSL?
259 self.sslcertck = 0 # Enable strict SSL cert checking
260 self.sslcertpath = None # Path to trusted certificates
261 self.sslfingerprint = None # SSL key fingerprint to check
262 self.properties = None # Extension properties
264 ('remote', 'String'),
265 # leave out mailboxes and localnames
266 ('password', 'String'),
267 # Leave out smtphunt, fetchdomains
268 ('smtpaddress', 'String'),
269 ('smtpname', 'String'),
270 ('preconnect', 'String'),
271 ('postconnect', 'String'),
275 ('antispam', 'String'),
277 ('flush', 'Boolean'),
278 ('fetchall', 'Boolean'),
279 ('rewrite', 'Boolean'),
280 ('forcecr', 'Boolean'),
281 ('stripcr', 'Boolean'),
282 ('pass8bits', 'Boolean'),
283 ('mimedecode', 'Boolean'),
284 ('dropstatus', 'Boolean'),
285 ('dropdelivered', 'Boolean'),
289 ('fetchlimit', 'Int'),
290 ('batchlimit', 'Int'),
293 ('sslkey', 'String'),
294 ('sslcert', 'String'),
295 ('sslcertck', 'Boolean'),
296 ('sslcertpath', 'String'),
297 ('sslfingerprint', 'String'),
298 ('properties', 'String'))
302 res = res + "user " + `self.remote` + " there ";
304 res = res + "with password " + `self.password` + " "
307 for x in self.localnames:
308 res = res + " " + `x`
310 if (self.keep != UserDefaults.keep
311 or self.flush != UserDefaults.flush
312 or self.fetchall != UserDefaults.fetchall
313 or self.rewrite != UserDefaults.rewrite
314 or self.forcecr != UserDefaults.forcecr
315 or self.stripcr != UserDefaults.stripcr
316 or self.pass8bits != UserDefaults.pass8bits
317 or self.mimedecode != UserDefaults.mimedecode
318 or self.dropstatus != UserDefaults.dropstatus
319 or self.dropdelivered != UserDefaults.dropdelivered
320 or self.idle != UserDefaults.idle):
321 res = res + " options"
322 if self.keep != UserDefaults.keep:
323 res = res + flag2str(self.keep, 'keep')
324 if self.flush != UserDefaults.flush:
325 res = res + flag2str(self.flush, 'flush')
326 if self.fetchall != UserDefaults.fetchall:
327 res = res + flag2str(self.fetchall, 'fetchall')
328 if self.rewrite != UserDefaults.rewrite:
329 res = res + flag2str(self.rewrite, 'rewrite')
330 if self.forcecr != UserDefaults.forcecr:
331 res = res + flag2str(self.forcecr, 'forcecr')
332 if self.stripcr != UserDefaults.stripcr:
333 res = res + flag2str(self.stripcr, 'stripcr')
334 if self.pass8bits != UserDefaults.pass8bits:
335 res = res + flag2str(self.pass8bits, 'pass8bits')
336 if self.mimedecode != UserDefaults.mimedecode:
337 res = res + flag2str(self.mimedecode, 'mimedecode')
338 if self.dropstatus != UserDefaults.dropstatus:
339 res = res + flag2str(self.dropstatus, 'dropstatus')
340 if self.dropdelivered != UserDefaults.dropdelivered:
341 res = res + flag2str(self.dropdelivered, 'dropdelivered')
342 if self.idle != UserDefaults.idle:
343 res = res + flag2str(self.idle, 'idle')
344 if self.limit != UserDefaults.limit:
345 res = res + " limit " + `self.limit`
346 if self.warnings != UserDefaults.warnings:
347 res = res + " warnings " + `self.warnings`
348 if self.fetchlimit != UserDefaults.fetchlimit:
349 res = res + " fetchlimit " + `self.fetchlimit`
350 if self.batchlimit != UserDefaults.batchlimit:
351 res = res + " batchlimit " + `self.batchlimit`
352 if self.ssl and self.ssl != UserDefaults.ssl:
353 res = res + flag2str(self.ssl, 'ssl')
354 if self.sslkey and self.sslkey != UserDefaults.sslkey:
355 res = res + " sslkey " + `self.sslkey`
356 if self.sslcert and self.sslcert != UserDefaults.sslcert:
357 res = res + " sslcert " + `self.sslcert`
358 if self.sslproto and self.sslproto != UserDefaults.sslproto:
359 res = res + " sslproto " + `self.sslproto`
360 if self.sslcertck and self.sslcertck != UserDefaults.sslcertck:
361 res = res + flag2str(self.sslcertck, 'sslcertck')
362 if self.sslcertpath and self.sslcertpath != UserDefaults.sslcertpath:
363 res = res + " sslcertpath " + `self.sslcertpath`
364 if self.sslfingerprint and self.sslfingerprint != UserDefaults.sslfingerprint:
365 res = res + " sslfingerprint " + `self.sslfingerprint`
366 if self.expunge != UserDefaults.expunge:
367 res = res + " expunge " + `self.expunge`
369 trimmed = self.smtphunt;
370 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
371 trimmed = trimmed[0:len(trimmed) - 1]
372 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
373 trimmed = trimmed[0:len(trimmed) - 1]
375 res = res + " smtphost "
379 trimmed = self.fetchdomains
380 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
381 trimmed = trimmed[0:len(trimmed) - 1]
383 res = res + " fetchdomains "
388 res = res + " folder"
389 for x in self.mailboxes:
392 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
393 if getattr(self, fld):
394 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
395 if self.lmtp != UserDefaults.lmtp:
396 res = res + flag2str(self.lmtp, 'lmtp')
397 if self.antispam != UserDefaults.antispam:
398 res = res + " antispam " + self.antispam + "\n"
402 return "[User: " + repr(self) + "]"
408 defaultports = {"auto":0,
417 authlist = ("any", "password", "gssapi", "kerberos", "ssh")
420 'title' : 'List Selection Help',
421 'banner': 'List Selection',
423 You must select an item in the list box (by clicking on it).
426 def flag2str(value, string):
427 # make a string representation of a .fetchmailrc flag or negated flag
431 if value == FALSE: str = str + ("no ")
435 class LabeledEntry(Frame):
436 # widget consisting of entry field with caption to left
437 def bind(self, key, action):
438 self.E.bind(key, action)
441 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
442 Frame.__init__(self, Master)
443 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
444 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
445 self.L.pack({'side':'left'})
446 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
448 def ButtonBar(frame, legend, ref, alternatives, depth, command):
449 # array of radio buttons, caption to left, picking from a string list
451 width = (len(alternatives)+1) / depth;
452 Label(bar, text=legend).pack(side=LEFT)
453 for column in range(width):
454 subframe = Frame(bar)
455 for row in range(depth):
456 ind = width * row + column
457 if ind < len(alternatives):
458 Radiobutton(subframe,
459 {'text':alternatives[ind],
461 'value':alternatives[ind],
462 'command':command}).pack(side=TOP, anchor=W)
464 # This is just a spacer
465 Radiobutton(subframe,
466 {'text':" ",'state':DISABLED}).pack(side=TOP, anchor=W)
467 subframe.pack(side=LEFT)
471 def helpwin(helpdict):
472 # help message window with a self-destruct button
474 helpwin.title(helpdict['title'])
475 helpwin.iconname(helpdict['title'])
476 Label(helpwin, text=helpdict['banner']).pack()
477 textframe = Frame(helpwin)
478 scroll = Scrollbar(textframe)
479 helpwin.textwidget = Text(textframe, setgrid=TRUE)
480 textframe.pack(side=TOP, expand=YES, fill=BOTH)
481 helpwin.textwidget.config(yscrollcommand=scroll.set)
482 helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
483 scroll.config(command=helpwin.textwidget.yview)
484 scroll.pack(side=RIGHT, fill=BOTH)
485 helpwin.textwidget.insert(END, helpdict['text']);
486 Button(helpwin, text='Done',
487 command=lambda x=helpwin: x.destroy(), bd=2).pack()
488 textframe.pack(side=TOP)
490 def make_icon_window(base, image):
492 # Some older pythons will error out on this
493 icon_image = PhotoImage(data=image)
494 icon_window = Toplevel()
495 Label(icon_window, image=icon_image, bg='black').pack()
496 base.master.iconwindow(icon_window)
497 # Avoid TkInter brain death. PhotoImage objects go out of
498 # scope when the enclosing function returns. Therefore
499 # we have to explicitly link them to something.
500 base.keepalive.append(icon_image)
504 class ListEdit(Frame):
505 # edit a list of values (duplicates not allowed) with a supplied editor hook
506 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
508 self.deletor = deletor
511 # Set up a widget to accept new elements
512 self.newval = StringVar(master)
513 newwin = LabeledEntry(master, newlegend, self.newval, '12')
514 newwin.bind('<Double-1>', self.handleNew)
515 newwin.bind('<Return>', self.handleNew)
516 newwin.pack(side=TOP, fill=X, anchor=E)
518 # Edit the existing list
519 listframe = Frame(master)
520 scroll = Scrollbar(listframe)
521 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
524 self.listwidget.insert(END, x)
525 listframe.pack(side=TOP, expand=YES, fill=BOTH)
526 self.listwidget.config(yscrollcommand=scroll.set)
527 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
528 scroll.config(command=self.listwidget.yview)
529 scroll.pack(side=RIGHT, fill=BOTH)
530 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
531 self.listwidget.bind('<Double-1>', self.handleList);
532 self.listwidget.bind('<Return>', self.handleList);
536 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
537 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
539 self.helptxt = helptxt
540 Button(bf, text='Help', fg='blue',
541 command=self.help).pack(side=RIGHT)
545 helpwin(self.helptxt)
547 def handleList(self, event):
550 def handleNew(self, event):
551 item = self.newval.get()
553 entire = self.listwidget.get(0, self.listwidget.index('end'));
554 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
555 self.listwidget.insert('end', item)
556 if self.list != None: self.list.append(item)
558 apply(self.editor, (item,))
562 select = self.listwidget.curselection()
567 if index and self.editor:
568 label = self.listwidget.get(index);
570 apply(self.editor, (label,))
572 def deleteItem(self):
573 select = self.listwidget.curselection()
577 index = string.atoi(select[0])
578 label = self.listwidget.get(index);
579 self.listwidget.delete(index)
580 if self.list != None:
582 if self.deletor != None:
583 apply(self.deletor, (label,))
585 def ConfirmQuit(frame, context):
588 text = 'Really quit ' + context + ' without saving?',
590 strings = ('Yes', 'No'),
594 def dispose_window(master, legend, help, savelegend='OK'):
595 dispose = Frame(master, relief=RAISED, bd=5)
596 Label(dispose, text=legend).pack(side=TOP,pady=10)
597 Button(dispose, text=savelegend, fg='blue',
598 command=master.save).pack(side=LEFT)
599 Button(dispose, text='Quit', fg='blue',
600 command=master.nosave).pack(side=LEFT)
601 Button(dispose, text='Help', fg='blue',
602 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
607 # Common methods for Tkinter widgets -- deals with Tkinter declaration
608 def post(self, widgetclass, field):
609 for x in widgetclass.typemap:
610 if x[1] == 'Boolean':
611 setattr(self, x[0], BooleanVar(self))
612 elif x[1] == 'String':
613 setattr(self, x[0], StringVar(self))
615 setattr(self, x[0], IntVar(self))
616 source = getattr(getattr(self, field), x[0])
618 getattr(self, x[0]).set(source)
620 def fetch(self, widgetclass, field):
621 for x in widgetclass.typemap:
622 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
625 # First, code to set the global fetchmail run controls.
628 configure_novice_help = {
629 'title' : 'Fetchmail novice configurator help',
630 'banner': 'Novice configurator help',
632 In the `Novice Configurator Controls' panel, you can:
634 Press `Save' to save the new fetchmail configuration you have created.
635 Press `Quit' to exit without saving.
636 Press `Help' to bring up this help message.
638 In the `Novice Configuration' panels, you will set up the basic data
639 needed to create a simple fetchmail setup. These include:
641 1. The name of the remote site you want to query.
643 2. Your login name on that site.
645 3. Your password on that site.
647 4. A protocol to use (POP, IMAP, ETRN, etc.)
649 5. A polling interval.
651 6. Options to fetch old messages as well as new, uor to suppress
652 deletion of fetched message.
654 The novice-configuration code will assume that you want to forward mail
655 to a local sendmail listener with no special options.
658 configure_expert_help = {
659 'title' : 'Fetchmail expert configurator help',
660 'banner': 'Expert configurator help',
662 In the `Expert Configurator Controls' panel, you can:
664 Press `Save' to save the new fetchmail configuration you have edited.
665 Press `Quit' to exit without saving.
666 Press `Help' to bring up this help message.
668 In the `Run Controls' panel, you can set the following options that
669 control how fetchmail runs:
672 Number of seconds to wait between polls in the background.
673 If zero, fetchmail will run in foreground.
676 If empty, emit progress and error messages to stderr.
677 Otherwise this gives the name of the files to write to.
678 This field is ignored if the "Log to syslog?" option is on.
681 If empty, store seen-message IDs in .fetchids under user's home
682 directory. If nonempty, use given file name.
685 Who to send multidrop mail to as a last resort if no address can
686 be matched. Normally empty; in this case, fetchmail treats the
687 invoking user as the address of last resort unless that user is
688 root. If that user is root, fetchmail sends to `postmaster'.
691 If this option is on (the default) error mail goes to the sender.
692 Otherwise it goes to the postmaster.
695 If this option is on, spam bounces are sent to the sender or
696 postmaster (depending on the "Bounces to sender?" option. Otherwise,
697 spam bounces are not sent (the default).
700 If false (the default) fetchmail generates a Received line into
701 each message and generates a HELO from the machine it is running on.
702 If true, fetchmail generates no Received line and HELOs as if it were
705 In the `Remote Mail Configurations' panel, you can:
707 1. Enter the name of a new remote mail server you want fetchmail to query.
709 To do this, simply enter a label for the poll configuration in the
710 `New Server:' box. The label should be a DNS name of the server (unless
711 you are using ssh or some other tunneling method and will fill in the `via'
712 option on the site configuration screen).
714 2. Change the configuration of an existing site.
716 To do this, find the site's label in the listbox and double-click it.
717 This will take you to a site configuration dialogue.
721 class ConfigurationEdit(Frame, MyWidget):
722 def __init__(self, configuration, outfile, master, onexit):
724 self.configuration = configuration
725 self.outfile = outfile
726 self.container = master
728 ConfigurationEdit.mode_to_help = {
729 'novice':configure_novice_help, 'expert':configure_expert_help
732 def server_edit(self, sitename):
733 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
735 def server_delete(self, sitename):
737 for user in self.subwidgets.keys():
739 del self.configuration[sitename]
743 def edit(self, mode):
745 Frame.__init__(self, self.container)
746 self.master.title('fetchmail ' + self.mode + ' configurator');
747 self.master.iconname('fetchmail ' + self.mode + ' configurator');
748 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
749 self.keepalive = [] # Use this to anchor the PhotoImage object
750 make_icon_window(self, fetchmail_icon)
752 self.post(Configuration, 'configuration')
755 'Configurator ' + self.mode + ' Controls',
756 ConfigurationEdit.mode_to_help[self.mode],
759 gf = Frame(self, relief=RAISED, bd = 5)
761 text='Fetchmail Run Controls',
762 bd=2).pack(side=TOP, pady=10)
767 if self.mode != 'novice':
769 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
770 log.pack(side=RIGHT, anchor=E)
772 # Set the poll interval
773 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
774 de.pack(side=RIGHT, anchor=E)
779 if self.mode != 'novice':
782 {'text':'Bounces to sender?',
783 'variable':self.bouncemail,
784 'relief':GROOVE}).pack(side=LEFT, anchor=W)
789 {'text':'send spam bounces?',
790 'variable':self.spambounce,
791 'relief':GROOVE}).pack(side=LEFT, anchor=W)
796 {'text':'Log to syslog?',
797 'variable':self.syslog,
798 'relief':GROOVE}).pack(side=LEFT, anchor=W)
799 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
800 log.pack(side=RIGHT, anchor=E)
804 {'text':'Invisible mode?',
805 'variable':self.invisible,
806 'relief':GROOVE}).pack(side=LEFT, anchor=W)
808 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
809 log.pack(side=RIGHT, anchor=E)
813 # Expert mode allows us to edit multiple sites
814 lf = Frame(self, relief=RAISED, bd=5)
816 text='Remote Mail Server Configurations',
817 bd=2).pack(side=TOP, pady=10)
818 ListEdit('New Server:',
819 map(lambda x: x.pollname, self.configuration.servers),
820 lambda site, self=self: self.server_edit(site),
821 lambda site, self=self: self.server_delete(site),
826 for sitename in self.subwidgets.keys():
827 self.subwidgets[sitename].destruct()
828 self.master.destroy()
832 if ConfirmQuit(self, self.mode + " configuration editor"):
836 for sitename in self.subwidgets.keys():
837 self.subwidgets[sitename].save()
838 self.fetch(Configuration, 'configuration')
842 elif not os.path.isfile(self.outfile) or Dialog(self,
843 title = 'Overwrite existing run control file?',
844 text = 'Really overwrite existing run control file?',
846 strings = ('Yes', 'No'),
847 default = 1).num == 0:
849 os.rename(self.outfile, self.outfile + "~")
850 # Pre-1.5.2 compatibility...
853 fm = open(self.outfile, 'w')
855 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
856 fm.write(`self.configuration`)
860 os.chmod(self.outfile, 0600)
864 # Server editing stuff.
867 'title' : 'Remote site help',
868 'banner': 'Remote sites',
870 When you add a site name to the list here,
871 you initialize an entry telling fetchmail
872 how to poll a new site.
874 When you select a sitename (by double-
875 clicking it, or by single-clicking to
876 select and then clicking the Edit button),
877 you will open a window to configure that
882 'title' : 'Server options help',
883 'banner': 'Server Options',
885 The server options screen controls fetchmail
886 options that apply to one of your mailservers.
888 Once you have a mailserver configuration set
889 up as you like it, you can select `OK' to
890 store it in the server list maintained in
891 the main configuration window.
893 If you wish to discard changes to a server
894 configuration, select `Quit'.
898 'title' : 'Run Control help',
899 'banner': 'Run Controls',
901 If the `Poll normally' checkbox is on, the host is polled as part of
902 the normal operation of fetchmail when it is run with no arguments.
903 If it is off, fetchmail will only query this host when it is given as
904 a command-line argument.
906 The `True name of server' box should specify the actual DNS name
907 to query. By default this is the same as the poll name.
909 Normally each host described in the file is queried once each
910 poll cycle. If `Cycles to skip between polls' is greater than 0,
911 that's the number of poll cycles that are skipped between the
912 times this post is actually polled.
914 The `Server timeout' is the number of seconds fetchmail will wait
915 for a reply from the mailserver before concluding it is hung and
920 'title' : 'Protocol and Port help',
921 'banner': 'Protocol and Port',
923 These options control the remote-mail protocol
924 and TCP/IP service port used to query this
927 If you click the `Probe for supported protocols'
928 button, fetchmail will try to find you the most
929 capable server on the selected host (this will
930 only work if you're conncted to the Internet).
931 The probe only checks for ordinary IMAP and POP
932 protocols; fortunately these are the most
933 frequently supported.
935 The `Protocol' button bar offers you a choice of
936 all the different protocols available. The `auto'
937 protocol is the default mode; it probes the host
938 ports for POP3 and IMAP to see if either is
941 Normally the TCP/IP service port to use is
942 dictated by the protocol choice. The `Port'
943 field (only present in expert mode) lets you
944 set a non-standard port.
948 'title' : 'Security option help',
949 'banner': 'Security',
951 The `interface' option allows you to specify a range
952 of IP addresses to monitor for activity. If these
953 addresses are not active, fetchmail will not poll.
954 Specifying this may protect you from a spoofing attack
955 if your client machine has more than one IP gateway
956 address and some of the gateways are to insecure nets.
958 The `monitor' option, if given, specifies the only
959 device through which fetchmail is permitted to connect
960 to servers. This option may be used to prevent
961 fetchmail from triggering an expensive dial-out if the
962 interface is not already active.
964 The `interface' and `monitor' options are available
965 only for Linux and freeBSD systems. See the fetchmail
966 manual page for details on these.
968 The ssl option enables SSL communication with a mailserver
969 supporting Secure Sockets Layer. The sslkey and sslcert options
970 declare key and certificate files for use with SSL.
971 The sslcertck option enables strict checking of SSL server
972 certificates (and sslcertpath gives trusted certificate
973 directory). With sslfingerprint, you can specify a finger-
974 print the server's key is checked against.
976 The `netsec' option will be configurable only if fetchmail
977 was compiled with IPV6 support. If you need to use it,
978 you probably know what to do.
982 'title' : 'Multidrop option help',
983 'banner': 'Multidrop',
985 These options are only useful with multidrop mode.
986 See the manual page for extended discussion.
990 'title' : 'User list help',
991 'banner': 'User list',
993 When you add a user name to the list here,
994 you initialize an entry telling fetchmail
995 to poll the site on behalf of the new user.
997 When you select a username (by double-
998 clicking it, or by single-clicking to
999 select and then clicking the Edit button),
1000 you will open a window to configure the
1001 user's options on that site.
1004 class ServerEdit(Frame, MyWidget):
1005 def __init__(self, host, parent):
1006 self.parent = parent
1008 self.subwidgets = {}
1009 for site in parent.configuration.servers:
1010 if site.pollname == host:
1012 if (self.server == None):
1013 self.server = Server()
1014 self.server.pollname = host
1015 self.server.via = None
1016 parent.configuration.servers.append(self.server)
1018 def edit(self, mode, master=None):
1019 Frame.__init__(self, master)
1021 self.master.title('Fetchmail host ' + self.server.pollname);
1022 self.master.iconname('Fetchmail host ' + self.server.pollname);
1023 self.post(Server, 'server')
1024 self.makeWidgets(self.server.pollname, mode)
1025 self.keepalive = [] # Use this to anchor the PhotoImage object
1026 make_icon_window(self, fetchmail_icon)
1029 # self.wait_window()
1033 for username in self.subwidgets.keys():
1034 self.subwidgets[username].destruct()
1035 del self.parent.subwidgets[self.server.pollname]
1036 self.master.destroy()
1039 if ConfirmQuit(self, 'server option editing'):
1043 self.fetch(Server, 'server')
1044 for username in self.subwidgets.keys():
1045 self.subwidgets[username].save()
1048 def defaultPort(self):
1049 proto = self.protocol.get()
1050 # Callback to reset the port number whenever the protocol type changes.
1051 # We used to only reset the port if it had a default (zero) value.
1052 # This turns out to be a bad idea especially in Novice mode -- if
1053 # you set POP3 and then set IMAP, the port invisibly remained 110.
1054 # Now we reset unconditionally on the theory that if you're setting
1055 # a custom port number you should be in expert mode and playing
1056 # close enough attention to notice this...
1057 self.port.set(defaultports[proto])
1058 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
1060 def user_edit(self, username, mode):
1061 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
1063 def user_delete(self, username):
1064 if self.subwidgets.has_key(username):
1065 self.subwidgets[username].destruct()
1066 del self.server[username]
1068 def makeWidgets(self, host, mode):
1069 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
1071 leftwin = Frame(self);
1074 if mode != 'novice':
1075 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
1076 Label(ctlwin, text="Run Controls").pack(side=TOP)
1077 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
1078 LabeledEntry(ctlwin, 'True name of ' + host + ':',
1079 self.via, leftwidth).pack(side=TOP, fill=X)
1080 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
1081 self.interval, leftwidth).pack(side=TOP, fill=X)
1082 LabeledEntry(ctlwin, 'Server timeout (seconds):',
1083 self.timeout, leftwidth).pack(side=TOP, fill=X)
1084 Button(ctlwin, text='Help', fg='blue',
1085 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
1088 # Compute the available protocols from the compile-time options
1089 protolist = ['auto']
1090 if 'pop2' in feature_options:
1091 protolist.append("POP2")
1092 if 'pop3' in feature_options:
1093 protolist = protolist + ["POP3", "APOP", "KPOP"]
1094 if 'sdps' in feature_options:
1095 protolist.append("SDPS")
1096 if 'imap' in feature_options:
1097 protolist.append("IMAP")
1098 if 'etrn' in feature_options:
1099 protolist.append("ETRN")
1100 if 'odmr' in feature_options:
1101 protolist.append("ODMR")
1103 protwin = Frame(leftwin, relief=RAISED, bd=5)
1104 Label(protwin, text="Protocol").pack(side=TOP)
1105 ButtonBar(protwin, '',
1106 self.protocol, protolist, 2,
1108 if mode != 'novice':
1109 LabeledEntry(protwin, 'On server TCP/IP port:',
1110 self.port, leftwidth).pack(side=TOP, fill=X)
1112 Checkbutton(protwin,
1113 text="POP3: track `seen' with client-side UIDLs?",
1114 variable=self.uidl).pack(side=TOP)
1115 Button(protwin, text='Probe for supported protocols', fg='blue',
1116 command=self.autoprobe).pack(side=LEFT)
1117 Button(protwin, text='Help', fg='blue',
1118 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1119 protwin.pack(fill=X)
1121 userwin = Frame(leftwin, relief=RAISED, bd=5)
1122 Label(userwin, text="User entries for " + host).pack(side=TOP)
1123 ListEdit("New user: ",
1124 map(lambda x: x.remote, self.server.users),
1125 lambda u, m=mode, s=self: s.user_edit(u, m),
1126 lambda u, s=self: s.user_delete(u),
1128 userwin.pack(fill=X)
1130 leftwin.pack(side=LEFT, anchor=N, fill=X);
1132 if mode != 'novice':
1133 rightwin = Frame(self);
1135 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1136 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1137 LabeledEntry(mdropwin, 'Envelope address header:',
1138 self.envelope, '22').pack(side=TOP, fill=X)
1139 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1140 self.envskip, '22').pack(side=TOP, fill=X)
1141 LabeledEntry(mdropwin, 'Name prefix to strip:',
1142 self.qvirtual, '22').pack(side=TOP, fill=X)
1143 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1144 variable=self.dns).pack(side=TOP)
1145 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1146 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1147 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1148 ListEdit("New domain: ",
1149 self.server.localdomains, None, None, mdropwin, multihelp)
1150 mdropwin.pack(fill=X)
1152 if os_type in ('linux', 'freebsd') or 'netsec' in feature_options:
1153 secwin = Frame(rightwin, relief=RAISED, bd=5)
1154 Label(secwin, text="Security").pack(side=TOP)
1155 # Don't actually let users set this. KPOP sets it implicitly
1156 # ButtonBar(secwin, 'Authorization mode:',
1157 # self.auth, authlist, 1, None).pack(side=TOP)
1158 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1159 LabeledEntry(secwin, 'IP range to check before poll:',
1160 self.interface, leftwidth).pack(side=TOP, fill=X)
1161 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1162 LabeledEntry(secwin, 'Interface to monitor:',
1163 self.monitor, leftwidth).pack(side=TOP, fill=X)
1164 if 'netsec' in feature_options or 'netsec' in dictmembers:
1165 LabeledEntry(secwin, 'IPV6 security options:',
1166 self.netsec, leftwidth).pack(side=TOP, fill=X)
1167 # Someday this should handle Kerberos 5 too
1168 if 'kerberos' in feature_options:
1169 LabeledEntry(secwin, 'Principal:',
1170 self.principal, '12').pack(side=TOP, fill=X)
1171 # ESMTP authentication
1172 LabeledEntry(secwin, 'ESMTP name:',
1173 self.esmtpname, '12').pack(side=TOP, fill=X)
1174 LabeledEntry(secwin, 'ESMTP password:',
1175 self.esmtppassword, '12').pack(side=TOP, fill=X)
1176 Button(secwin, text='Help', fg='blue',
1177 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1180 rightwin.pack(side=LEFT, anchor=N);
1182 def autoprobe(self):
1183 # Note: this only handles case (1) near fetchmail.c:1032
1184 # We're assuming people smart enough to set up ssh tunneling
1185 # won't need autoprobing.
1187 realhost = self.server.via
1189 realhost = self.server.pollname
1191 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1192 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1194 sock.connect((realhost, port))
1195 greetline = sock.recv(1024)
1201 confwin = Toplevel()
1202 if greetline == None:
1203 title = "Autoprobe of " + realhost + " failed"
1205 Fetchmailconf didn't find any mailservers active.
1206 This could mean the host doesn't support any,
1207 or that your Internet connection is down, or
1208 that the host is so slow that the probe timed
1209 out before getting a response.
1213 # OK, now try to recognize potential problems
1215 if protocol == "POP2":
1216 warnings = warnings + """
1217 It appears you have somehow found a mailserver running only POP2.
1218 Congratulations. Have you considered a career in archaeology?
1220 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1221 Unless the first line of your fetchmail -V output includes the string "POP2",
1222 you'll have to build it from sources yourself with the configure
1223 switch --enable-POP2.
1227 ### POP3 servers start here
1229 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1230 warnings = warnings + """
1231 This appears to be an old version of the UC Davis POP server. These are
1232 dangerously unreliable (among other problems, they may drop your mailbox
1233 on the floor if your connection is interrupted during the session).
1235 It is strongly recommended that you find a better POP3 server. The fetchmail
1236 FAQ includes pointers to good ones.
1239 # Steve VanDevender <stevev@efn.org> writes:
1240 # The only system I have seen this happen with is cucipop-1.31
1241 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1242 # 2.x and probably quite a few other systems. It appears to be a
1243 # bug or bad interaction with the SunOS realloc() -- it turns out
1244 # that internally cucipop does allocate a certain data structure in
1245 # multiples of 16, using realloc() to bump it up to the next
1246 # multiple if it needs more.
1248 # The distinctive symptom is that when there are 16 messages in the
1249 # inbox, you can RETR and DELE all 16 messages successfully, but on
1250 # QUIT cucipop returns something like "-ERR Error locking your
1251 # mailbox" and aborts without updating it.
1253 # The cucipop banner looks like:
1255 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1257 if string.find(greetline, "Cubic Circle") > 0:
1258 warnings = warnings + """
1259 I see your server is running cucipop. Better make sure the server box
1260 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1261 under that version, and doesn't cope with the result gracefully. Newer
1262 SunOS and Solaris machines run cucipop OK.
1264 Also, some versions of cucipop don't assert an exclusive lock on your
1265 mailbox when it's being queried. This means that if you have more than
1266 one fetchmail query running against the same mailbox, bad things can happen.
1268 if string.find(greetline, "David POP3 Server") > 0:
1269 warnings = warnings + """
1270 This POP3 server is badly broken. You should get rid of it -- and the
1271 brain-dead Microsoft operating system it rode in on.
1274 # The greeting line on the server known to be buggy is:
1275 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1277 if string.find(greetline, "FTGate") > 0:
1278 warnings = warnings + """
1279 This POP server has a weird bug; it says OK twice in response to TOP.
1280 Its response to RETR is normal, so use the `fetchall' option.
1283 if string.find(greetline, " geonet.de") > 0:
1284 warnings = warnings + """
1285 You appear to be using geonet. As of late 2002, the TOP command on
1286 geonet's POP3 is broken. Use the fetchall option.
1289 if string.find(greetline, "GMX POP3 StreamProxy") > 0:
1290 warnings = warnings + """
1291 The GMX POP3 server behaves badly on authentication failures, sending
1292 back a non-conformant error message (missing an <code>-ERR</code> tag)
1293 that confuses fetchmail. Use IMAP.
1296 if string.find(greetline, "OpenMail") > 0:
1297 warnings = warnings + """
1298 You appear to be using some version of HP OpenMail. Many versions of
1299 OpenMail do not process the "TOP" command correctly; the symptom is that
1300 only the header and first line of each message is retrieved. To work
1301 around this bug, turn on `fetchall' on all user entries associated with
1305 if string.find(greetline, "Escape character is") > 0:
1306 warnings = warnings + """
1307 Your greeting line looks like it was written by a fetid pile of
1308 camel dung identified to me as `popa3d written by Solar Designer'.
1309 Beware! The UIDL support in this thing is known to be completely broken,
1310 and other things probably are too.
1313 if string.find(greetline, "MercuryP/NLM v1.48") > 0:
1314 warnings = warnings + """
1315 This is not a POP3 server. It has delusions of being one, but after
1316 RETR all messages are automatically marked to be deleted. The only
1317 way to prevent this is to issue an RSET before leaving the server.
1318 Fetchmail does this, but we suspect this is probably broken in lots
1322 if string.find(greetline, "POP-Max") > 0:
1323 warnings = warnings + """
1324 The Mail Max POP3 server screws up on mail with attachments. It
1325 reports the message size with attachments included, but doesn't
1326 download them on a RETR or TOP (this violates the IMAP RFCs). It also
1327 doesn't implement TOP correctly. You should get rid of it -- and the
1328 brain-dead NT server it rode in on.
1331 if string.find(greetline, "POP3 Server Ready") > 0:
1332 warnings = warnings + """
1333 Some server that uses this greeting line has been observed to choke on
1334 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1337 if string.find(greetline, "QPOP") > 0:
1338 warnings = warnings + """
1339 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1340 knows all about qpopper. However, be aware that the 2.53 version of
1341 qpopper does something odd that causes fetchmail to hang with a socket
1342 error on very large messages. This is probably not a fetchmail bug, as
1343 it has been observed with fetchpop. The fix is to upgrade to qpopper
1344 3.0beta or a more recent version. Better yet, switch to IMAP.
1347 if string.find(greetline, " sprynet.com") > 0:
1348 warnings = warnings + """
1349 You appear to be using a SpryNet server. In mid-1999 it was reported that
1350 the SpryNet TOP command marks messages seen. Therefore, for proper error
1351 recovery in the event of a line drop, it is strongly recommended that you
1352 turn on `fetchall' on all user entries associated with this server.
1355 if string.find(greetline, "TEMS POP3") > 0:
1356 warnings = warnings + """
1357 Your POP3 server has "TEMS" in its header line. At least one such
1358 server does not process the "TOP" command correctly; the symptom is
1359 that fetchmail hangs when trying to retrieve mail. To work around
1360 this bug, turn on `fetchall' on all user entries associated with this
1364 if string.find(greetline, " spray.se") > 0:
1365 warnings = warnings + """
1366 Your POP3 server has "spray.se" in its header line. In May 2000 at
1367 least one such server did not process the "TOP" command correctly; the
1368 symptom is that messages are treated as headerless. To work around
1369 this bug, turn on `fetchall' on all user entries associated with this
1373 if string.find(greetline, " usa.net") > 0:
1374 warnings = warnings + """
1375 You appear to be using USA.NET's free mail service. Their POP3 servers
1376 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1377 fetchmail can compensate. They seem to require that fetchall be switched on
1378 (otherwise you won't necessarily see all your mail, not even new mail).
1379 They also botch the TOP command the fetchmail normally uses for retrieval
1380 (it only retrieves about 10 lines rather than the number specified).
1381 Turning on fetchall will disable the use of TOP.
1383 Therefore, it is strongly recommended that you turn on `fetchall' on all
1384 user entries associated with this server.
1387 if string.find(greetline, " Novonyx POP3") > 0:
1388 warnings = warnings + """
1389 Your mailserver is running Novonyx POP3. This server, at least as of
1390 version 2.17, seems to have problems handling and reporting seen bits.
1391 You may have to use the fetchall option.
1394 if string.find(greetline, " IMS POP3") > 0:
1395 warnings = warnings + """
1396 Some servers issuing the greeting line 'IMS POP3' have been known to
1397 do byte-stuffing incorrectly. This means that if a message you receive
1398 has a . (period) at start of line, fetchmail will become confused and
1399 probably wedge itself. (This bug was recorded on IMS POP3 0.86.)
1403 ### IMAP servers start here
1405 if string.find(greetline, "GroupWise") > 0:
1406 warnings = warnings + """
1407 The Novell GroupWise IMAP server would be better named GroupFoolish;
1408 it is (according to the designer of IMAP) unusably broken. Among
1409 other things, it doesn't include a required content length in its
1410 BODY[TEXT] response.<p>
1412 Fetchmail works around this problem, but we strongly recommend voting
1413 with your dollars for a server that isn't brain-dead. If you stick
1414 with code as shoddy as GroupWise seems to be, you will probably pay
1415 for it with other problems.<p>
1418 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1419 warnings = warnings + """
1420 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1421 set the messages's Seen flag. As a result, if you use the keep option the
1422 same messages will be downloaded over and over.
1425 if string.find(greetline, "InterChange") > 0:
1426 warnings = warnings + """
1428 The InterChange IMAP server at release levels below 3.61.08 screws up
1429 on mail with attachments. It doesn't fetch them if you give it a
1430 BODY[TEXT] request, though it does if you request RFC822.TEXT.
1431 According to the IMAP RFCs and their maintainer these should be
1432 equivalent -- and we can't drop the BODY[TEXT] form because M$
1433 Exchange (quite legally under RFC2062) rejectsit. The InterChange
1434 folks claim to have fixed this bug in 3.61.08.
1437 if string.find(greetline, "Imail") > 0:
1438 warnings = warnings + """
1439 We've seen a bug report indicating that this IMAP server (at least as of
1440 version 5.0.7) returns an invalid body size for messages with MIME
1441 attachments; the effect is to drop the attachments on the floor. We
1442 recommend you upgrade to a non-broken IMAP server.
1445 if string.find(greetline, "Domino IMAP4") > 0:
1446 warnings = warnings + """
1447 Your IMAP server appears to be Lotus Domino. This server, at least up
1448 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1449 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1450 will see attachments as part of the message text. If your Domino server's
1451 POP3 facility is enabled, we recommend you fall back on it.
1455 ### Checks for protocol variants start here
1457 closebrak = string.find(greetline, ">")
1458 if closebrak > 0 and greetline[closebrak+1] == "\r":
1459 warnings = warnings + """
1460 It looks like you could use APOP on this server and avoid sending it your
1461 password in clear. You should talk to the mailserver administrator about
1465 if string.find(greetline, "IMAP2bis") > 0:
1466 warnings = warnings + """
1467 IMAP2bis servers have a minor problem; they can't peek at messages without
1468 marking them seen. If you take a line hit during the retrieval, the
1469 interrupted message may get left on the server, marked seen.
1471 To work around this, it is recommended that you set the `fetchall'
1472 option on all user entries associated with this server, so any stuck
1473 mail will be retrieved next time around.
1475 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1476 a pointer to an open-source implementation.
1479 if string.find(greetline, "IMAP4rev1") > 0:
1480 warnings = warnings + """
1481 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1482 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1483 has therefore been extremely well tested with this class of server.
1487 warnings = warnings + """
1488 Fetchmail doesn't know anything special about this server type.
1492 # Display success window with warnings
1493 title = "Autoprobe of " + realhost + " succeeded"
1494 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1495 self.protocol.set(protocol)
1496 confwin.title(title)
1497 confwin.iconname(title)
1498 Label(confwin, text=title).pack()
1499 Message(confwin, text=confirm, width=600).pack()
1500 Button(confwin, text='Done',
1501 command=lambda x=confwin: x.destroy(), bd=2).pack()
1504 # User editing stuff
1508 'title' : 'User option help',
1509 'banner': 'User options',
1511 You may use this panel to set options
1512 that may differ between individual
1515 Once you have a user configuration set
1516 up as you like it, you can select `OK' to
1517 store it in the user list maintained in
1518 the site configuration window.
1520 If you wish to discard the changes you have
1521 made to user options, select `Quit'.
1525 'title' : 'Local name help',
1526 'banner': 'Local names',
1528 The local name(s) in a user entry are the
1529 people on the client machine who should
1530 receive mail from the poll described.
1532 Note: if a user entry has more than one
1533 local name, messages will be retrieved
1534 in multidrop mode. This complicates
1535 the configuration issues; see the manual
1536 page section on multidrop mode.
1538 Warning: Be careful with local names
1539 such as foo@bar.com, as that can cause
1540 the mail to be sent to foo@bar.com instead
1541 of sending it to your local system.
1544 class UserEdit(Frame, MyWidget):
1545 def __init__(self, username, parent):
1546 self.parent = parent
1548 for user in parent.server.users:
1549 if user.remote == username:
1551 if self.user == None:
1553 self.user.remote = username
1554 self.user.localnames = [username]
1555 parent.server.users.append(self.user)
1557 def edit(self, mode, master=None):
1558 Frame.__init__(self, master)
1560 self.master.title('Fetchmail user ' + self.user.remote
1561 + ' querying ' + self.parent.server.pollname);
1562 self.master.iconname('Fetchmail user ' + self.user.remote);
1563 self.post(User, 'user')
1564 self.makeWidgets(mode, self.parent.server.pollname)
1565 self.keepalive = [] # Use this to anchor the PhotoImage object
1566 make_icon_window(self, fetchmail_icon)
1569 # self.wait_window()
1573 # Yes, this test can fail -- if you delete the parent window.
1574 if self.parent.subwidgets.has_key(self.user.remote):
1575 del self.parent.subwidgets[self.user.remote]
1576 self.master.destroy()
1579 if ConfirmQuit(self, 'user option editing'):
1584 for x in self.user.localnames: ok = ok + (string.find(x, '@') != -1)
1585 if ok == 0 or Dialog(self,
1586 title = "Really accept an embedded '@' ?",
1587 text = "Local names with an embedded '@', such as in foo@bar "
1588 "might result in your mail being sent to foo@bar.com "
1589 "instead of your local system.\n Are you sure you want "
1590 "a local user name with an '@' in it?",
1591 bitmap = 'question',
1592 strings = ('Yes', 'No'),
1593 default = 1).num == 0:
1594 self.fetch(User, 'user')
1597 def makeWidgets(self, mode, servername):
1598 dispose_window(self,
1599 "User options for " + self.user.remote + " querying " + servername,
1602 if mode != 'novice':
1603 leftwin = Frame(self);
1607 secwin = Frame(leftwin, relief=RAISED, bd=5)
1608 Label(secwin, text="Authentication").pack(side=TOP)
1609 LabeledEntry(secwin, 'Password:',
1610 self.password, '12').pack(side=TOP, fill=X)
1611 secwin.pack(fill=X, anchor=N)
1613 if 'ssl' in feature_options or 'ssl' in dictmembers:
1614 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1615 Checkbutton(sslwin, text="Use SSL?",
1616 variable=self.ssl).pack(side=TOP, fill=X)
1617 LabeledEntry(sslwin, 'SSL key:',
1618 self.sslkey, '14').pack(side=TOP, fill=X)
1619 LabeledEntry(sslwin, 'SSL certificate:',
1620 self.sslcert, '14').pack(side=TOP, fill=X)
1621 Checkbutton(sslwin, text="Check server SSL certificate?",
1622 variable=self.sslcertck).pack(side=TOP, fill=X)
1623 LabeledEntry(sslwin, 'SSL trusted certificate directory:',
1624 self.sslcertpath, '14').pack(side=TOP, fill=X)
1625 LabeledEntry(sslwin, 'SSL key fingerprint:',
1626 self.sslfingerprint, '14').pack(side=TOP, fill=X)
1627 sslwin.pack(fill=X, anchor=N)
1629 names = Frame(leftwin, relief=RAISED, bd=5)
1630 Label(names, text="Local names").pack(side=TOP)
1631 ListEdit("New name: ",
1632 self.user.localnames, None, None, names, localhelp)
1633 names.pack(fill=X, anchor=N)
1635 if mode != 'novice':
1636 targwin = Frame(leftwin, relief=RAISED, bd=5)
1637 Label(targwin, text="Forwarding Options").pack(side=TOP)
1638 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1639 ListEdit("New listener:",
1640 self.user.smtphunt, None, None, targwin, None)
1641 Label(targwin, text="Domains to fetch from (ODMR/ETRN only)").pack(side=TOP)
1642 ListEdit("Domains:",
1643 self.user.fetchdomains, None, None, targwin, None)
1644 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1645 self.smtpaddress, '26').pack(side=TOP, fill=X)
1646 LabeledEntry(targwin, 'Set RCPT To address:',
1647 self.smtpname, '26').pack(side=TOP, fill=X)
1648 LabeledEntry(targwin, 'Connection setup command:',
1649 self.preconnect, '26').pack(side=TOP, fill=X)
1650 LabeledEntry(targwin, 'Connection wrapup command:',
1651 self.postconnect, '26').pack(side=TOP, fill=X)
1652 LabeledEntry(targwin, 'Local delivery agent:',
1653 self.mda, '26').pack(side=TOP, fill=X)
1654 LabeledEntry(targwin, 'BSMTP output file:',
1655 self.bsmtp, '26').pack(side=TOP, fill=X)
1656 LabeledEntry(targwin, 'Listener spam-block codes:',
1657 self.antispam, '26').pack(side=TOP, fill=X)
1658 LabeledEntry(targwin, 'Pass-through properties:',
1659 self.properties, '26').pack(side=TOP, fill=X)
1660 Checkbutton(targwin, text="Use LMTP?",
1661 variable=self.lmtp).pack(side=TOP, fill=X)
1662 targwin.pack(fill=X, anchor=N)
1664 if mode != 'novice':
1665 leftwin.pack(side=LEFT, fill=X, anchor=N)
1666 rightwin = Frame(self)
1670 optwin = Frame(rightwin, relief=RAISED, bd=5)
1671 Label(optwin, text="Processing Options").pack(side=TOP)
1672 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1673 variable=self.keep).pack(side=TOP, anchor=W)
1674 Checkbutton(optwin, text="Fetch old messages as well as new",
1675 variable=self.fetchall).pack(side=TOP, anchor=W)
1676 if mode != 'novice':
1677 Checkbutton(optwin, text="Flush seen messages before retrieval",
1678 variable=self.flush).pack(side=TOP, anchor=W)
1679 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1680 variable=self.rewrite).pack(side=TOP, anchor=W)
1681 Checkbutton(optwin, text="Force CR/LF at end of each line",
1682 variable=self.forcecr).pack(side=TOP, anchor=W)
1683 Checkbutton(optwin, text="Strip CR from end of each line",
1684 variable=self.stripcr).pack(side=TOP, anchor=W)
1685 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1686 variable=self.pass8bits).pack(side=TOP, anchor=W)
1687 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1688 variable=self.mimedecode).pack(side=TOP, anchor=W)
1689 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1690 variable=self.dropstatus).pack(side=TOP, anchor=W)
1691 Checkbutton(optwin, text="Drop Delivered-To lines from forwarded messages",
1692 variable=self.dropdelivered).pack(side=TOP, anchor=W)
1695 if mode != 'novice':
1696 limwin = Frame(rightwin, relief=RAISED, bd=5)
1697 Label(limwin, text="Resource Limits").pack(side=TOP)
1698 LabeledEntry(limwin, 'Message size limit:',
1699 self.limit, '30').pack(side=TOP, fill=X)
1700 LabeledEntry(limwin, 'Size warning interval:',
1701 self.warnings, '30').pack(side=TOP, fill=X)
1702 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1703 self.fetchlimit, '30').pack(side=TOP, fill=X)
1704 LabeledEntry(limwin, 'Max messages to forward per poll:',
1705 self.batchlimit, '30').pack(side=TOP, fill=X)
1706 if self.parent.server.protocol not in ('ETRN', 'ODMR'):
1707 LabeledEntry(limwin, 'Interval between expunges:',
1708 self.expunge, '30').pack(side=TOP, fill=X)
1709 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1710 variable=self.idle).pack(side=TOP, anchor=W)
1713 if self.parent.server.protocol == 'IMAP':
1714 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1715 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1716 ListEdit("New folder:", self.user.mailboxes,
1717 None, None, foldwin, None)
1718 foldwin.pack(fill=X, anchor=N)
1720 if mode != 'novice':
1721 rightwin.pack(side=LEFT)
1727 # Top-level window that offers either novice or expert mode
1728 # (but not both at once; it disappears when one is selected).
1731 class Configurator(Frame):
1732 def __init__(self, outfile, master, onexit, parent):
1733 Frame.__init__(self, master)
1734 self.outfile = outfile
1735 self.onexit = onexit
1736 self.parent = parent
1737 self.master.title('fetchmail configurator');
1738 self.master.iconname('fetchmail configurator');
1740 self.keepalive = [] # Use this to anchor the PhotoImage object
1741 make_icon_window(self, fetchmail_icon)
1743 Message(self, text="""
1744 Use `Novice Configuration' for basic fetchmail setup;
1745 with this, you can easily set up a single-drop connection
1746 to one remote mail server.
1747 """, width=600).pack(side=TOP)
1748 Button(self, text='Novice Configuration',
1749 fg='blue', command=self.novice).pack()
1751 Message(self, text="""
1752 Use `Expert Configuration' for advanced fetchmail setup,
1753 including multiple-site or multidrop connections.
1754 """, width=600).pack(side=TOP)
1755 Button(self, text='Expert Configuration',
1756 fg='blue', command=self.expert).pack()
1758 Message(self, text="""
1759 Or you can just select `Quit' to leave the configurator now and
1760 return to the main panel.
1761 """, width=600).pack(side=TOP)
1762 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1763 master.protocol("WM_DELETE_WINDOW", self.leave)
1766 self.master.destroy()
1767 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1770 self.master.destroy()
1771 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1774 self.master.destroy()
1777 # Run a command in a scrolling text widget, displaying its output
1779 class RunWindow(Frame):
1780 def __init__(self, command, master, parent):
1781 Frame.__init__(self, master)
1782 self.master = master
1783 self.master.title('fetchmail run window');
1784 self.master.iconname('fetchmail run window');
1787 text="Running "+command,
1788 bd=2).pack(side=TOP, pady=10)
1789 self.keepalive = [] # Use this to anchor the PhotoImage object
1790 make_icon_window(self, fetchmail_icon)
1792 # This is a scrolling text window
1793 textframe = Frame(self)
1794 scroll = Scrollbar(textframe)
1795 self.textwidget = Text(textframe, setgrid=TRUE)
1796 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1797 self.textwidget.config(yscrollcommand=scroll.set)
1798 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1799 scroll.config(command=self.textwidget.yview)
1800 scroll.pack(side=RIGHT, fill=BOTH)
1801 textframe.pack(side=TOP)
1803 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1805 self.update() # Draw widget before executing fetchmail
1807 # Always look for a runnable command in the directory we're running in
1808 # first. This avoids some obscure version-skew errors that can occur
1809 # if you pick up an old fetchmail from the standard system locations.
1810 os.environ["PATH"] = os.path.dirname(sys.argv[0]) + ":" + os.environ["PATH"]
1811 child_stdout = os.popen(command + " 2>&1", "r")
1813 ch = child_stdout.read(1)
1816 self.textwidget.insert(END, ch)
1817 self.textwidget.insert(END, "Done.")
1818 self.textwidget.see(END);
1821 self.master.destroy()
1823 # Here's where we choose either configuration or launching
1825 class MainWindow(Frame):
1826 def __init__(self, outfile, master=None):
1827 Frame.__init__(self, master)
1828 self.outfile = outfile
1829 self.master.title('fetchmail launcher');
1830 self.master.iconname('fetchmail launcher');
1833 text='Fetchmailconf ' + version,
1834 bd=2).pack(side=TOP, pady=10)
1835 self.keepalive = [] # Use this to anchor the PhotoImage object
1836 make_icon_window(self, fetchmail_icon)
1839 ## Test icon display with the following:
1840 # icon_image = PhotoImage(data=fetchmail_icon)
1841 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1842 # self.keepalive.append(icon_image)
1844 Message(self, text="""
1845 Use `Configure fetchmail' to tell fetchmail about the remote
1846 servers it should poll (the host name, your username there,
1847 whether to use POP or IMAP, and so forth).
1848 """, width=600).pack(side=TOP)
1849 self.configbutton = Button(self, text='Configure fetchmail',
1850 fg='blue', command=self.configure)
1851 self.configbutton.pack()
1853 Message(self, text="""
1854 Use `Test fetchmail' to run fetchmail with debugging enabled.
1855 This is a good way to test out a new configuration.
1856 """, width=600).pack(side=TOP)
1857 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1859 Message(self, text="""
1860 Use `Run fetchmail' to run fetchmail in foreground.
1861 Progress messages will be shown, but not debug messages.
1862 """, width=600).pack(side=TOP)
1863 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1865 Message(self, text="""
1866 Or you can just select `Quit' to exit the launcher now.
1867 """, width=600).pack(side=TOP)
1868 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1870 def configure(self):
1871 self.configbutton.configure(state=DISABLED)
1872 Configurator(self.outfile, Toplevel(),
1873 lambda self=self: self.configbutton.configure(state=NORMAL),
1876 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1879 RunWindow("fetchmail -d0", Toplevel(), self)
1884 # Functions for turning a dictionary into an instantiated object tree.
1886 def intersect(list1, list2):
1887 # Compute set intersection of lists
1894 def setdiff(list1, list2):
1895 # Compute set difference of lists
1902 def copy_instance(toclass, fromdict):
1903 # Initialize a class object of given type from a conformant dictionary.
1904 for fld in fromdict.keys():
1905 if not fld in dictmembers:
1906 dictmembers.append(fld)
1907 # The `optional' fields are the ones we can ignore for purposes of
1908 # conformability checking; they'll still get copied if they are
1909 # present in the dictionary.
1910 optional = ('interface', 'monitor',
1911 'netsec', 'esmtpname', 'esmtppassword',
1912 'ssl', 'sslkey', 'sslcert', 'sslproto', 'sslcertck',
1913 'sslcertpath', 'sslfingerprint', 'showdots')
1914 class_sig = setdiff(toclass.__dict__.keys(), optional)
1916 dict_keys = setdiff(fromdict.keys(), optional)
1918 common = intersect(class_sig, dict_keys)
1919 if 'typemap' in class_sig:
1920 class_sig.remove('typemap')
1921 if tuple(class_sig) != tuple(dict_keys):
1922 print "Fields don't match what fetchmailconf expected:"
1923 # print "Class signature: " + `class_sig`
1924 # print "Dictionary keys: " + `dict_keys`
1925 diff = setdiff(class_sig, common)
1927 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1928 diff = setdiff(dict_keys, common)
1930 print "Not matched in dictionary keys: " + `diff`
1933 for x in fromdict.keys():
1934 setattr(toclass, x, fromdict[x])
1937 # And this is the main sequence. How it works:
1939 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1940 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1941 # Run execfile on the file to pull fetchmailrc into Python global space.
1942 # You don't want static data, though; you want, instead, a tree of objects
1943 # with the same data members and added appropriate methods.
1945 # This is what the copy_instance function() is for. It tries to copy a
1946 # dictionary field by field into a class, aborting if the class and dictionary
1947 # have different data members (except for any typemap member in the class;
1948 # that one is strictly for use by the MyWidget supperclass).
1950 # Once the object tree is set up, require user to choose novice or expert
1951 # mode and instantiate an edit object for the configuration. Class methods
1952 # will take it all from there.
1954 # Options (not documented because they're for fetchmailconf debuggers only):
1955 # -d: Read the configuration and dump it to stdout before editing. Dump
1956 # the edited result to stdout as well.
1957 # -f: specify the run control file to read.
1959 if __name__ == '__main__':
1961 if not os.environ.has_key("DISPLAY"):
1962 print "fetchmailconf must be run under X"
1965 fetchmail_icon = """
1966 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1967 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1968 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1969 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1970 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1971 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1972 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1973 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1974 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1975 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1976 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1977 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1978 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1979 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1980 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1981 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1982 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1983 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1984 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1985 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1986 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1987 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1988 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1989 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1990 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1991 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1992 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1993 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1994 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1995 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1996 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1997 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1999 # The base64 data in the string above was generated by the following procedure:
2002 # print base64.encodestring(open("fetchmail.gif", "rb").read())
2006 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
2007 dump = rcfile = None;
2008 for (switch, val) in options:
2009 if (switch == '-d'):
2011 elif (switch == '-f'):
2014 # Get client host's FQDN
2015 hostname = socket.gethostbyaddr(socket.gethostname())[0]
2018 ConfigurationDefaults = Configuration()
2019 ServerDefaults = Server()
2020 UserDefaults = User()
2022 # Read the existing configuration. We set the umask to 077 to make sure
2023 # that group & other read/write permissions are shut off -- we wouldn't
2024 # want crackers to snoop password information out of the tempfile.
2025 tmpfile = tempfile.mktemp()
2027 cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
2029 cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
2034 print "`" + cmd + "' run failure, status " + `s`
2037 print "Unknown error while running fetchmail --configdump"
2044 print "Can't read configuration output of fetchmail --configdump."
2050 # The tricky part -- initializing objects from the configuration global
2051 # `Configuration' is the top level of the object tree we're going to mung.
2052 # The dictmembers list is used to track the set of fields the dictionary
2053 # contains; in particular, we can use it to tell whether things like the
2054 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
2056 Fetchmailrc = Configuration()
2057 copy_instance(Fetchmailrc, fetchmailrc)
2058 Fetchmailrc.servers = [];
2059 for server in fetchmailrc['servers']:
2061 copy_instance(Newsite, server)
2062 Fetchmailrc.servers.append(Newsite)
2064 for user in server['users']:
2066 copy_instance(Newuser, user)
2067 Newsite.users.append(Newuser)
2069 # We may want to display the configuration and quit
2071 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
2073 # The theory here is that -f alone sets the rcfile location,
2074 # but -d and -f together mean the new configuration should go to stdout.
2075 if not rcfile and not dump:
2076 rcfile = os.environ["HOME"] + "/.fetchmailrc"
2078 # OK, now run the configuration edit
2079 root = MainWindow(rcfile)
2082 # The following sets edit modes for GNU EMACS