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.tracepolls = FALSE # Add trace-poll info to headers
98 self.users = [] # List of user entries for site
100 ('pollname', 'String'),
102 ('active', 'Boolean'),
104 ('protocol', 'String'),
109 ('envelope', 'String'),
111 ('qvirtual', 'String'),
114 # leave localdomains out
115 ('interface', 'String'),
116 ('monitor', 'String'),
117 ('plugin', 'String'),
118 ('plugout', 'String'),
119 ('netsec', 'String'),
120 ('principal', 'String'),
121 ('tracepolls','Boolean'))
123 def dump(self, folded):
125 if self.active: res = res + "poll"
126 else: res = res + "skip"
127 res = res + (" " + self.pollname)
129 res = res + (" via " + str(self.via) + "\n");
130 if self.protocol != ServerDefaults.protocol:
131 res = res + " with proto " + self.protocol
132 if self.port != defaultports[self.protocol] and self.port != 0:
133 res = res + " port " + `self.port`
134 if self.timeout != ServerDefaults.timeout:
135 res = res + " timeout " + `self.timeout`
136 if self.interval != ServerDefaults.interval:
137 res = res + " interval " + `self.interval`
138 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
140 res = res + " envelope " + `self.envskip` + " " + self.envelope
142 res = res + " envelope " + self.envelope
144 res = res + (" qvirtual " + str(self.qvirtual) + "\n");
145 if self.auth != ServerDefaults.auth:
146 res = res + " auth " + self.auth
147 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
148 res = res + " and options"
149 if self.dns != ServerDefaults.dns:
150 res = res + flag2str(self.dns, 'dns')
151 if self.uidl != ServerDefaults.uidl:
152 res = res + flag2str(self.uidl, 'uidl')
153 if folded: res = res + "\n "
154 else: res = res + " "
160 if self.aka and self.localdomains: res = res + " "
161 if self.localdomains:
162 res = res + ("localdomains")
163 for x in self.localdomains:
165 if (self.aka or self.localdomains):
172 res = res + "tracepolls\n"
175 res = res + " interface " + str(self.interface)
177 res = res + " monitor " + str(self.monitor)
179 res = res + " plugin " + `self.plugin`
181 res = res + " plugout " + `self.plugout`
183 res = res + " netsec " + str(self.netsec)
185 res = res + " principal " + `self.principal`
186 if self.interface or self.monitor or self.netsec or self.principal or self.plugin or self.plugout:
190 if res[-1] == " ": res = res[0:-1]
192 for user in self.users:
193 res = res + repr(user)
197 def __delitem__(self, name):
198 for ui in range(len(self.users)):
199 if self.users[ui].remote == name:
204 return self.dump(TRUE)
207 return "[Server: " + self.dump(FALSE) + "]"
211 if os.environ.has_key("USER"):
212 self.remote = os.environ["USER"] # Remote username
213 elif os.environ.has_key("LOGNAME"):
214 self.remote = os.environ["LOGNAME"]
216 print "Can't get your username!"
218 self.localnames = [self.remote,]# Local names
219 self.password = None # Password for mail account access
220 self.mailboxes = [] # Remote folders to retrieve from
221 self.smtphunt = [] # Hosts to forward to
222 self.fetchdomains = [] # Domains to fetch from
223 self.smtpaddress = None # Append this to MAIL FROM line
224 self.smtpname = None # Use this for RCPT TO
225 self.preconnect = None # Connection setup
226 self.postconnect = None # Connection wrapup
227 self.mda = None # Mail Delivery Agent
228 self.bsmtp = None # BSMTP output file
229 self.lmtp = FALSE # Use LMTP rather than SMTP?
230 self.antispam = "571 550 501 554" # Listener's spam-block code
231 self.keep = FALSE # Keep messages
232 self.flush = FALSE # Flush messages
233 self.fetchall = FALSE # Fetch old messages
234 self.rewrite = TRUE # Rewrite message headers
235 self.forcecr = FALSE # Force LF -> CR/LF
236 self.stripcr = FALSE # Strip CR
237 self.pass8bits = FALSE # Force BODY=7BIT
238 self.mimedecode = FALSE # Undo MIME armoring
239 self.dropstatus = FALSE # Drop incoming Status lines
240 self.dropdelivered = FALSE # Drop incoming Delivered-To lines
241 self.idle = FALSE # IDLE after poll
242 self.limit = 0 # Message size limit
243 self.warnings = 3600 # Size warning interval (see tunable.h)
244 self.fetchlimit = 0 # Max messages fetched per batch
245 self.batchlimit = 0 # Max message forwarded per batch
246 self.expunge = 0 # Interval between expunges (IMAP)
247 self.ssl = 0 # Enable Seccure Socket Layer
248 self.sslkey = None # SSL key filename
249 self.sslcert = None # SSL certificate filename
250 self.sslproto = None # Force SSL?
251 self.sslcertck = 0 # Enable strict SSL cert checking
252 self.sslcertpath = None # Path to trusted certificates
253 self.sslfingerprint = None # SSL key fingerprint to check
254 self.properties = None # Extension properties
256 ('remote', 'String'),
257 # leave out mailboxes and localnames
258 ('password', 'String'),
259 # Leave out smtphunt, fetchdomains
260 ('smtpaddress', 'String'),
261 ('smtpname', 'String'),
262 ('preconnect', 'String'),
263 ('postconnect', 'String'),
267 ('antispam', 'String'),
269 ('flush', 'Boolean'),
270 ('fetchall', 'Boolean'),
271 ('rewrite', 'Boolean'),
272 ('forcecr', 'Boolean'),
273 ('stripcr', 'Boolean'),
274 ('pass8bits', 'Boolean'),
275 ('mimedecode', 'Boolean'),
276 ('dropstatus', 'Boolean'),
277 ('dropdelivered', 'Boolean'),
281 ('fetchlimit', 'Int'),
282 ('batchlimit', 'Int'),
285 ('sslkey', 'String'),
286 ('sslcert', 'String'),
287 ('sslcertck', 'Boolean'),
288 ('sslcertpath', 'String'),
289 ('sslfingerprint', 'String'),
290 ('properties', 'String'))
294 res = res + "user " + `self.remote` + " there ";
296 res = res + "with password " + `self.password` + " "
299 for x in self.localnames:
300 res = res + " " + `x`
302 if (self.keep != UserDefaults.keep
303 or self.flush != UserDefaults.flush
304 or self.fetchall != UserDefaults.fetchall
305 or self.rewrite != UserDefaults.rewrite
306 or self.forcecr != UserDefaults.forcecr
307 or self.stripcr != UserDefaults.stripcr
308 or self.pass8bits != UserDefaults.pass8bits
309 or self.mimedecode != UserDefaults.mimedecode
310 or self.dropstatus != UserDefaults.dropstatus
311 or self.dropdelivered != UserDefaults.dropdelivered
312 or self.idle != UserDefaults.idle):
313 res = res + " options"
314 if self.keep != UserDefaults.keep:
315 res = res + flag2str(self.keep, 'keep')
316 if self.flush != UserDefaults.flush:
317 res = res + flag2str(self.flush, 'flush')
318 if self.fetchall != UserDefaults.fetchall:
319 res = res + flag2str(self.fetchall, 'fetchall')
320 if self.rewrite != UserDefaults.rewrite:
321 res = res + flag2str(self.rewrite, 'rewrite')
322 if self.forcecr != UserDefaults.forcecr:
323 res = res + flag2str(self.forcecr, 'forcecr')
324 if self.stripcr != UserDefaults.stripcr:
325 res = res + flag2str(self.stripcr, 'stripcr')
326 if self.pass8bits != UserDefaults.pass8bits:
327 res = res + flag2str(self.pass8bits, 'pass8bits')
328 if self.mimedecode != UserDefaults.mimedecode:
329 res = res + flag2str(self.mimedecode, 'mimedecode')
330 if self.dropstatus != UserDefaults.dropstatus:
331 res = res + flag2str(self.dropstatus, 'dropstatus')
332 if self.dropdelivered != UserDefaults.dropdelivered:
333 res = res + flag2str(self.dropdelivered, 'dropdelivered')
334 if self.idle != UserDefaults.idle:
335 res = res + flag2str(self.idle, 'idle')
336 if self.limit != UserDefaults.limit:
337 res = res + " limit " + `self.limit`
338 if self.warnings != UserDefaults.warnings:
339 res = res + " warnings " + `self.warnings`
340 if self.fetchlimit != UserDefaults.fetchlimit:
341 res = res + " fetchlimit " + `self.fetchlimit`
342 if self.batchlimit != UserDefaults.batchlimit:
343 res = res + " batchlimit " + `self.batchlimit`
344 if self.ssl and self.ssl != UserDefaults.ssl:
345 res = res + flag2str(self.ssl, 'ssl')
346 if self.sslkey and self.sslkey != UserDefaults.sslkey:
347 res = res + " sslkey " + `self.sslkey`
348 if self.sslcert and self.sslcert != UserDefaults.sslcert:
349 res = res + " sslcert " + `self.sslcert`
350 if self.sslproto and self.sslproto != UserDefaults.sslproto:
351 res = res + " sslproto " + `self.sslproto`
352 if self.sslcertck and self.sslcertck != UserDefaults.sslcertck:
353 res = res + flag2str(self.sslcertck, 'sslcertck')
354 if self.sslcertpath and self.sslcertpath != UserDefaults.sslcertpath:
355 res = res + " sslcertpath " + `self.sslcertpath`
356 if self.sslfingerprint and self.sslfingerprint != UserDefaults.sslfingerprint:
357 res = res + " sslfingerprint " + `self.sslfingerprint`
358 if self.expunge != UserDefaults.expunge:
359 res = res + " expunge " + `self.expunge`
361 trimmed = self.smtphunt;
362 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
363 trimmed = trimmed[0:len(trimmed) - 1]
364 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
365 trimmed = trimmed[0:len(trimmed) - 1]
367 res = res + " smtphost "
371 trimmed = self.fetchdomains
372 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
373 trimmed = trimmed[0:len(trimmed) - 1]
375 res = res + " fetchdomains "
380 res = res + " folder"
381 for x in self.mailboxes:
384 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
385 if getattr(self, fld):
386 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
387 if self.lmtp != UserDefaults.lmtp:
388 res = res + flag2str(self.lmtp, 'lmtp')
389 if self.antispam != UserDefaults.antispam:
390 res = res + " antispam " + self.antispam + "\n"
394 return "[User: " + repr(self) + "]"
400 defaultports = {"auto":0,
409 authlist = ("any", "password", "gssapi", "kerberos", "ssh")
412 'title' : 'List Selection Help',
413 'banner': 'List Selection',
415 You must select an item in the list box (by clicking on it).
418 def flag2str(value, string):
419 # make a string representation of a .fetchmailrc flag or negated flag
423 if value == FALSE: str = str + ("no ")
427 class LabeledEntry(Frame):
428 # widget consisting of entry field with caption to left
429 def bind(self, key, action):
430 self.E.bind(key, action)
433 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
434 Frame.__init__(self, Master)
435 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
436 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
437 self.L.pack({'side':'left'})
438 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
440 def ButtonBar(frame, legend, ref, alternatives, depth, command):
441 # array of radio buttons, caption to left, picking from a string list
443 width = (len(alternatives)+1) / depth;
444 Label(bar, text=legend).pack(side=LEFT)
445 for column in range(width):
446 subframe = Frame(bar)
447 for row in range(depth):
448 ind = width * row + column
449 if ind < len(alternatives):
450 Radiobutton(subframe,
451 {'text':alternatives[ind],
453 'value':alternatives[ind],
454 'command':command}).pack(side=TOP, anchor=W)
456 # This is just a spacer
457 Radiobutton(subframe,
458 {'text':" ",'state':DISABLED}).pack(side=TOP, anchor=W)
459 subframe.pack(side=LEFT)
463 def helpwin(helpdict):
464 # help message window with a self-destruct button
466 helpwin.title(helpdict['title'])
467 helpwin.iconname(helpdict['title'])
468 Label(helpwin, text=helpdict['banner']).pack()
469 textframe = Frame(helpwin)
470 scroll = Scrollbar(textframe)
471 helpwin.textwidget = Text(textframe, setgrid=TRUE)
472 textframe.pack(side=TOP, expand=YES, fill=BOTH)
473 helpwin.textwidget.config(yscrollcommand=scroll.set)
474 helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
475 scroll.config(command=helpwin.textwidget.yview)
476 scroll.pack(side=RIGHT, fill=BOTH)
477 helpwin.textwidget.insert(END, helpdict['text']);
478 Button(helpwin, text='Done',
479 command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
480 textframe.pack(side=TOP)
482 def make_icon_window(base, image):
484 # Some older pythons will error out on this
485 icon_image = PhotoImage(data=image)
486 icon_window = Toplevel()
487 Label(icon_window, image=icon_image, bg='black').pack()
488 base.master.iconwindow(icon_window)
489 # Avoid TkInter brain death. PhotoImage objects go out of
490 # scope when the enclosing function returns. Therefore
491 # we have to explicitly link them to something.
492 base.keepalive.append(icon_image)
496 class ListEdit(Frame):
497 # edit a list of values (duplicates not allowed) with a supplied editor hook
498 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
500 self.deletor = deletor
503 # Set up a widget to accept new elements
504 self.newval = StringVar(master)
505 newwin = LabeledEntry(master, newlegend, self.newval, '12')
506 newwin.bind('<Double-1>', self.handleNew)
507 newwin.bind('<Return>', self.handleNew)
508 newwin.pack(side=TOP, fill=X, anchor=E)
510 # Edit the existing list
511 listframe = Frame(master)
512 scroll = Scrollbar(listframe)
513 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
516 self.listwidget.insert(END, x)
517 listframe.pack(side=TOP, expand=YES, fill=BOTH)
518 self.listwidget.config(yscrollcommand=scroll.set)
519 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
520 scroll.config(command=self.listwidget.yview)
521 scroll.pack(side=RIGHT, fill=BOTH)
522 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
523 self.listwidget.bind('<Double-1>', self.handleList);
524 self.listwidget.bind('<Return>', self.handleList);
528 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
529 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
531 self.helptxt = helptxt
532 Button(bf, text='Help', fg='blue',
533 command=self.help).pack(side=RIGHT)
537 helpwin(self.helptxt)
539 def handleList(self, event):
542 def handleNew(self, event):
543 item = self.newval.get()
545 entire = self.listwidget.get(0, self.listwidget.index('end'));
546 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
547 self.listwidget.insert('end', item)
548 if self.list != None: self.list.append(item)
550 apply(self.editor, (item,))
554 select = self.listwidget.curselection()
559 if index and self.editor:
560 label = self.listwidget.get(index);
562 apply(self.editor, (label,))
564 def deleteItem(self):
565 select = self.listwidget.curselection()
569 index = string.atoi(select[0])
570 label = self.listwidget.get(index);
571 self.listwidget.delete(index)
572 if self.list != None:
574 if self.deletor != None:
575 apply(self.deletor, (label,))
577 def ConfirmQuit(frame, context):
580 text = 'Really quit ' + context + ' without saving?',
582 strings = ('Yes', 'No'),
586 def dispose_window(master, legend, help, savelegend='OK'):
587 dispose = Frame(master, relief=RAISED, bd=5)
588 Label(dispose, text=legend).pack(side=TOP,pady=10)
589 Button(dispose, text=savelegend, fg='blue',
590 command=master.save).pack(side=LEFT)
591 Button(dispose, text='Quit', fg='blue',
592 command=master.nosave).pack(side=LEFT)
593 Button(dispose, text='Help', fg='blue',
594 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
599 # Common methods for Tkinter widgets -- deals with Tkinter declaration
600 def post(self, widgetclass, field):
601 for x in widgetclass.typemap:
602 if x[1] == 'Boolean':
603 setattr(self, x[0], BooleanVar(self))
604 elif x[1] == 'String':
605 setattr(self, x[0], StringVar(self))
607 setattr(self, x[0], IntVar(self))
608 source = getattr(getattr(self, field), x[0])
610 getattr(self, x[0]).set(source)
612 def fetch(self, widgetclass, field):
613 for x in widgetclass.typemap:
614 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
617 # First, code to set the global fetchmail run controls.
620 configure_novice_help = {
621 'title' : 'Fetchmail novice configurator help',
622 'banner': 'Novice configurator help',
624 In the `Novice Configurator Controls' panel, you can:
626 Press `Save' to save the new fetchmail configuration you have created.
627 Press `Quit' to exit without saving.
628 Press `Help' to bring up this help message.
630 In the `Novice Configuration' panels, you will set up the basic data
631 needed to create a simple fetchmail setup. These include:
633 1. The name of the remote site you want to query.
635 2. Your login name on that site.
637 3. Your password on that site.
639 4. A protocol to use (POP, IMAP, ETRN, etc.)
641 5. A polling interval.
643 6. Options to fetch old messages as well as new, uor to suppress
644 deletion of fetched message.
646 The novice-configuration code will assume that you want to forward mail
647 to a local sendmail listener with no special options.
650 configure_expert_help = {
651 'title' : 'Fetchmail expert configurator help',
652 'banner': 'Expert configurator help',
654 In the `Expert Configurator Controls' panel, you can:
656 Press `Save' to save the new fetchmail configuration you have edited.
657 Press `Quit' to exit without saving.
658 Press `Help' to bring up this help message.
660 In the `Run Controls' panel, you can set the following options that
661 control how fetchmail runs:
664 Number of seconds to wait between polls in the background.
665 If zero, fetchmail will run in foreground.
668 If empty, emit progress and error messages to stderr.
669 Otherwise this gives the name of the files to write to.
670 This field is ignored if the "Log to syslog?" option is on.
673 If empty, store seen-message IDs in .fetchids under user's home
674 directory. If nonempty, use given file name.
677 Who to send multidrop mail to as a last resort if no address can
678 be matched. Normally empty; in this case, fetchmail treats the
679 invoking user as the address of last resort unless that user is
680 root. If that user is root, fetchmail sends to `postmaster'.
683 If this option is on (the default) error mail goes to the sender.
684 Otherwise it goes to the postmaster.
687 If this option is on, spam bounces are sent to the sender or
688 postmaster (depending on the "Bounces to sender?" option. Otherwise,
689 spam bounces are not sent (the default).
692 If false (the default) fetchmail generates a Received line into
693 each message and generates a HELO from the machine it is running on.
694 If true, fetchmail generates no Received line and HELOs as if it were
697 In the `Remote Mail Configurations' panel, you can:
699 1. Enter the name of a new remote mail server you want fetchmail to query.
701 To do this, simply enter a label for the poll configuration in the
702 `New Server:' box. The label should be a DNS name of the server (unless
703 you are using ssh or some other tunneling method and will fill in the `via'
704 option on the site configuration screen).
706 2. Change the configuration of an existing site.
708 To do this, find the site's label in the listbox and double-click it.
709 This will take you to a site configuration dialogue.
713 class ConfigurationEdit(Frame, MyWidget):
714 def __init__(self, configuration, outfile, master, onexit):
716 self.configuration = configuration
717 self.outfile = outfile
718 self.container = master
720 ConfigurationEdit.mode_to_help = {
721 'novice':configure_novice_help, 'expert':configure_expert_help
724 def server_edit(self, sitename):
725 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
727 def server_delete(self, sitename):
729 for user in self.subwidgets.keys():
731 del self.configuration[sitename]
735 def edit(self, mode):
737 Frame.__init__(self, self.container)
738 self.master.title('fetchmail ' + self.mode + ' configurator');
739 self.master.iconname('fetchmail ' + self.mode + ' configurator');
740 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
741 self.keepalive = [] # Use this to anchor the PhotoImage object
742 make_icon_window(self, fetchmail_icon)
744 self.post(Configuration, 'configuration')
747 'Configurator ' + self.mode + ' Controls',
748 ConfigurationEdit.mode_to_help[self.mode],
751 gf = Frame(self, relief=RAISED, bd = 5)
753 text='Fetchmail Run Controls',
754 bd=2).pack(side=TOP, pady=10)
759 if self.mode != 'novice':
761 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
762 log.pack(side=RIGHT, anchor=E)
764 # Set the poll interval
765 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
766 de.pack(side=RIGHT, anchor=E)
771 if self.mode != 'novice':
774 {'text':'Bounces to sender?',
775 'variable':self.bouncemail,
776 'relief':GROOVE}).pack(side=LEFT, anchor=W)
781 {'text':'send spam bounces?',
782 'variable':self.spambounce,
783 'relief':GROOVE}).pack(side=LEFT, anchor=W)
788 {'text':'Log to syslog?',
789 'variable':self.syslog,
790 'relief':GROOVE}).pack(side=LEFT, anchor=W)
791 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
792 log.pack(side=RIGHT, anchor=E)
796 {'text':'Invisible mode?',
797 'variable':self.invisible,
798 'relief':GROOVE}).pack(side=LEFT, anchor=W)
800 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
801 log.pack(side=RIGHT, anchor=E)
805 # Expert mode allows us to edit multiple sites
806 lf = Frame(self, relief=RAISED, bd=5)
808 text='Remote Mail Server Configurations',
809 bd=2).pack(side=TOP, pady=10)
810 ListEdit('New Server:',
811 map(lambda x: x.pollname, self.configuration.servers),
812 lambda site, self=self: self.server_edit(site),
813 lambda site, self=self: self.server_delete(site),
818 for sitename in self.subwidgets.keys():
819 self.subwidgets[sitename].destruct()
820 self.master.destroy()
824 if ConfirmQuit(self, self.mode + " configuration editor"):
828 for sitename in self.subwidgets.keys():
829 self.subwidgets[sitename].save()
830 self.fetch(Configuration, 'configuration')
834 elif not os.path.isfile(self.outfile) or Dialog(self,
835 title = 'Overwrite existing run control file?',
836 text = 'Really overwrite existing run control file?',
838 strings = ('Yes', 'No'),
839 default = 1).num == 0:
841 os.rename(self.outfile, self.outfile + "~")
842 # Pre-1.5.2 compatibility...
845 fm = open(self.outfile, 'w')
847 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
848 fm.write(`self.configuration`)
852 os.chmod(self.outfile, 0600)
856 # Server editing stuff.
859 'title' : 'Remote site help',
860 'banner': 'Remote sites',
862 When you add a site name to the list here,
863 you initialize an entry telling fetchmail
864 how to poll a new site.
866 When you select a sitename (by double-
867 clicking it, or by single-clicking to
868 select and then clicking the Edit button),
869 you will open a window to configure that
874 'title' : 'Server options help',
875 'banner': 'Server Options',
877 The server options screen controls fetchmail
878 options that apply to one of your mailservers.
880 Once you have a mailserver configuration set
881 up as you like it, you can select `OK' to
882 store it in the server list maintained in
883 the main configuration window.
885 If you wish to discard changes to a server
886 configuration, select `Quit'.
890 'title' : 'Run Control help',
891 'banner': 'Run Controls',
893 If the `Poll normally' checkbox is on, the host is polled as part of
894 the normal operation of fetchmail when it is run with no arguments.
895 If it is off, fetchmail will only query this host when it is given as
896 a command-line argument.
898 The `True name of server' box should specify the actual DNS name
899 to query. By default this is the same as the poll name.
901 Normally each host described in the file is queried once each
902 poll cycle. If `Cycles to skip between polls' is greater than 0,
903 that's the number of poll cycles that are skipped between the
904 times this post is actually polled.
906 The `Server timeout' is the number of seconds fetchmail will wait
907 for a reply from the mailserver before concluding it is hung and
912 'title' : 'Protocol and Port help',
913 'banner': 'Protocol and Port',
915 These options control the remote-mail protocol
916 and TCP/IP service port used to query this
919 If you click the `Probe for supported protocols'
920 button, fetchmail will try to find you the most
921 capable server on the selected host (this will
922 only work if you're conncted to the Internet).
923 The probe only checks for ordinary IMAP and POP
924 protocols; fortunately these are the most
925 frequently supported.
927 The `Protocol' button bar offers you a choice of
928 all the different protocols available. The `auto'
929 protocol is the default mode; it probes the host
930 ports for POP3 and IMAP to see if either is
933 Normally the TCP/IP service port to use is
934 dictated by the protocol choice. The `Port'
935 field (only present in expert mode) lets you
936 set a non-standard port.
940 'title' : 'Security option help',
941 'banner': 'Security',
943 The `interface' option allows you to specify a range
944 of IP addresses to monitor for activity. If these
945 addresses are not active, fetchmail will not poll.
946 Specifying this may protect you from a spoofing attack
947 if your client machine has more than one IP gateway
948 address and some of the gateways are to insecure nets.
950 The `monitor' option, if given, specifies the only
951 device through which fetchmail is permitted to connect
952 to servers. This option may be used to prevent
953 fetchmail from triggering an expensive dial-out if the
954 interface is not already active.
956 The `interface' and `monitor' options are available
957 only for Linux and freeBSD systems. See the fetchmail
958 manual page for details on these.
960 The ssl option enables SSL communication with a mailserver
961 supporting Secure Sockets Layer. The sslkey and sslcert options
962 declare key and certificate files for use with SSL.
963 The sslcertck option enables strict checking of SSL server
964 certificates (and sslcertpath gives trusted certificate
965 directory). With sslfingerprint, you can specify a finger-
966 print the server's key is checked against.
968 The `netsec' option will be configurable only if fetchmail
969 was compiled with IPV6 support. If you need to use it,
970 you probably know what to do.
974 'title' : 'Multidrop option help',
975 'banner': 'Multidrop',
977 These options are only useful with multidrop mode.
978 See the manual page for extended discussion.
982 'title' : 'User list help',
983 'banner': 'User list',
985 When you add a user name to the list here,
986 you initialize an entry telling fetchmail
987 to poll the site on behalf of the new user.
989 When you select a username (by double-
990 clicking it, or by single-clicking to
991 select and then clicking the Edit button),
992 you will open a window to configure the
993 user's options on that site.
996 class ServerEdit(Frame, MyWidget):
997 def __init__(self, host, parent):
1000 self.subwidgets = {}
1001 for site in parent.configuration.servers:
1002 if site.pollname == host:
1004 if (self.server == None):
1005 self.server = Server()
1006 self.server.pollname = host
1007 self.server.via = None
1008 parent.configuration.servers.append(self.server)
1010 def edit(self, mode, master=None):
1011 Frame.__init__(self, master)
1013 self.master.title('Fetchmail host ' + self.server.pollname);
1014 self.master.iconname('Fetchmail host ' + self.server.pollname);
1015 self.post(Server, 'server')
1016 self.makeWidgets(self.server.pollname, mode)
1017 self.keepalive = [] # Use this to anchor the PhotoImage object
1018 make_icon_window(self, fetchmail_icon)
1021 # self.wait_window()
1025 for username in self.subwidgets.keys():
1026 self.subwidgets[username].destruct()
1027 del self.parent.subwidgets[self.server.pollname]
1028 Widget.destroy(self.master)
1031 if ConfirmQuit(self, 'server option editing'):
1035 self.fetch(Server, 'server')
1036 for username in self.subwidgets.keys():
1037 self.subwidgets[username].save()
1040 def defaultPort(self):
1041 proto = self.protocol.get()
1042 # Callback to reset the port number whenever the protocol type changes.
1043 # We used to only reset the port if it had a default (zero) value.
1044 # This turns out to be a bad idea especially in Novice mode -- if
1045 # you set POP3 and then set IMAP, the port invisibly remained 110.
1046 # Now we reset unconditionally on the theory that if you're setting
1047 # a custom port number you should be in expert mode and playing
1048 # close enough attention to notice this...
1049 self.port.set(defaultports[proto])
1050 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
1052 def user_edit(self, username, mode):
1053 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
1055 def user_delete(self, username):
1056 if self.subwidgets.has_key(username):
1057 self.subwidgets[username].destruct()
1058 del self.server[username]
1060 def makeWidgets(self, host, mode):
1061 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
1063 leftwin = Frame(self);
1066 if mode != 'novice':
1067 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
1068 Label(ctlwin, text="Run Controls").pack(side=TOP)
1069 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
1070 LabeledEntry(ctlwin, 'True name of ' + host + ':',
1071 self.via, leftwidth).pack(side=TOP, fill=X)
1072 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
1073 self.interval, leftwidth).pack(side=TOP, fill=X)
1074 LabeledEntry(ctlwin, 'Server timeout (seconds):',
1075 self.timeout, leftwidth).pack(side=TOP, fill=X)
1076 Button(ctlwin, text='Help', fg='blue',
1077 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
1080 # Compute the available protocols from the compile-time options
1081 protolist = ['auto']
1082 if 'pop2' in feature_options:
1083 protolist.append("POP2")
1084 if 'pop3' in feature_options:
1085 protolist = protolist + ["POP3", "APOP", "KPOP"]
1086 if 'sdps' in feature_options:
1087 protolist.append("SDPS")
1088 if 'imap' in feature_options:
1089 protolist.append("IMAP")
1090 if 'etrn' in feature_options:
1091 protolist.append("ETRN")
1092 if 'odmr' in feature_options:
1093 protolist.append("ODMR")
1095 protwin = Frame(leftwin, relief=RAISED, bd=5)
1096 Label(protwin, text="Protocol").pack(side=TOP)
1097 ButtonBar(protwin, '',
1098 self.protocol, protolist, 2,
1100 if mode != 'novice':
1101 LabeledEntry(protwin, 'On server TCP/IP port:',
1102 self.port, leftwidth).pack(side=TOP, fill=X)
1104 Checkbutton(protwin,
1105 text="POP3: track `seen' with client-side UIDLs?",
1106 variable=self.uidl).pack(side=TOP)
1107 Button(protwin, text='Probe for supported protocols', fg='blue',
1108 command=self.autoprobe).pack(side=LEFT)
1109 Button(protwin, text='Help', fg='blue',
1110 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1111 protwin.pack(fill=X)
1113 userwin = Frame(leftwin, relief=RAISED, bd=5)
1114 Label(userwin, text="User entries for " + host).pack(side=TOP)
1115 ListEdit("New user: ",
1116 map(lambda x: x.remote, self.server.users),
1117 lambda u, m=mode, s=self: s.user_edit(u, m),
1118 lambda u, s=self: s.user_delete(u),
1120 userwin.pack(fill=X)
1122 leftwin.pack(side=LEFT, anchor=N, fill=X);
1124 if mode != 'novice':
1125 rightwin = Frame(self);
1127 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1128 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1129 LabeledEntry(mdropwin, 'Envelope address header:',
1130 self.envelope, '22').pack(side=TOP, fill=X)
1131 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1132 self.envskip, '22').pack(side=TOP, fill=X)
1133 LabeledEntry(mdropwin, 'Name prefix to strip:',
1134 self.qvirtual, '22').pack(side=TOP, fill=X)
1135 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1136 variable=self.dns).pack(side=TOP)
1137 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1138 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1139 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1140 ListEdit("New domain: ",
1141 self.server.localdomains, None, None, mdropwin, multihelp)
1142 mdropwin.pack(fill=X)
1144 if os_type in ('linux', 'freebsd') or 'netsec' in feature_options:
1145 secwin = Frame(rightwin, relief=RAISED, bd=5)
1146 Label(secwin, text="Security").pack(side=TOP)
1147 # Don't actually let users set this. KPOP sets it implicitly
1148 # ButtonBar(secwin, 'Authorization mode:',
1149 # self.auth, authlist, 1, None).pack(side=TOP)
1150 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1151 LabeledEntry(secwin, 'IP range to check before poll:',
1152 self.interface, leftwidth).pack(side=TOP, fill=X)
1153 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1154 LabeledEntry(secwin, 'Interface to monitor:',
1155 self.monitor, leftwidth).pack(side=TOP, fill=X)
1156 if 'netsec' in feature_options or 'netsec' in dictmembers:
1157 LabeledEntry(secwin, 'IPV6 security options:',
1158 self.netsec, leftwidth).pack(side=TOP, fill=X)
1159 # Someday this should handle Kerberos 5 too
1160 if 'kerberos' in feature_options:
1161 LabeledEntry(secwin, 'Principal:',
1162 self.principal, '12').pack(side=TOP, fill=X)
1163 Button(secwin, text='Help', fg='blue',
1164 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1167 rightwin.pack(side=LEFT, anchor=N);
1169 def autoprobe(self):
1170 # Note: this only handles case (1) near fetchmail.c:1032
1171 # We're assuming people smart enough to set up ssh tunneling
1172 # won't need autoprobing.
1174 realhost = self.server.via
1176 realhost = self.server.pollname
1178 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1179 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1181 sock.connect(realhost, port)
1182 greetline = sock.recv(1024)
1188 confwin = Toplevel()
1189 if greetline == None:
1190 title = "Autoprobe of " + realhost + " failed"
1192 Fetchmailconf didn't find any mailservers active.
1193 This could mean the host doesn't support any,
1194 or that your Internet connection is down, or
1195 that the host is so slow that the probe timed
1196 out before getting a response.
1200 # OK, now try to recognize potential problems
1202 if protocol == "POP2":
1203 warnings = warnings + """
1204 It appears you have somehow found a mailserver running only POP2.
1205 Congratulations. Have you considered a career in archaeology?
1207 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1208 Unless the first line of your fetchmail -V output includes the string "POP2",
1209 you'll have to build it from sources yourself with the configure
1210 switch --enable-POP2.
1214 ### POP3 servers start here
1216 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1217 warnings = warnings + """
1218 This appears to be an old version of the UC Davis POP server. These are
1219 dangerously unreliable (among other problems, they may drop your mailbox
1220 on the floor if your connection is interrupted during the session).
1222 It is strongly recommended that you find a better POP3 server. The fetchmail
1223 FAQ includes pointers to good ones.
1226 # Steve VanDevender <stevev@efn.org> writes:
1227 # The only system I have seen this happen with is cucipop-1.31
1228 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1229 # 2.x and probably quite a few other systems. It appears to be a
1230 # bug or bad interaction with the SunOS realloc() -- it turns out
1231 # that internally cucipop does allocate a certain data structure in
1232 # multiples of 16, using realloc() to bump it up to the next
1233 # multiple if it needs more.
1235 # The distinctive symptom is that when there are 16 messages in the
1236 # inbox, you can RETR and DELE all 16 messages successfully, but on
1237 # QUIT cucipop returns something like "-ERR Error locking your
1238 # mailbox" and aborts without updating it.
1240 # The cucipop banner looks like:
1242 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1244 if string.find(greetline, "Cubic Circle") > 0:
1245 warnings = warnings + """
1246 I see your server is running cucipop. Better make sure the server box
1247 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1248 under that version, and doesn't cope with the result gracefully. Newer
1249 SunOS and Solaris machines run cucipop OK.
1251 Also, some versions of cucipop don't assert an exclusive lock on your
1252 mailbox when it's being queried. This means that if you have more than
1253 one fetchmail query running against the same mailbox, bad things can happen.
1255 if string.find(greetline, "David POP3 Server") > 0:
1256 warnings = warnings + """
1257 This POP3 server is badly broken. You should get rid of it -- and the
1258 brain-dead Microsoft operating system it rode in on.
1261 # The greeting line on the server known to be buggy is:
1262 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1264 if string.find(greetline, "FTGate") > 0:
1265 warnings = warnings + """
1266 This POP server has a weird bug; it says OK twice in response to TOP.
1267 Its response to RETR is normal, so use the `fetchall' option.
1270 if string.find(greetline, "OpenMail") > 0:
1271 warnings = warnings + """
1272 You appear to be using some version of HP OpenMail. Many versions of
1273 OpenMail do not process the "TOP" command correctly; the symptom is that
1274 only the header and first line of each message is retrieved. To work
1275 around this bug, turn on `fetchall' on all user entries associated with
1279 if string.find(greetline, "Escape character is") > 0:
1280 warnings = warnings + """
1281 Your greeting line looks like it was written by a fetid pile of
1282 camel dung identified to me as `popa3d written by Solar Designer'.
1283 Beware! The UIDL support in this thing is known to be completely broken,
1284 and other things probably are too.
1287 if string.find(greetline, "MercuryP/NLM v1.48") > 0:
1288 warnings = warnings + """
1289 This is not a POP3 server. It has delusions of being one, but after
1290 RETR all messages are automatically marked to be deleted. The only
1291 way to prevent this is to issue an RSET before leaving the server.
1292 Fetchmail does this, but we suspect this is probably broken in lots
1296 if string.find(greetline, "POP-Max") > 0:
1297 warnings = warnings + """
1298 The Mail Max POP3 server screws up on mail with attachments. It
1299 reports the message size with attachments included, but doesn't
1300 download them on a RETR or TOP (this violates the IMAP RFCs). It also
1301 doesn't implement TOP correctly. You should get rid of it -- and the
1302 brain-dead NT server it rode in on.
1305 if string.find(greetline, "POP3 Server Ready") > 0:
1306 warnings = warnings + """
1307 Some server that uses this greeting line has been observed to choke on
1308 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1311 if string.find(greetline, "QPOP") > 0:
1312 warnings = warnings + """
1313 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1314 knows all about qpopper. However, be aware that the 2.53 version of
1315 qpopper does something odd that causes fetchmail to hang with a socket
1316 error on very large messages. This is probably not a fetchmail bug, as
1317 it has been observed with fetchpop. The fix is to upgrade to qpopper
1318 3.0beta or a more recent version. Better yet, switch to IMAP.
1321 if string.find(greetline, " sprynet.com") > 0:
1322 warnings = warnings + """
1323 You appear to be using a SpryNet server. In mid-1999 it was reported that
1324 the SpryNet TOP command marks messages seen. Therefore, for proper error
1325 recovery in the event of a line drop, it is strongly recommended that you
1326 turn on `fetchall' on all user entries associated with this server.
1329 if string.find(greetline, "TEMS POP3") > 0:
1330 warnings = warnings + """
1331 Your POP3 server has "TEMS" in its header line. At least one such
1332 server does not process the "TOP" command correctly; the symptom is
1333 that fetchmail hangs when trying to retrieve mail. To work around
1334 this bug, turn on `fetchall' on all user entries associated with this
1338 if string.find(greetline, " spray.se") > 0:
1339 warnings = warnings + """
1340 Your POP3 server has "spray.se" in its header line. In May 2000 at
1341 least one such server did not process the "TOP" command correctly; the
1342 symptom is that messages are treated as headerless. To work around
1343 this bug, turn on `fetchall' on all user entries associated with this
1347 if string.find(greetline, " usa.net") > 0:
1348 warnings = warnings + """
1349 You appear to be using USA.NET's free mail service. Their POP3 servers
1350 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1351 fetchmail can compensate. They seem to require that fetchall be switched on
1352 (otherwise you won't necessarily see all your mail, not even new mail).
1353 They also botch the TOP command the fetchmail normally uses for retrieval
1354 (it only retrieves about 10 lines rather than the number specified).
1355 Turning on fetchall will disable the use of TOP.
1357 Therefore, it is strongly recommended that you turn on `fetchall' on all
1358 user entries associated with this server.
1361 if string.find(greetline, " Novonyx POP3") > 0:
1362 warnings = warnings + """
1363 Your mailserver is running Novonyx POP3. This server, at least as of
1364 version 2.17, seems to have problems handling and reporting seen bits.
1365 You may have to use the fetchall option.
1369 ### IMAP servers start here
1371 if string.find(greetline, "GroupWise") > 0:
1372 warnings = warnings + """
1373 The Novell GroupWise IMAP server would be better named GroupFoolish;
1374 it is (according to the designer of IMAP) unusably broken. Among
1375 other things, it doesn't include a required content length in its
1376 BODY[TEXT] response.<p>
1378 Fetchmail works around this problem, but we strongly recommend voting
1379 with your dollars for a server that isn't brain-dead. If you stick
1380 with code as shoddy as GroupWise seems to be, you will probably pay
1381 for it with other problems.<p>
1384 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1385 warnings = warnings + """
1386 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1387 set the messages's Seen flag. As a result, if you use the keep option the
1388 same messages will be downloaded over and over.
1391 if string.find(greetline, "InterChange") > 0:
1392 warnings = warnings + """
1394 The InterChange IMAP server at release levels below 3.61.08 screws up
1395 on mail with attachments. It doesn't fetch them if you give it a
1396 BODY[TEXT] request, though it does if you request RFC822.TEXT.
1397 According to the IMAP RFCs and their maintainer these should be
1398 equivalent -- and we can't drop the BODY[TEXT] form because M$
1399 Exchange (quite legally under RFC2062) rejectsit. The InterChange
1400 folks claim to have fixed this bug in 3.61.08.
1403 if string.find(greetline, "Imail") > 0:
1404 warnings = warnings + """
1405 We've seen a bug report indicating that this IMAP server (at least as of
1406 version 5.0.7) returns an invalid body size for messages with MIME
1407 attachments; the effect is to drop the attachments on the floor. We
1408 recommend you upgrade to a non-broken IMAP server.
1411 if string.find(greetline, "Domino IMAP4") > 0:
1412 warnings = warnings + """
1413 Your IMAP server appears to be Lotus Domino. This server, at least up
1414 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1415 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1416 will see attachments as part of the message text. If your Domino server's
1417 POP3 facility is enabled, we recommend you fall back on it.
1421 ### Checks for protocol variants start here
1423 closebrak = string.find(greetline, ">")
1424 if closebrak > 0 and greetline[closebrak+1] == "\r":
1425 warnings = warnings + """
1426 It looks like you could use APOP on this server and avoid sending it your
1427 password in clear. You should talk to the mailserver administrator about
1431 if string.find(greetline, "IMAP2bis") > 0:
1432 warnings = warnings + """
1433 IMAP2bis servers have a minor problem; they can't peek at messages without
1434 marking them seen. If you take a line hit during the retrieval, the
1435 interrupted message may get left on the server, marked seen.
1437 To work around this, it is recommended that you set the `fetchall'
1438 option on all user entries associated with this server, so any stuck
1439 mail will be retrieved next time around.
1441 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1442 a pointer to an open-source implementation.
1445 if string.find(greetline, "IMAP4rev1") > 0:
1446 warnings = warnings + """
1447 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1448 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1449 has therefore been extremely well tested with this class of server.
1453 warnings = warnings + """
1454 Fetchmail doesn't know anything special about this server type.
1458 # Display success window with warnings
1459 title = "Autoprobe of " + realhost + " succeeded"
1460 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1461 self.protocol.set(protocol)
1462 confwin.title(title)
1463 confwin.iconname(title)
1464 Label(confwin, text=title).pack()
1465 Message(confwin, text=confirm, width=600).pack()
1466 Button(confwin, text='Done',
1467 command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1470 # User editing stuff
1474 'title' : 'User option help',
1475 'banner': 'User options',
1477 You may use this panel to set options
1478 that may differ between individual
1481 Once you have a user configuration set
1482 up as you like it, you can select `OK' to
1483 store it in the user list maintained in
1484 the site configuration window.
1486 If you wish to discard the changes you have
1487 made to user options, select `Quit'.
1491 'title' : 'Local name help',
1492 'banner': 'Local names',
1494 The local name(s) in a user entry are the
1495 people on the client machine who should
1496 receive mail from the poll described.
1498 Note: if a user entry has more than one
1499 local name, messages will be retrieved
1500 in multidrop mode. This complicates
1501 the configuration issues; see the manual
1502 page section on multidrop mode.
1504 Warning: Be careful with local names
1505 such as foo@bar.com, as that can cause
1506 the mail to be sent to foo@bar.com instead
1507 of sending it to your local system.
1510 class UserEdit(Frame, MyWidget):
1511 def __init__(self, username, parent):
1512 self.parent = parent
1514 for user in parent.server.users:
1515 if user.remote == username:
1517 if self.user == None:
1519 self.user.remote = username
1520 self.user.localnames = [username]
1521 parent.server.users.append(self.user)
1523 def edit(self, mode, master=None):
1524 Frame.__init__(self, master)
1526 self.master.title('Fetchmail user ' + self.user.remote
1527 + ' querying ' + self.parent.server.pollname);
1528 self.master.iconname('Fetchmail user ' + self.user.remote);
1529 self.post(User, 'user')
1530 self.makeWidgets(mode, self.parent.server.pollname)
1531 self.keepalive = [] # Use this to anchor the PhotoImage object
1532 make_icon_window(self, fetchmail_icon)
1535 # self.wait_window()
1539 # Yes, this test can fail -- if you delete the parent window.
1540 if self.parent.subwidgets.has_key(self.user.remote):
1541 del self.parent.subwidgets[self.user.remote]
1542 Widget.destroy(self.master)
1545 if ConfirmQuit(self, 'user option editing'):
1550 for x in self.user.localnames: ok = ok + (string.find(x, '@') != -1)
1551 if ok == 0 or Dialog(self,
1552 title = "Really accept an embedded '@' ?",
1553 text = "Local names with an embedded '@', such as in foo@bar "
1554 "might result in your mail being sent to foo@bar.com "
1555 "instead of your local system.\n Are you sure you want "
1556 "a local user name with an '@' in it?",
1557 bitmap = 'question',
1558 strings = ('Yes', 'No'),
1559 default = 1).num == 0:
1560 self.fetch(User, 'user')
1563 def makeWidgets(self, mode, servername):
1564 dispose_window(self,
1565 "User options for " + self.user.remote + " querying " + servername,
1568 if mode != 'novice':
1569 leftwin = Frame(self);
1573 secwin = Frame(leftwin, relief=RAISED, bd=5)
1574 Label(secwin, text="Authentication").pack(side=TOP)
1575 LabeledEntry(secwin, 'Password:',
1576 self.password, '12').pack(side=TOP, fill=X)
1577 secwin.pack(fill=X, anchor=N)
1579 if 'ssl' in feature_options or 'ssl' in dictmembers:
1580 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1581 Checkbutton(sslwin, text="Use SSL?",
1582 variable=self.ssl).pack(side=TOP, fill=X)
1583 LabeledEntry(sslwin, 'SSL key:',
1584 self.sslkey, '14').pack(side=TOP, fill=X)
1585 LabeledEntry(sslwin, 'SSL certificate:',
1586 self.sslcert, '14').pack(side=TOP, fill=X)
1587 Checkbutton(sslwin, text="Check server SSL certificate?",
1588 variable=self.sslcertck).pack(side=TOP, fill=X)
1589 LabeledEntry(sslwin, 'SSL trusted certificate directory:',
1590 self.sslcertpath, '14').pack(side=TOP, fill=X)
1591 LabeledEntry(sslwin, 'SSL key fingerprint:',
1592 self.sslfingerprint, '14').pack(side=TOP, fill=X)
1593 sslwin.pack(fill=X, anchor=N)
1595 names = Frame(leftwin, relief=RAISED, bd=5)
1596 Label(names, text="Local names").pack(side=TOP)
1597 ListEdit("New name: ",
1598 self.user.localnames, None, None, names, localhelp)
1599 names.pack(fill=X, anchor=N)
1601 if mode != 'novice':
1602 targwin = Frame(leftwin, relief=RAISED, bd=5)
1603 Label(targwin, text="Forwarding Options").pack(side=TOP)
1604 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1605 ListEdit("New listener:",
1606 self.user.smtphunt, None, None, targwin, None)
1607 Label(targwin, text="Domains to fetch from (ODMR/ETRN only)").pack(side=TOP)
1608 ListEdit("Domains:",
1609 self.user.fetchdomains, None, None, targwin, None)
1610 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1611 self.smtpaddress, '26').pack(side=TOP, fill=X)
1612 LabeledEntry(targwin, 'Set RCPT To address:',
1613 self.smtpname, '26').pack(side=TOP, fill=X)
1614 LabeledEntry(targwin, 'Connection setup command:',
1615 self.preconnect, '26').pack(side=TOP, fill=X)
1616 LabeledEntry(targwin, 'Connection wrapup command:',
1617 self.postconnect, '26').pack(side=TOP, fill=X)
1618 LabeledEntry(targwin, 'Local delivery agent:',
1619 self.mda, '26').pack(side=TOP, fill=X)
1620 LabeledEntry(targwin, 'BSMTP output file:',
1621 self.bsmtp, '26').pack(side=TOP, fill=X)
1622 LabeledEntry(targwin, 'Listener spam-block codes:',
1623 self.antispam, '26').pack(side=TOP, fill=X)
1624 LabeledEntry(targwin, 'Pass-through properties:',
1625 self.properties, '26').pack(side=TOP, fill=X)
1626 Checkbutton(targwin, text="Use LMTP?",
1627 variable=self.lmtp).pack(side=TOP, fill=X)
1628 targwin.pack(fill=X, anchor=N)
1630 if mode != 'novice':
1631 leftwin.pack(side=LEFT, fill=X, anchor=N)
1632 rightwin = Frame(self)
1636 optwin = Frame(rightwin, relief=RAISED, bd=5)
1637 Label(optwin, text="Processing Options").pack(side=TOP)
1638 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1639 variable=self.keep).pack(side=TOP, anchor=W)
1640 Checkbutton(optwin, text="Fetch old messages as well as new",
1641 variable=self.fetchall).pack(side=TOP, anchor=W)
1642 if mode != 'novice':
1643 Checkbutton(optwin, text="Flush seen messages before retrieval",
1644 variable=self.flush).pack(side=TOP, anchor=W)
1645 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1646 variable=self.rewrite).pack(side=TOP, anchor=W)
1647 Checkbutton(optwin, text="Force CR/LF at end of each line",
1648 variable=self.forcecr).pack(side=TOP, anchor=W)
1649 Checkbutton(optwin, text="Strip CR from end of each line",
1650 variable=self.stripcr).pack(side=TOP, anchor=W)
1651 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1652 variable=self.pass8bits).pack(side=TOP, anchor=W)
1653 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1654 variable=self.mimedecode).pack(side=TOP, anchor=W)
1655 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1656 variable=self.dropstatus).pack(side=TOP, anchor=W)
1657 Checkbutton(optwin, text="Drop Delivered-To lines from forwarded messages",
1658 variable=self.dropdelivered).pack(side=TOP, anchor=W)
1661 if mode != 'novice':
1662 limwin = Frame(rightwin, relief=RAISED, bd=5)
1663 Label(limwin, text="Resource Limits").pack(side=TOP)
1664 LabeledEntry(limwin, 'Message size limit:',
1665 self.limit, '30').pack(side=TOP, fill=X)
1666 LabeledEntry(limwin, 'Size warning interval:',
1667 self.warnings, '30').pack(side=TOP, fill=X)
1668 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1669 self.fetchlimit, '30').pack(side=TOP, fill=X)
1670 LabeledEntry(limwin, 'Max messages to forward per poll:',
1671 self.batchlimit, '30').pack(side=TOP, fill=X)
1672 if self.parent.server.protocol not in ('ETRN', 'ODMR'):
1673 LabeledEntry(limwin, 'Interval between expunges:',
1674 self.expunge, '30').pack(side=TOP, fill=X)
1675 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1676 variable=self.idle).pack(side=TOP, anchor=W)
1679 if self.parent.server.protocol == 'IMAP':
1680 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1681 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1682 ListEdit("New folder:", self.user.mailboxes,
1683 None, None, foldwin, None)
1684 foldwin.pack(fill=X, anchor=N)
1686 if mode != 'novice':
1687 rightwin.pack(side=LEFT)
1693 # Top-level window that offers either novice or expert mode
1694 # (but not both at once; it disappears when one is selected).
1697 class Configurator(Frame):
1698 def __init__(self, outfile, master, onexit, parent):
1699 Frame.__init__(self, master)
1700 self.outfile = outfile
1701 self.onexit = onexit
1702 self.parent = parent
1703 self.master.title('fetchmail configurator');
1704 self.master.iconname('fetchmail configurator');
1706 self.keepalive = [] # Use this to anchor the PhotoImage object
1707 make_icon_window(self, fetchmail_icon)
1709 Message(self, text="""
1710 Use `Novice Configuration' for basic fetchmail setup;
1711 with this, you can easily set up a single-drop connection
1712 to one remote mail server.
1713 """, width=600).pack(side=TOP)
1714 Button(self, text='Novice Configuration',
1715 fg='blue', command=self.novice).pack()
1717 Message(self, text="""
1718 Use `Expert Configuration' for advanced fetchmail setup,
1719 including multiple-site or multidrop connections.
1720 """, width=600).pack(side=TOP)
1721 Button(self, text='Expert Configuration',
1722 fg='blue', command=self.expert).pack()
1724 Message(self, text="""
1725 Or you can just select `Quit' to leave the configurator now and
1726 return to the main panel.
1727 """, width=600).pack(side=TOP)
1728 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1729 master.protocol("WM_DELETE_WINDOW", self.leave)
1732 self.master.destroy()
1733 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1736 self.master.destroy()
1737 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1740 self.master.destroy()
1743 # Run a command in a scrolling text widget, displaying its output
1745 class RunWindow(Frame):
1746 def __init__(self, command, master, parent):
1747 Frame.__init__(self, master)
1748 self.master = master
1749 self.master.title('fetchmail run window');
1750 self.master.iconname('fetchmail run window');
1753 text="Running "+command,
1754 bd=2).pack(side=TOP, pady=10)
1755 self.keepalive = [] # Use this to anchor the PhotoImage object
1756 make_icon_window(self, fetchmail_icon)
1758 # This is a scrolling text window
1759 textframe = Frame(self)
1760 scroll = Scrollbar(textframe)
1761 self.textwidget = Text(textframe, setgrid=TRUE)
1762 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1763 self.textwidget.config(yscrollcommand=scroll.set)
1764 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1765 scroll.config(command=self.textwidget.yview)
1766 scroll.pack(side=RIGHT, fill=BOTH)
1767 textframe.pack(side=TOP)
1769 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1771 self.update() # Draw widget before executing fetchmail
1773 # Always look for a runnable command in the directory we're running in
1774 # first. This avoids some obscure version-skew errors that can occur
1775 # if you pick up an old fetchmail from the standard system locations.
1776 os.environ["PATH"] = os.path.dirname(sys.argv[0]) + ":" + os.environ["PATH"]
1777 child_stdout = os.popen(command + " 2>&1", "r")
1779 ch = child_stdout.read(1)
1782 self.textwidget.insert(END, ch)
1783 self.textwidget.insert(END, "Done.")
1784 self.textwidget.see(END);
1787 Widget.destroy(self.master)
1789 # Here's where we choose either configuration or launching
1791 class MainWindow(Frame):
1792 def __init__(self, outfile, master=None):
1793 Frame.__init__(self, master)
1794 self.outfile = outfile
1795 self.master.title('fetchmail launcher');
1796 self.master.iconname('fetchmail launcher');
1799 text='Fetchmailconf ' + version,
1800 bd=2).pack(side=TOP, pady=10)
1801 self.keepalive = [] # Use this to anchor the PhotoImage object
1802 make_icon_window(self, fetchmail_icon)
1805 ## Test icon display with the following:
1806 # icon_image = PhotoImage(data=fetchmail_icon)
1807 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1808 # self.keepalive.append(icon_image)
1810 Message(self, text="""
1811 Use `Configure fetchmail' to tell fetchmail about the remote
1812 servers it should poll (the host name, your username there,
1813 whether to use POP or IMAP, and so forth).
1814 """, width=600).pack(side=TOP)
1815 self.configbutton = Button(self, text='Configure fetchmail',
1816 fg='blue', command=self.configure)
1817 self.configbutton.pack()
1819 Message(self, text="""
1820 Use `Test fetchmail' to run fetchmail with debugging enabled.
1821 This is a good way to test out a new configuration.
1822 """, width=600).pack(side=TOP)
1823 Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1825 Message(self, text="""
1826 Use `Run fetchmail' to run fetchmail in foreground.
1827 Progress messages will be shown, but not debug messages.
1828 """, width=600).pack(side=TOP)
1829 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1831 Message(self, text="""
1832 Or you can just select `Quit' to exit the launcher now.
1833 """, width=600).pack(side=TOP)
1834 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1836 def configure(self):
1837 self.configbutton.configure(state=DISABLED)
1838 Configurator(self.outfile, Toplevel(),
1839 lambda self=self: self.configbutton.configure(state=NORMAL),
1842 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1845 RunWindow("fetchmail -d0", Toplevel(), self)
1850 # Functions for turning a dictionary into an instantiated object tree.
1852 def intersect(list1, list2):
1853 # Compute set intersection of lists
1860 def setdiff(list1, list2):
1861 # Compute set difference of lists
1868 def copy_instance(toclass, fromdict):
1869 # Initialize a class object of given type from a conformant dictionary.
1870 for fld in fromdict.keys():
1871 if not fld in dictmembers:
1872 dictmembers.append(fld)
1873 # The `optional' fields are the ones we can ignore for purposes of
1874 # conformability checking; they'll still get copied if they are
1875 # present in the dictionary.
1876 optional = ('interface', 'monitor',
1878 'ssl', 'sslkey', 'sslcert', 'sslproto', 'sslcertck',
1879 'sslcertpath', 'sslfingerprint', 'showdots')
1880 class_sig = setdiff(toclass.__dict__.keys(), optional)
1882 dict_keys = setdiff(fromdict.keys(), optional)
1884 common = intersect(class_sig, dict_keys)
1885 if 'typemap' in class_sig:
1886 class_sig.remove('typemap')
1887 if tuple(class_sig) != tuple(dict_keys):
1888 print "Fields don't match what fetchmailconf expected:"
1889 # print "Class signature: " + `class_sig`
1890 # print "Dictionary keys: " + `dict_keys`
1891 diff = setdiff(class_sig, common)
1893 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1894 diff = setdiff(dict_keys, common)
1896 print "Not matched in dictionary keys: " + `diff`
1899 for x in fromdict.keys():
1900 setattr(toclass, x, fromdict[x])
1903 # And this is the main sequence. How it works:
1905 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1906 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1907 # Run execfile on the file to pull fetchmailrc into Python global space.
1908 # You don't want static data, though; you want, instead, a tree of objects
1909 # with the same data members and added appropriate methods.
1911 # This is what the copy_instance function() is for. It tries to copy a
1912 # dictionary field by field into a class, aborting if the class and dictionary
1913 # have different data members (except for any typemap member in the class;
1914 # that one is strictly for use by the MyWidget supperclass).
1916 # Once the object tree is set up, require user to choose novice or expert
1917 # mode and instantiate an edit object for the configuration. Class methods
1918 # will take it all from there.
1920 # Options (not documented because they're for fetchmailconf debuggers only):
1921 # -d: Read the configuration and dump it to stdout before editing. Dump
1922 # the edited result to stdout as well.
1923 # -f: specify the run control file to read.
1925 if __name__ == '__main__':
1927 if not os.environ.has_key("DISPLAY"):
1928 print "fetchmailconf must be run under X"
1931 fetchmail_icon = """
1932 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1933 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1934 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1935 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1936 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1937 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1938 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1939 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1940 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1941 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1942 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1943 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1944 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1945 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1946 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1947 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1948 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1949 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1950 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1951 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1952 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1953 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1954 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1955 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1956 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1957 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1958 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1959 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1960 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1961 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1962 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1963 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1965 # The base64 data in the string above was generated by the following procedure:
1968 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1972 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1973 dump = rcfile = None;
1974 for (switch, val) in options:
1975 if (switch == '-d'):
1977 elif (switch == '-f'):
1980 # Get client host's FQDN
1981 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1984 ConfigurationDefaults = Configuration()
1985 ServerDefaults = Server()
1986 UserDefaults = User()
1988 # Read the existing configuration. We set the umask to 077 to make sure
1989 # that group & other read/write permissions are shut off -- we wouldn't
1990 # want crackers to snoop password information out of the tempfile.
1991 tmpfile = tempfile.mktemp()
1993 cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1995 cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
2000 print "`" + cmd + "' run failure, status " + `s`
2003 print "Unknown error while running fetchmail --configdump"
2010 print "Can't read configuration output of fetchmail --configdump."
2016 # The tricky part -- initializing objects from the configuration global
2017 # `Configuration' is the top level of the object tree we're going to mung.
2018 # The dictmembers list is used to track the set of fields the dictionary
2019 # contains; in particular, we can use it to tell whether things like the
2020 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
2022 Fetchmailrc = Configuration()
2023 copy_instance(Fetchmailrc, fetchmailrc)
2024 Fetchmailrc.servers = [];
2025 for server in fetchmailrc['servers']:
2027 copy_instance(Newsite, server)
2028 Fetchmailrc.servers.append(Newsite)
2030 for user in server['users']:
2032 copy_instance(Newuser, user)
2033 Newsite.users.append(Newuser)
2035 # We may want to display the configuration and quit
2037 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
2039 # The theory here is that -f alone sets the rcfile location,
2040 # but -d and -f together mean the new configuration should go to stdout.
2041 if not rcfile and not dump:
2042 rcfile = os.environ["HOME"] + "/.fetchmailrc"
2044 # OK, now run the configuration edit
2045 root = MainWindow(rcfile)
2048 # The following sets edit modes for GNU EMACS