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 = "" # 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.fetchsizelimit = 100 # Max message sizes fetched per transaction
254 self.fastuidl = 10 # Do fast uidl 9 out of 10 times
255 self.batchlimit = 0 # Max message forwarded per batch
256 self.expunge = 0 # Interval between expunges (IMAP)
257 self.ssl = 0 # Enable Seccure Socket Layer
258 self.sslkey = None # SSL key filename
259 self.sslcert = None # SSL certificate filename
260 self.sslproto = None # Force SSL?
261 self.sslcertck = 0 # Enable strict SSL cert checking
262 self.sslcertpath = None # Path to trusted certificates
263 self.sslfingerprint = None # SSL key fingerprint to check
264 self.properties = None # Extension properties
266 ('remote', 'String'),
267 # leave out mailboxes and localnames
268 ('password', 'String'),
269 # Leave out smtphunt, fetchdomains
270 ('smtpaddress', 'String'),
271 ('smtpname', 'String'),
272 ('preconnect', 'String'),
273 ('postconnect', 'String'),
277 ('antispam', 'String'),
279 ('flush', 'Boolean'),
280 ('fetchall', 'Boolean'),
281 ('rewrite', 'Boolean'),
282 ('forcecr', 'Boolean'),
283 ('stripcr', 'Boolean'),
284 ('pass8bits', 'Boolean'),
285 ('mimedecode', 'Boolean'),
286 ('dropstatus', 'Boolean'),
287 ('dropdelivered', 'Boolean'),
291 ('fetchlimit', 'Int'),
292 ('fetchsizelimit', 'Int'),
294 ('batchlimit', 'Int'),
297 ('sslkey', 'String'),
298 ('sslcert', 'String'),
299 ('sslcertck', 'Boolean'),
300 ('sslcertpath', 'String'),
301 ('sslfingerprint', 'String'),
302 ('properties', 'String'))
306 res = res + "user " + `self.remote` + " there ";
308 res = res + "with password " + `self.password` + " "
311 for x in self.localnames:
312 res = res + " " + `x`
314 if (self.keep != UserDefaults.keep
315 or self.flush != UserDefaults.flush
316 or self.fetchall != UserDefaults.fetchall
317 or self.rewrite != UserDefaults.rewrite
318 or self.forcecr != UserDefaults.forcecr
319 or self.stripcr != UserDefaults.stripcr
320 or self.pass8bits != UserDefaults.pass8bits
321 or self.mimedecode != UserDefaults.mimedecode
322 or self.dropstatus != UserDefaults.dropstatus
323 or self.dropdelivered != UserDefaults.dropdelivered
324 or self.idle != UserDefaults.idle):
325 res = res + " options"
326 if self.keep != UserDefaults.keep:
327 res = res + flag2str(self.keep, 'keep')
328 if self.flush != UserDefaults.flush:
329 res = res + flag2str(self.flush, 'flush')
330 if self.fetchall != UserDefaults.fetchall:
331 res = res + flag2str(self.fetchall, 'fetchall')
332 if self.rewrite != UserDefaults.rewrite:
333 res = res + flag2str(self.rewrite, 'rewrite')
334 if self.forcecr != UserDefaults.forcecr:
335 res = res + flag2str(self.forcecr, 'forcecr')
336 if self.stripcr != UserDefaults.stripcr:
337 res = res + flag2str(self.stripcr, 'stripcr')
338 if self.pass8bits != UserDefaults.pass8bits:
339 res = res + flag2str(self.pass8bits, 'pass8bits')
340 if self.mimedecode != UserDefaults.mimedecode:
341 res = res + flag2str(self.mimedecode, 'mimedecode')
342 if self.dropstatus != UserDefaults.dropstatus:
343 res = res + flag2str(self.dropstatus, 'dropstatus')
344 if self.dropdelivered != UserDefaults.dropdelivered:
345 res = res + flag2str(self.dropdelivered, 'dropdelivered')
346 if self.idle != UserDefaults.idle:
347 res = res + flag2str(self.idle, 'idle')
348 if self.limit != UserDefaults.limit:
349 res = res + " limit " + `self.limit`
350 if self.warnings != UserDefaults.warnings:
351 res = res + " warnings " + `self.warnings`
352 if self.fetchlimit != UserDefaults.fetchlimit:
353 res = res + " fetchlimit " + `self.fetchlimit`
354 if self.fetchsizelimit != UserDefaults.fetchsizelimit:
355 res = res + " fetchsizelimit " + `self.fetchsizelimit`
356 if self.fastuidl != UserDefaults.fastuidl:
357 res = res + " fastuidl " + `self.fastuidl`
358 if self.batchlimit != UserDefaults.batchlimit:
359 res = res + " batchlimit " + `self.batchlimit`
360 if self.ssl and self.ssl != UserDefaults.ssl:
361 res = res + flag2str(self.ssl, 'ssl')
362 if self.sslkey and self.sslkey != UserDefaults.sslkey:
363 res = res + " sslkey " + `self.sslkey`
364 if self.sslcert and self.sslcert != UserDefaults.sslcert:
365 res = res + " sslcert " + `self.sslcert`
366 if self.sslproto and self.sslproto != UserDefaults.sslproto:
367 res = res + " sslproto " + `self.sslproto`
368 if self.sslcertck and self.sslcertck != UserDefaults.sslcertck:
369 res = res + flag2str(self.sslcertck, 'sslcertck')
370 if self.sslcertpath and self.sslcertpath != UserDefaults.sslcertpath:
371 res = res + " sslcertpath " + `self.sslcertpath`
372 if self.sslfingerprint and self.sslfingerprint != UserDefaults.sslfingerprint:
373 res = res + " sslfingerprint " + `self.sslfingerprint`
374 if self.expunge != UserDefaults.expunge:
375 res = res + " expunge " + `self.expunge`
377 trimmed = self.smtphunt;
378 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
379 trimmed = trimmed[0:len(trimmed) - 1]
380 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
381 trimmed = trimmed[0:len(trimmed) - 1]
383 res = res + " smtphost "
387 trimmed = self.fetchdomains
388 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
389 trimmed = trimmed[0:len(trimmed) - 1]
391 res = res + " fetchdomains "
396 res = res + " folder"
397 for x in self.mailboxes:
400 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
401 if getattr(self, fld):
402 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
403 if self.lmtp != UserDefaults.lmtp:
404 res = res + flag2str(self.lmtp, 'lmtp')
405 if self.antispam != UserDefaults.antispam:
406 res = res + " antispam " + self.antispam + "\n"
410 return "[User: " + repr(self) + "]"
416 defaultports = {"auto":0,
425 authlist = ("any", "password", "gssapi", "kerberos", "ssh", "otp")
428 'title' : 'List Selection Help',
429 'banner': 'List Selection',
431 You must select an item in the list box (by clicking on it).
434 def flag2str(value, string):
435 # make a string representation of a .fetchmailrc flag or negated flag
439 if value == FALSE: str = str + ("no ")
443 class LabeledEntry(Frame):
444 # widget consisting of entry field with caption to left
445 def bind(self, key, action):
446 self.E.bind(key, action)
449 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
450 Frame.__init__(self, Master)
451 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
452 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
453 self.L.pack({'side':'left'})
454 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
456 def ButtonBar(frame, legend, ref, alternatives, depth, command):
457 # array of radio buttons, caption to left, picking from a string list
459 width = (len(alternatives)+1) / depth;
460 Label(bar, text=legend).pack(side=LEFT)
461 for column in range(width):
462 subframe = Frame(bar)
463 for row in range(depth):
464 ind = width * row + column
465 if ind < len(alternatives):
466 Radiobutton(subframe,
467 {'text':alternatives[ind],
469 'value':alternatives[ind],
470 'command':command}).pack(side=TOP, anchor=W)
472 # This is just a spacer
473 Radiobutton(subframe,
474 {'text':" ",'state':DISABLED}).pack(side=TOP, anchor=W)
475 subframe.pack(side=LEFT)
479 def helpwin(helpdict):
480 # help message window with a self-destruct button
482 helpwin.title(helpdict['title'])
483 helpwin.iconname(helpdict['title'])
484 Label(helpwin, text=helpdict['banner']).pack()
485 textframe = Frame(helpwin)
486 scroll = Scrollbar(textframe)
487 helpwin.textwidget = Text(textframe, setgrid=TRUE)
488 textframe.pack(side=TOP, expand=YES, fill=BOTH)
489 helpwin.textwidget.config(yscrollcommand=scroll.set)
490 helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
491 scroll.config(command=helpwin.textwidget.yview)
492 scroll.pack(side=RIGHT, fill=BOTH)
493 helpwin.textwidget.insert(END, helpdict['text']);
494 Button(helpwin, text='Done',
495 command=lambda x=helpwin: x.destroy(), bd=2).pack()
496 textframe.pack(side=TOP)
498 def make_icon_window(base, image):
500 # Some older pythons will error out on this
501 icon_image = PhotoImage(data=image)
502 icon_window = Toplevel()
503 Label(icon_window, image=icon_image, bg='black').pack()
504 base.master.iconwindow(icon_window)
505 # Avoid TkInter brain death. PhotoImage objects go out of
506 # scope when the enclosing function returns. Therefore
507 # we have to explicitly link them to something.
508 base.keepalive.append(icon_image)
512 class ListEdit(Frame):
513 # edit a list of values (duplicates not allowed) with a supplied editor hook
514 def __init__(self, newlegend, list, editor, deletor, master, helptxt):
516 self.deletor = deletor
519 # Set up a widget to accept new elements
520 self.newval = StringVar(master)
521 newwin = LabeledEntry(master, newlegend, self.newval, '12')
522 newwin.bind('<Double-1>', self.handleNew)
523 newwin.bind('<Return>', self.handleNew)
524 newwin.pack(side=TOP, fill=X, anchor=E)
526 # Edit the existing list
527 listframe = Frame(master)
528 scroll = Scrollbar(listframe)
529 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
532 self.listwidget.insert(END, x)
533 listframe.pack(side=TOP, expand=YES, fill=BOTH)
534 self.listwidget.config(yscrollcommand=scroll.set)
535 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
536 scroll.config(command=self.listwidget.yview)
537 scroll.pack(side=RIGHT, fill=BOTH)
538 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
539 self.listwidget.bind('<Double-1>', self.handleList);
540 self.listwidget.bind('<Return>', self.handleList);
544 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
545 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
547 self.helptxt = helptxt
548 Button(bf, text='Help', fg='blue',
549 command=self.help).pack(side=RIGHT)
553 helpwin(self.helptxt)
555 def handleList(self, event):
558 def handleNew(self, event):
559 item = self.newval.get()
561 entire = self.listwidget.get(0, self.listwidget.index('end'));
562 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
563 self.listwidget.insert('end', item)
564 if self.list != None: self.list.append(item)
566 apply(self.editor, (item,))
570 select = self.listwidget.curselection()
575 if index and self.editor:
576 label = self.listwidget.get(index);
578 apply(self.editor, (label,))
580 def deleteItem(self):
581 select = self.listwidget.curselection()
585 index = string.atoi(select[0])
586 label = self.listwidget.get(index);
587 self.listwidget.delete(index)
588 if self.list != None:
590 if self.deletor != None:
591 apply(self.deletor, (label,))
593 def ConfirmQuit(frame, context):
596 text = 'Really quit ' + context + ' without saving?',
598 strings = ('Yes', 'No'),
602 def dispose_window(master, legend, help, savelegend='OK'):
603 dispose = Frame(master, relief=RAISED, bd=5)
604 Label(dispose, text=legend).pack(side=TOP,pady=10)
605 Button(dispose, text=savelegend, fg='blue',
606 command=master.save).pack(side=LEFT)
607 Button(dispose, text='Quit', fg='blue',
608 command=master.nosave).pack(side=LEFT)
609 Button(dispose, text='Help', fg='blue',
610 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
615 # Common methods for Tkinter widgets -- deals with Tkinter declaration
616 def post(self, widgetclass, field):
617 for x in widgetclass.typemap:
618 if x[1] == 'Boolean':
619 setattr(self, x[0], BooleanVar(self))
620 elif x[1] == 'String':
621 setattr(self, x[0], StringVar(self))
623 setattr(self, x[0], IntVar(self))
624 source = getattr(getattr(self, field), x[0])
626 getattr(self, x[0]).set(source)
628 def fetch(self, widgetclass, field):
629 for x in widgetclass.typemap:
630 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
633 # First, code to set the global fetchmail run controls.
636 configure_novice_help = {
637 'title' : 'Fetchmail novice configurator help',
638 'banner': 'Novice configurator help',
640 In the `Novice Configurator Controls' panel, you can:
642 Press `Save' to save the new fetchmail configuration you have created.
643 Press `Quit' to exit without saving.
644 Press `Help' to bring up this help message.
646 In the `Novice Configuration' panels, you will set up the basic data
647 needed to create a simple fetchmail setup. These include:
649 1. The name of the remote site you want to query.
651 2. Your login name on that site.
653 3. Your password on that site.
655 4. A protocol to use (POP, IMAP, ETRN, etc.)
657 5. A polling interval.
659 6. Options to fetch old messages as well as new, uor to suppress
660 deletion of fetched message.
662 The novice-configuration code will assume that you want to forward mail
663 to a local sendmail listener with no special options.
666 configure_expert_help = {
667 'title' : 'Fetchmail expert configurator help',
668 'banner': 'Expert configurator help',
670 In the `Expert Configurator Controls' panel, you can:
672 Press `Save' to save the new fetchmail configuration you have edited.
673 Press `Quit' to exit without saving.
674 Press `Help' to bring up this help message.
676 In the `Run Controls' panel, you can set the following options that
677 control how fetchmail runs:
680 Number of seconds to wait between polls in the background.
681 If zero, fetchmail will run in foreground.
684 If empty, emit progress and error messages to stderr.
685 Otherwise this gives the name of the files to write to.
686 This field is ignored if the "Log to syslog?" option is on.
689 If empty, store seen-message IDs in .fetchids under user's home
690 directory. If nonempty, use given file name.
693 Who to send multidrop mail to as a last resort if no address can
694 be matched. Normally empty; in this case, fetchmail treats the
695 invoking user as the address of last resort unless that user is
696 root. If that user is root, fetchmail sends to `postmaster'.
699 If this option is on (the default) error mail goes to the sender.
700 Otherwise it goes to the postmaster.
703 If this option is on, spam bounces are sent to the sender or
704 postmaster (depending on the "Bounces to sender?" option. Otherwise,
705 spam bounces are not sent (the default).
708 If false (the default) fetchmail generates a Received line into
709 each message and generates a HELO from the machine it is running on.
710 If true, fetchmail generates no Received line and HELOs as if it were
713 In the `Remote Mail Configurations' panel, you can:
715 1. Enter the name of a new remote mail server you want fetchmail to query.
717 To do this, simply enter a label for the poll configuration in the
718 `New Server:' box. The label should be a DNS name of the server (unless
719 you are using ssh or some other tunneling method and will fill in the `via'
720 option on the site configuration screen).
722 2. Change the configuration of an existing site.
724 To do this, find the site's label in the listbox and double-click it.
725 This will take you to a site configuration dialogue.
729 class ConfigurationEdit(Frame, MyWidget):
730 def __init__(self, configuration, outfile, master, onexit):
732 self.configuration = configuration
733 self.outfile = outfile
734 self.container = master
736 ConfigurationEdit.mode_to_help = {
737 'novice':configure_novice_help, 'expert':configure_expert_help
740 def server_edit(self, sitename):
741 self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
743 def server_delete(self, sitename):
745 for user in self.subwidgets.keys():
747 del self.configuration[sitename]
751 def edit(self, mode):
753 Frame.__init__(self, self.container)
754 self.master.title('fetchmail ' + self.mode + ' configurator');
755 self.master.iconname('fetchmail ' + self.mode + ' configurator');
756 self.master.protocol('WM_DELETE_WINDOW', self.nosave)
757 self.keepalive = [] # Use this to anchor the PhotoImage object
758 make_icon_window(self, fetchmail_icon)
760 self.post(Configuration, 'configuration')
763 'Configurator ' + self.mode + ' Controls',
764 ConfigurationEdit.mode_to_help[self.mode],
767 gf = Frame(self, relief=RAISED, bd = 5)
769 text='Fetchmail Run Controls',
770 bd=2).pack(side=TOP, pady=10)
775 if self.mode != 'novice':
777 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
778 log.pack(side=RIGHT, anchor=E)
780 # Set the poll interval
781 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
782 de.pack(side=RIGHT, anchor=E)
787 if self.mode != 'novice':
790 {'text':'Bounces to sender?',
791 'variable':self.bouncemail,
792 'relief':GROOVE}).pack(side=LEFT, anchor=W)
797 {'text':'send spam bounces?',
798 'variable':self.spambounce,
799 'relief':GROOVE}).pack(side=LEFT, anchor=W)
804 {'text':'Log to syslog?',
805 'variable':self.syslog,
806 'relief':GROOVE}).pack(side=LEFT, anchor=W)
807 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
808 log.pack(side=RIGHT, anchor=E)
812 {'text':'Invisible mode?',
813 'variable':self.invisible,
814 'relief':GROOVE}).pack(side=LEFT, anchor=W)
816 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
817 log.pack(side=RIGHT, anchor=E)
821 # Expert mode allows us to edit multiple sites
822 lf = Frame(self, relief=RAISED, bd=5)
824 text='Remote Mail Server Configurations',
825 bd=2).pack(side=TOP, pady=10)
826 ListEdit('New Server:',
827 map(lambda x: x.pollname, self.configuration.servers),
828 lambda site, self=self: self.server_edit(site),
829 lambda site, self=self: self.server_delete(site),
834 for sitename in self.subwidgets.keys():
835 self.subwidgets[sitename].destruct()
836 self.master.destroy()
840 if ConfirmQuit(self, self.mode + " configuration editor"):
844 for sitename in self.subwidgets.keys():
845 self.subwidgets[sitename].save()
846 self.fetch(Configuration, 'configuration')
850 elif not os.path.isfile(self.outfile) or Dialog(self,
851 title = 'Overwrite existing run control file?',
852 text = 'Really overwrite existing run control file?',
854 strings = ('Yes', 'No'),
855 default = 1).num == 0:
857 os.rename(self.outfile, self.outfile + "~")
858 # Pre-1.5.2 compatibility...
861 fm = open(self.outfile, 'w')
863 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
864 fm.write(`self.configuration`)
868 os.chmod(self.outfile, 0600)
872 # Server editing stuff.
875 'title' : 'Remote site help',
876 'banner': 'Remote sites',
878 When you add a site name to the list here,
879 you initialize an entry telling fetchmail
880 how to poll a new site.
882 When you select a sitename (by double-
883 clicking it, or by single-clicking to
884 select and then clicking the Edit button),
885 you will open a window to configure that
890 'title' : 'Server options help',
891 'banner': 'Server Options',
893 The server options screen controls fetchmail
894 options that apply to one of your mailservers.
896 Once you have a mailserver configuration set
897 up as you like it, you can select `OK' to
898 store it in the server list maintained in
899 the main configuration window.
901 If you wish to discard changes to a server
902 configuration, select `Quit'.
906 'title' : 'Run Control help',
907 'banner': 'Run Controls',
909 If the `Poll normally' checkbox is on, the host is polled as part of
910 the normal operation of fetchmail when it is run with no arguments.
911 If it is off, fetchmail will only query this host when it is given as
912 a command-line argument.
914 The `True name of server' box should specify the actual DNS name
915 to query. By default this is the same as the poll name.
917 Normally each host described in the file is queried once each
918 poll cycle. If `Cycles to skip between polls' is greater than 0,
919 that's the number of poll cycles that are skipped between the
920 times this post is actually polled.
922 The `Server timeout' is the number of seconds fetchmail will wait
923 for a reply from the mailserver before concluding it is hung and
928 'title' : 'Protocol and Port help',
929 'banner': 'Protocol and Port',
931 These options control the remote-mail protocol
932 and TCP/IP service port used to query this
935 If you click the `Probe for supported protocols'
936 button, fetchmail will try to find you the most
937 capable server on the selected host (this will
938 only work if you're conncted to the Internet).
939 The probe only checks for ordinary IMAP and POP
940 protocols; fortunately these are the most
941 frequently supported.
943 The `Protocol' button bar offers you a choice of
944 all the different protocols available. The `auto'
945 protocol is the default mode; it probes the host
946 ports for POP3 and IMAP to see if either is
949 Normally the TCP/IP service port to use is
950 dictated by the protocol choice. The `Port'
951 field (only present in expert mode) lets you
952 set a non-standard port.
956 'title' : 'Security option help',
957 'banner': 'Security',
959 The `interface' option allows you to specify a range
960 of IP addresses to monitor for activity. If these
961 addresses are not active, fetchmail will not poll.
962 Specifying this may protect you from a spoofing attack
963 if your client machine has more than one IP gateway
964 address and some of the gateways are to insecure nets.
966 The `monitor' option, if given, specifies the only
967 device through which fetchmail is permitted to connect
968 to servers. This option may be used to prevent
969 fetchmail from triggering an expensive dial-out if the
970 interface is not already active.
972 The `interface' and `monitor' options are available
973 only for Linux and freeBSD systems. See the fetchmail
974 manual page for details on these.
976 The ssl option enables SSL communication with a mailserver
977 supporting Secure Sockets Layer. The sslkey and sslcert options
978 declare key and certificate files for use with SSL.
979 The sslcertck option enables strict checking of SSL server
980 certificates (and sslcertpath gives trusted certificate
981 directory). With sslfingerprint, you can specify a finger-
982 print the server's key is checked against.
984 The `netsec' option will be configurable only if fetchmail
985 was compiled with IPV6 support. If you need to use it,
986 you probably know what to do.
990 'title' : 'Multidrop option help',
991 'banner': 'Multidrop',
993 These options are only useful with multidrop mode.
994 See the manual page for extended discussion.
998 'title' : 'User list help',
999 'banner': 'User list',
1001 When you add a user name to the list here,
1002 you initialize an entry telling fetchmail
1003 to poll the site on behalf of the new user.
1005 When you select a username (by double-
1006 clicking it, or by single-clicking to
1007 select and then clicking the Edit button),
1008 you will open a window to configure the
1009 user's options on that site.
1012 class ServerEdit(Frame, MyWidget):
1013 def __init__(self, host, parent):
1014 self.parent = parent
1016 self.subwidgets = {}
1017 for site in parent.configuration.servers:
1018 if site.pollname == host:
1020 if (self.server == None):
1021 self.server = Server()
1022 self.server.pollname = host
1023 self.server.via = None
1024 parent.configuration.servers.append(self.server)
1026 def edit(self, mode, master=None):
1027 Frame.__init__(self, master)
1029 self.master.title('Fetchmail host ' + self.server.pollname);
1030 self.master.iconname('Fetchmail host ' + self.server.pollname);
1031 self.post(Server, 'server')
1032 self.makeWidgets(self.server.pollname, mode)
1033 self.keepalive = [] # Use this to anchor the PhotoImage object
1034 make_icon_window(self, fetchmail_icon)
1037 # self.wait_window()
1041 for username in self.subwidgets.keys():
1042 self.subwidgets[username].destruct()
1043 del self.parent.subwidgets[self.server.pollname]
1044 self.master.destroy()
1047 if ConfirmQuit(self, 'server option editing'):
1051 self.fetch(Server, 'server')
1052 for username in self.subwidgets.keys():
1053 self.subwidgets[username].save()
1056 def defaultPort(self):
1057 proto = self.protocol.get()
1058 # Callback to reset the port number whenever the protocol type changes.
1059 # We used to only reset the port if it had a default (zero) value.
1060 # This turns out to be a bad idea especially in Novice mode -- if
1061 # you set POP3 and then set IMAP, the port invisibly remained 110.
1062 # Now we reset unconditionally on the theory that if you're setting
1063 # a custom port number you should be in expert mode and playing
1064 # close enough attention to notice this...
1065 self.port.set(defaultports[proto])
1066 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
1068 def user_edit(self, username, mode):
1069 self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
1071 def user_delete(self, username):
1072 if self.subwidgets.has_key(username):
1073 self.subwidgets[username].destruct()
1074 del self.server[username]
1076 def makeWidgets(self, host, mode):
1077 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
1079 leftwin = Frame(self);
1082 if mode != 'novice':
1083 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
1084 Label(ctlwin, text="Run Controls").pack(side=TOP)
1085 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
1086 LabeledEntry(ctlwin, 'True name of ' + host + ':',
1087 self.via, leftwidth).pack(side=TOP, fill=X)
1088 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
1089 self.interval, leftwidth).pack(side=TOP, fill=X)
1090 LabeledEntry(ctlwin, 'Server timeout (seconds):',
1091 self.timeout, leftwidth).pack(side=TOP, fill=X)
1092 Button(ctlwin, text='Help', fg='blue',
1093 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
1096 # Compute the available protocols from the compile-time options
1097 protolist = ['auto']
1098 if 'pop2' in feature_options:
1099 protolist.append("POP2")
1100 if 'pop3' in feature_options:
1101 protolist = protolist + ["POP3", "APOP", "KPOP"]
1102 if 'sdps' in feature_options:
1103 protolist.append("SDPS")
1104 if 'imap' in feature_options:
1105 protolist.append("IMAP")
1106 if 'etrn' in feature_options:
1107 protolist.append("ETRN")
1108 if 'odmr' in feature_options:
1109 protolist.append("ODMR")
1111 protwin = Frame(leftwin, relief=RAISED, bd=5)
1112 Label(protwin, text="Protocol").pack(side=TOP)
1113 ButtonBar(protwin, '',
1114 self.protocol, protolist, 2,
1116 if mode != 'novice':
1117 LabeledEntry(protwin, 'On server TCP/IP port:',
1118 self.port, leftwidth).pack(side=TOP, fill=X)
1120 Checkbutton(protwin,
1121 text="POP3: track `seen' with client-side UIDLs?",
1122 variable=self.uidl).pack(side=TOP)
1123 Button(protwin, text='Probe for supported protocols', fg='blue',
1124 command=self.autoprobe).pack(side=LEFT)
1125 Button(protwin, text='Help', fg='blue',
1126 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1127 protwin.pack(fill=X)
1129 userwin = Frame(leftwin, relief=RAISED, bd=5)
1130 Label(userwin, text="User entries for " + host).pack(side=TOP)
1131 ListEdit("New user: ",
1132 map(lambda x: x.remote, self.server.users),
1133 lambda u, m=mode, s=self: s.user_edit(u, m),
1134 lambda u, s=self: s.user_delete(u),
1136 userwin.pack(fill=X)
1138 leftwin.pack(side=LEFT, anchor=N, fill=X);
1140 if mode != 'novice':
1141 rightwin = Frame(self);
1143 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1144 Label(mdropwin, text="Multidrop options").pack(side=TOP)
1145 LabeledEntry(mdropwin, 'Envelope address header:',
1146 self.envelope, '22').pack(side=TOP, fill=X)
1147 LabeledEntry(mdropwin, 'Envelope headers to skip:',
1148 self.envskip, '22').pack(side=TOP, fill=X)
1149 LabeledEntry(mdropwin, 'Name prefix to strip:',
1150 self.qvirtual, '22').pack(side=TOP, fill=X)
1151 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1152 variable=self.dns).pack(side=TOP)
1153 Label(mdropwin, text="DNS aliases").pack(side=TOP)
1154 ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1155 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1156 ListEdit("New domain: ",
1157 self.server.localdomains, None, None, mdropwin, multihelp)
1158 mdropwin.pack(fill=X)
1160 if os_type in ('linux', 'freebsd') or 'netsec' in feature_options:
1161 secwin = Frame(rightwin, relief=RAISED, bd=5)
1162 Label(secwin, text="Security").pack(side=TOP)
1163 # Don't actually let users set this. KPOP sets it implicitly
1164 # ButtonBar(secwin, 'Authorization mode:',
1165 # self.auth, authlist, 1, None).pack(side=TOP)
1166 if os_type == 'linux' or os_type == 'freebsd' or 'interface' in dictmembers:
1167 LabeledEntry(secwin, 'IP range to check before poll:',
1168 self.interface, leftwidth).pack(side=TOP, fill=X)
1169 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1170 LabeledEntry(secwin, 'Interface to monitor:',
1171 self.monitor, leftwidth).pack(side=TOP, fill=X)
1172 if 'netsec' in feature_options or 'netsec' in dictmembers:
1173 LabeledEntry(secwin, 'IPV6 security options:',
1174 self.netsec, leftwidth).pack(side=TOP, fill=X)
1175 # Someday this should handle Kerberos 5 too
1176 if 'kerberos' in feature_options:
1177 LabeledEntry(secwin, 'Principal:',
1178 self.principal, '12').pack(side=TOP, fill=X)
1179 # ESMTP authentication
1180 LabeledEntry(secwin, 'ESMTP name:',
1181 self.esmtpname, '12').pack(side=TOP, fill=X)
1182 LabeledEntry(secwin, 'ESMTP password:',
1183 self.esmtppassword, '12').pack(side=TOP, fill=X)
1184 Button(secwin, text='Help', fg='blue',
1185 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1188 rightwin.pack(side=LEFT, anchor=N);
1190 def autoprobe(self):
1191 # Note: this only handles case (1) near fetchmail.c:1032
1192 # We're assuming people smart enough to set up ssh tunneling
1193 # won't need autoprobing.
1195 realhost = self.server.via
1197 realhost = self.server.pollname
1199 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1200 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1202 sock.connect((realhost, port))
1203 greetline = sock.recv(1024)
1209 confwin = Toplevel()
1210 if greetline == None:
1211 title = "Autoprobe of " + realhost + " failed"
1213 Fetchmailconf didn't find any mailservers active.
1214 This could mean the host doesn't support any,
1215 or that your Internet connection is down, or
1216 that the host is so slow that the probe timed
1217 out before getting a response.
1221 # OK, now try to recognize potential problems
1223 if protocol == "POP2":
1224 warnings = warnings + """
1225 It appears you have somehow found a mailserver running only POP2.
1226 Congratulations. Have you considered a career in archaeology?
1228 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1229 Unless the first line of your fetchmail -V output includes the string "POP2",
1230 you'll have to build it from sources yourself with the configure
1231 switch --enable-POP2.
1235 ### POP3 servers start here
1237 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1238 warnings = warnings + """
1239 This appears to be an old version of the UC Davis POP server. These are
1240 dangerously unreliable (among other problems, they may drop your mailbox
1241 on the floor if your connection is interrupted during the session).
1243 It is strongly recommended that you find a better POP3 server. The fetchmail
1244 FAQ includes pointers to good ones.
1247 if string.find(greetline, "comcast.net") > 0:
1248 warnings = warnings + """
1249 The Comcast Maillennium POP3 server only returns the first 80K of a long
1250 message retrieved with TOP. Its response to RETR is normal, so use the
1254 # Steve VanDevender <stevev@efn.org> writes:
1255 # The only system I have seen this happen with is cucipop-1.31
1256 # under SunOS 4.1.4. cucipop-1.31 runs fine on at least Solaris
1257 # 2.x and probably quite a few other systems. It appears to be a
1258 # bug or bad interaction with the SunOS realloc() -- it turns out
1259 # that internally cucipop does allocate a certain data structure in
1260 # multiples of 16, using realloc() to bump it up to the next
1261 # multiple if it needs more.
1263 # The distinctive symptom is that when there are 16 messages in the
1264 # inbox, you can RETR and DELE all 16 messages successfully, but on
1265 # QUIT cucipop returns something like "-ERR Error locking your
1266 # mailbox" and aborts without updating it.
1268 # The cucipop banner looks like:
1270 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1272 if string.find(greetline, "Cubic Circle") > 0:
1273 warnings = warnings + """
1274 I see your server is running cucipop. Better make sure the server box
1275 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1276 under that version, and doesn't cope with the result gracefully. Newer
1277 SunOS and Solaris machines run cucipop OK.
1279 Also, some versions of cucipop don't assert an exclusive lock on your
1280 mailbox when it's being queried. This means that if you have more than
1281 one fetchmail query running against the same mailbox, bad things can happen.
1283 if string.find(greetline, "David POP3 Server") > 0:
1284 warnings = warnings + """
1285 This POP3 server is badly broken. You should get rid of it -- and the
1286 brain-dead Microsoft operating system it rode in on.
1289 # The greeting line on the server known to be buggy is:
1290 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1292 if string.find(greetline, "FTGate") > 0:
1293 warnings = warnings + """
1294 This POP server has a weird bug; it says OK twice in response to TOP.
1295 Its response to RETR is normal, so use the `fetchall' option.
1298 if string.find(greetline, " geonet.de") > 0:
1299 warnings = warnings + """
1300 You appear to be using geonet. As of late 2002, the TOP command on
1301 geonet's POP3 is broken. Use the fetchall option.
1304 if string.find(greetline, "OpenMail") > 0:
1305 warnings = warnings + """
1306 You appear to be using some version of HP OpenMail. Many versions of
1307 OpenMail do not process the "TOP" command correctly; the symptom is that
1308 only the header and first line of each message is retrieved. To work
1309 around this bug, turn on `fetchall' on all user entries associated with
1313 if string.find(greetline, "Escape character is") > 0:
1314 warnings = warnings + """
1315 Your greeting line looks like it was written by a fetid pile of
1316 camel dung identified to me as `popa3d written by Solar Designer'.
1317 Beware! The UIDL support in this thing is known to be completely broken,
1318 and other things probably are too.
1321 if string.find(greetline, "MercuryP/NLM v1.48") > 0:
1322 warnings = warnings + """
1323 This is not a POP3 server. It has delusions of being one, but after
1324 RETR all messages are automatically marked to be deleted. The only
1325 way to prevent this is to issue an RSET before leaving the server.
1326 Fetchmail does this, but we suspect this is probably broken in lots
1330 if string.find(greetline, "POP-Max") > 0:
1331 warnings = warnings + """
1332 The Mail Max POP3 server screws up on mail with attachments. It
1333 reports the message size with attachments included, but doesn't
1334 download them on a RETR or TOP (this violates the IMAP RFCs). It also
1335 doesn't implement TOP correctly. You should get rid of it -- and the
1336 brain-dead NT server it rode in on.
1339 if string.find(greetline, "POP3 Server Ready") > 0:
1340 warnings = warnings + """
1341 Some server that uses this greeting line has been observed to choke on
1342 TOP %d 99999999. Use the fetchall option. if necessary, to force RETR.
1345 if string.find(greetline, "QPOP") > 0:
1346 warnings = warnings + """
1347 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1348 knows all about qpopper. However, be aware that the 2.53 version of
1349 qpopper does something odd that causes fetchmail to hang with a socket
1350 error on very large messages. This is probably not a fetchmail bug, as
1351 it has been observed with fetchpop. The fix is to upgrade to qpopper
1352 3.0beta or a more recent version. Better yet, switch to IMAP.
1355 if string.find(greetline, " sprynet.com") > 0:
1356 warnings = warnings + """
1357 You appear to be using a SpryNet server. In mid-1999 it was reported that
1358 the SpryNet TOP command marks messages seen. Therefore, for proper error
1359 recovery in the event of a line drop, it is strongly recommended that you
1360 turn on `fetchall' on all user entries associated with this server.
1363 if string.find(greetline, "TEMS POP3") > 0:
1364 warnings = warnings + """
1365 Your POP3 server has "TEMS" in its header line. At least one such
1366 server does not process the "TOP" command correctly; the symptom is
1367 that fetchmail hangs when trying to retrieve mail. To work around
1368 this bug, turn on `fetchall' on all user entries associated with this
1372 if string.find(greetline, " spray.se") > 0:
1373 warnings = warnings + """
1374 Your POP3 server has "spray.se" in its header line. In May 2000 at
1375 least one such server did not process the "TOP" command correctly; the
1376 symptom is that messages are treated as headerless. To work around
1377 this bug, turn on `fetchall' on all user entries associated with this
1381 if string.find(greetline, " usa.net") > 0:
1382 warnings = warnings + """
1383 You appear to be using USA.NET's free mail service. Their POP3 servers
1384 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1385 fetchmail can compensate. They seem to require that fetchall be switched on
1386 (otherwise you won't necessarily see all your mail, not even new mail).
1387 They also botch the TOP command the fetchmail normally uses for retrieval
1388 (it only retrieves about 10 lines rather than the number specified).
1389 Turning on fetchall will disable the use of TOP.
1391 Therefore, it is strongly recommended that you turn on `fetchall' on all
1392 user entries associated with this server.
1395 if string.find(greetline, " Novonyx POP3") > 0:
1396 warnings = warnings + """
1397 Your mailserver is running Novonyx POP3. This server, at least as of
1398 version 2.17, seems to have problems handling and reporting seen bits.
1399 You may have to use the fetchall option.
1402 if string.find(greetline, " IMS POP3") > 0:
1403 warnings = warnings + """
1404 Some servers issuing the greeting line 'IMS POP3' have been known to
1405 do byte-stuffing incorrectly. This means that if a message you receive
1406 has a . (period) at start of line, fetchmail will become confused and
1407 probably wedge itself. (This bug was recorded on IMS POP3 0.86.)
1411 ### IMAP servers start here
1413 if string.find(greetline, "GroupWise") > 0:
1414 warnings = warnings + """
1415 The Novell GroupWise IMAP server would be better named GroupFoolish;
1416 it is (according to the designer of IMAP) unusably broken. Among
1417 other things, it doesn't include a required content length in its
1418 BODY[TEXT] response.<p>
1420 Fetchmail works around this problem, but we strongly recommend voting
1421 with your dollars for a server that isn't brain-dead. If you stick
1422 with code as shoddy as GroupWise seems to be, you will probably pay
1423 for it with other problems.<p>
1426 if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1427 warnings = warnings + """
1428 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1429 set the messages's Seen flag. As a result, if you use the keep option the
1430 same messages will be downloaded over and over.
1433 if string.find(greetline, "InterChange") > 0:
1434 warnings = warnings + """
1436 The InterChange IMAP server at release levels below 3.61.08 screws up
1437 on mail with attachments. It doesn't fetch them if you give it a
1438 BODY[TEXT] request, though it does if you request RFC822.TEXT.
1439 According to the IMAP RFCs and their maintainer these should be
1440 equivalent -- and we can't drop the BODY[TEXT] form because M$
1441 Exchange (quite legally under RFC2062) rejectsit. The InterChange
1442 folks claim to have fixed this bug in 3.61.08.
1445 if string.find(greetline, "Imail") > 0:
1446 warnings = warnings + """
1447 We've seen a bug report indicating that this IMAP server (at least as of
1448 version 5.0.7) returns an invalid body size for messages with MIME
1449 attachments; the effect is to drop the attachments on the floor. We
1450 recommend you upgrade to a non-broken IMAP server.
1453 if string.find(greetline, "Domino IMAP4") > 0:
1454 warnings = warnings + """
1455 Your IMAP server appears to be Lotus Domino. This server, at least up
1456 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1457 the details in the fetchmail FAQ). As a result, even MIME aware MUAs
1458 will see attachments as part of the message text. If your Domino server's
1459 POP3 facility is enabled, we recommend you fall back on it.
1463 ### Checks for protocol variants start here
1465 closebrak = string.find(greetline, ">")
1466 if closebrak > 0 and greetline[closebrak+1] == "\r":
1467 warnings = warnings + """
1468 It looks like you could use APOP on this server and avoid sending it your
1469 password in clear. You should talk to the mailserver administrator about
1473 if string.find(greetline, "IMAP2bis") > 0:
1474 warnings = warnings + """
1475 IMAP2bis servers have a minor problem; they can't peek at messages without
1476 marking them seen. If you take a line hit during the retrieval, the
1477 interrupted message may get left on the server, marked seen.
1479 To work around this, it is recommended that you set the `fetchall'
1480 option on all user entries associated with this server, so any stuck
1481 mail will be retrieved next time around.
1483 To fix this bug, upgrade to an IMAP4 server. The fetchmail FAQ includes
1484 a pointer to an open-source implementation.
1487 if string.find(greetline, "IMAP4rev1") > 0:
1488 warnings = warnings + """
1489 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1490 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1491 has therefore been extremely well tested with this class of server.
1495 warnings = warnings + """
1496 Fetchmail doesn't know anything special about this server type.
1500 # Display success window with warnings
1501 title = "Autoprobe of " + realhost + " succeeded"
1502 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1503 self.protocol.set(protocol)
1504 confwin.title(title)
1505 confwin.iconname(title)
1506 Label(confwin, text=title).pack()
1507 Message(confwin, text=confirm, width=600).pack()
1508 Button(confwin, text='Done',
1509 command=lambda x=confwin: x.destroy(), bd=2).pack()
1512 # User editing stuff
1516 'title' : 'User option help',
1517 'banner': 'User options',
1519 You may use this panel to set options
1520 that may differ between individual
1523 Once you have a user configuration set
1524 up as you like it, you can select `OK' to
1525 store it in the user list maintained in
1526 the site configuration window.
1528 If you wish to discard the changes you have
1529 made to user options, select `Quit'.
1533 'title' : 'Local name help',
1534 'banner': 'Local names',
1536 The local name(s) in a user entry are the
1537 people on the client machine who should
1538 receive mail from the poll described.
1540 Note: if a user entry has more than one
1541 local name, messages will be retrieved
1542 in multidrop mode. This complicates
1543 the configuration issues; see the manual
1544 page section on multidrop mode.
1546 Warning: Be careful with local names
1547 such as foo@bar.com, as that can cause
1548 the mail to be sent to foo@bar.com instead
1549 of sending it to your local system.
1552 class UserEdit(Frame, MyWidget):
1553 def __init__(self, username, parent):
1554 self.parent = parent
1556 for user in parent.server.users:
1557 if user.remote == username:
1559 if self.user == None:
1561 self.user.remote = username
1562 self.user.localnames = [username]
1563 parent.server.users.append(self.user)
1565 def edit(self, mode, master=None):
1566 Frame.__init__(self, master)
1568 self.master.title('Fetchmail user ' + self.user.remote
1569 + ' querying ' + self.parent.server.pollname);
1570 self.master.iconname('Fetchmail user ' + self.user.remote);
1571 self.post(User, 'user')
1572 self.makeWidgets(mode, self.parent.server.pollname)
1573 self.keepalive = [] # Use this to anchor the PhotoImage object
1574 make_icon_window(self, fetchmail_icon)
1577 # self.wait_window()
1581 # Yes, this test can fail -- if you delete the parent window.
1582 if self.parent.subwidgets.has_key(self.user.remote):
1583 del self.parent.subwidgets[self.user.remote]
1584 self.master.destroy()
1587 if ConfirmQuit(self, 'user option editing'):
1592 for x in self.user.localnames: ok = ok + (string.find(x, '@') != -1)
1593 if ok == 0 or Dialog(self,
1594 title = "Really accept an embedded '@' ?",
1595 text = "Local names with an embedded '@', such as in foo@bar "
1596 "might result in your mail being sent to foo@bar.com "
1597 "instead of your local system.\n Are you sure you want "
1598 "a local user name with an '@' in it?",
1599 bitmap = 'question',
1600 strings = ('Yes', 'No'),
1601 default = 1).num == 0:
1602 self.fetch(User, 'user')
1605 def makeWidgets(self, mode, servername):
1606 dispose_window(self,
1607 "User options for " + self.user.remote + " querying " + servername,
1610 if mode != 'novice':
1611 leftwin = Frame(self);
1615 secwin = Frame(leftwin, relief=RAISED, bd=5)
1616 Label(secwin, text="Authentication").pack(side=TOP)
1617 LabeledEntry(secwin, 'Password:',
1618 self.password, '12').pack(side=TOP, fill=X)
1619 secwin.pack(fill=X, anchor=N)
1621 if 'ssl' in feature_options or 'ssl' in dictmembers:
1622 sslwin = Frame(leftwin, relief=RAISED, bd=5)
1623 Checkbutton(sslwin, text="Use SSL?",
1624 variable=self.ssl).pack(side=TOP, fill=X)
1625 LabeledEntry(sslwin, 'SSL key:',
1626 self.sslkey, '14').pack(side=TOP, fill=X)
1627 LabeledEntry(sslwin, 'SSL certificate:',
1628 self.sslcert, '14').pack(side=TOP, fill=X)
1629 Checkbutton(sslwin, text="Check server SSL certificate?",
1630 variable=self.sslcertck).pack(side=TOP, fill=X)
1631 LabeledEntry(sslwin, 'SSL trusted certificate directory:',
1632 self.sslcertpath, '14').pack(side=TOP, fill=X)
1633 LabeledEntry(sslwin, 'SSL key fingerprint:',
1634 self.sslfingerprint, '14').pack(side=TOP, fill=X)
1635 sslwin.pack(fill=X, anchor=N)
1637 names = Frame(leftwin, relief=RAISED, bd=5)
1638 Label(names, text="Local names").pack(side=TOP)
1639 ListEdit("New name: ",
1640 self.user.localnames, None, None, names, localhelp)
1641 names.pack(fill=X, anchor=N)
1643 if mode != 'novice':
1644 targwin = Frame(leftwin, relief=RAISED, bd=5)
1645 Label(targwin, text="Forwarding Options").pack(side=TOP)
1646 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1647 ListEdit("New listener:",
1648 self.user.smtphunt, None, None, targwin, None)
1649 Label(targwin, text="Domains to fetch from (ODMR/ETRN only)").pack(side=TOP)
1650 ListEdit("Domains:",
1651 self.user.fetchdomains, None, None, targwin, None)
1652 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1653 self.smtpaddress, '26').pack(side=TOP, fill=X)
1654 LabeledEntry(targwin, 'Set RCPT To address:',
1655 self.smtpname, '26').pack(side=TOP, fill=X)
1656 LabeledEntry(targwin, 'Connection setup command:',
1657 self.preconnect, '26').pack(side=TOP, fill=X)
1658 LabeledEntry(targwin, 'Connection wrapup command:',
1659 self.postconnect, '26').pack(side=TOP, fill=X)
1660 LabeledEntry(targwin, 'Local delivery agent:',
1661 self.mda, '26').pack(side=TOP, fill=X)
1662 LabeledEntry(targwin, 'BSMTP output file:',
1663 self.bsmtp, '26').pack(side=TOP, fill=X)
1664 LabeledEntry(targwin, 'Listener spam-block codes:',
1665 self.antispam, '26').pack(side=TOP, fill=X)
1666 LabeledEntry(targwin, 'Pass-through properties:',
1667 self.properties, '26').pack(side=TOP, fill=X)
1668 Checkbutton(targwin, text="Use LMTP?",
1669 variable=self.lmtp).pack(side=TOP, fill=X)
1670 targwin.pack(fill=X, anchor=N)
1672 if mode != 'novice':
1673 leftwin.pack(side=LEFT, fill=X, anchor=N)
1674 rightwin = Frame(self)
1678 optwin = Frame(rightwin, relief=RAISED, bd=5)
1679 Label(optwin, text="Processing Options").pack(side=TOP)
1680 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1681 variable=self.keep).pack(side=TOP, anchor=W)
1682 Checkbutton(optwin, text="Fetch old messages as well as new",
1683 variable=self.fetchall).pack(side=TOP, anchor=W)
1684 if mode != 'novice':
1685 Checkbutton(optwin, text="Flush seen messages before retrieval",
1686 variable=self.flush).pack(side=TOP, anchor=W)
1687 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1688 variable=self.rewrite).pack(side=TOP, anchor=W)
1689 Checkbutton(optwin, text="Force CR/LF at end of each line",
1690 variable=self.forcecr).pack(side=TOP, anchor=W)
1691 Checkbutton(optwin, text="Strip CR from end of each line",
1692 variable=self.stripcr).pack(side=TOP, anchor=W)
1693 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1694 variable=self.pass8bits).pack(side=TOP, anchor=W)
1695 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1696 variable=self.mimedecode).pack(side=TOP, anchor=W)
1697 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1698 variable=self.dropstatus).pack(side=TOP, anchor=W)
1699 Checkbutton(optwin, text="Drop Delivered-To lines from forwarded messages",
1700 variable=self.dropdelivered).pack(side=TOP, anchor=W)
1703 if mode != 'novice':
1704 limwin = Frame(rightwin, relief=RAISED, bd=5)
1705 Label(limwin, text="Resource Limits").pack(side=TOP)
1706 LabeledEntry(limwin, 'Message size limit:',
1707 self.limit, '30').pack(side=TOP, fill=X)
1708 LabeledEntry(limwin, 'Size warning interval:',
1709 self.warnings, '30').pack(side=TOP, fill=X)
1710 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1711 self.fetchlimit, '30').pack(side=TOP, fill=X)
1712 LabeledEntry(limwin, 'Max message sizes to fetch per transaction:',
1713 self.fetchsizelimit, '30').pack(side=TOP, fill=X)
1714 if self.parent.server.protocol not in ('ETRN', 'ODMR'):
1715 LabeledEntry(limwin, 'Use fast UIDL:',
1716 self.fastuidl, '30').pack(side=TOP, fill=X)
1717 LabeledEntry(limwin, 'Max messages to forward per poll:',
1718 self.batchlimit, '30').pack(side=TOP, fill=X)
1719 if self.parent.server.protocol not in ('ETRN', 'ODMR'):
1720 LabeledEntry(limwin, 'Interval between expunges:',
1721 self.expunge, '30').pack(side=TOP, fill=X)
1722 Checkbutton(limwin, text="Idle after each poll (IMAP only)",
1723 variable=self.idle).pack(side=TOP, anchor=W)
1726 if self.parent.server.protocol == 'IMAP':
1727 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1728 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1729 ListEdit("New folder:", self.user.mailboxes,
1730 None, None, foldwin, None)
1731 foldwin.pack(fill=X, anchor=N)
1733 if mode != 'novice':
1734 rightwin.pack(side=LEFT)
1740 # Top-level window that offers either novice or expert mode
1741 # (but not both at once; it disappears when one is selected).
1744 class Configurator(Frame):
1745 def __init__(self, outfile, master, onexit, parent):
1746 Frame.__init__(self, master)
1747 self.outfile = outfile
1748 self.onexit = onexit
1749 self.parent = parent
1750 self.master.title('fetchmail configurator');
1751 self.master.iconname('fetchmail configurator');
1753 self.keepalive = [] # Use this to anchor the PhotoImage object
1754 make_icon_window(self, fetchmail_icon)
1756 Message(self, text="""
1757 Use `Novice Configuration' for basic fetchmail setup;
1758 with this, you can easily set up a single-drop connection
1759 to one remote mail server.
1760 """, width=600).pack(side=TOP)
1761 Button(self, text='Novice Configuration',
1762 fg='blue', command=self.novice).pack()
1764 Message(self, text="""
1765 Use `Expert Configuration' for advanced fetchmail setup,
1766 including multiple-site or multidrop connections.
1767 """, width=600).pack(side=TOP)
1768 Button(self, text='Expert Configuration',
1769 fg='blue', command=self.expert).pack()
1771 Message(self, text="""
1772 Or you can just select `Quit' to leave the configurator now and
1773 return to the main panel.
1774 """, width=600).pack(side=TOP)
1775 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1776 master.protocol("WM_DELETE_WINDOW", self.leave)
1779 self.master.destroy()
1780 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1783 self.master.destroy()
1784 ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1787 self.master.destroy()
1790 # Run a command in a scrolling text widget, displaying its output
1792 class RunWindow(Frame):
1793 def __init__(self, command, master, parent):
1794 Frame.__init__(self, master)
1795 self.master = master
1796 self.master.title('fetchmail run window');
1797 self.master.iconname('fetchmail run window');
1800 text="Running "+command,
1801 bd=2).pack(side=TOP, pady=10)
1802 self.keepalive = [] # Use this to anchor the PhotoImage object
1803 make_icon_window(self, fetchmail_icon)
1805 # This is a scrolling text window
1806 textframe = Frame(self)
1807 scroll = Scrollbar(textframe)
1808 self.textwidget = Text(textframe, setgrid=TRUE)
1809 textframe.pack(side=TOP, expand=YES, fill=BOTH)
1810 self.textwidget.config(yscrollcommand=scroll.set)
1811 self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1812 scroll.config(command=self.textwidget.yview)
1813 scroll.pack(side=RIGHT, fill=BOTH)
1814 textframe.pack(side=TOP)
1816 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1818 self.update() # Draw widget before executing fetchmail
1820 # Always look for a runnable command in the directory we're running in
1821 # first. This avoids some obscure version-skew errors that can occur
1822 # if you pick up an old fetchmail from the standard system locations.
1823 os.environ["PATH"] = os.path.dirname(sys.argv[0]) + ":" + os.environ["PATH"]
1824 child_stdout = os.popen(command + " 2>&1", "r")
1826 ch = child_stdout.read(1)
1829 self.textwidget.insert(END, ch)
1830 self.textwidget.insert(END, "Done.")
1831 self.textwidget.see(END);
1834 self.master.destroy()
1836 # Here's where we choose either configuration or launching
1838 class MainWindow(Frame):
1839 def __init__(self, outfile, master=None):
1840 Frame.__init__(self, master)
1841 self.outfile = outfile
1842 self.master.title('fetchmail launcher');
1843 self.master.iconname('fetchmail launcher');
1846 text='Fetchmailconf ' + version,
1847 bd=2).pack(side=TOP, pady=10)
1848 self.keepalive = [] # Use this to anchor the PhotoImage object
1849 make_icon_window(self, fetchmail_icon)
1852 ## Test icon display with the following:
1853 # icon_image = PhotoImage(data=fetchmail_icon)
1854 # Label(self, image=icon_image).pack(side=TOP, pady=10)
1855 # self.keepalive.append(icon_image)
1857 Message(self, text="""
1858 Use `Configure fetchmail' to tell fetchmail about the remote
1859 servers it should poll (the host name, your username there,
1860 whether to use POP or IMAP, and so forth).
1861 """, width=600).pack(side=TOP)
1862 self.configbutton = Button(self, text='Configure fetchmail',
1863 fg='blue', command=self.configure)
1864 self.configbutton.pack()
1866 Message(self, text="""
1867 Use `Run fetchmail' to run fetchmail with debugging enabled.
1868 This is a good way to test out a new configuration.
1869 """, width=600).pack(side=TOP)
1870 Button(self, text='Run fetchmail',fg='blue', command=self.test).pack()
1872 Message(self, text="""
1873 Use `Run fetchmail' to run fetchmail in foreground.
1874 Progress messages will be shown, but not debug messages.
1875 """, width=600).pack(side=TOP)
1876 Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1878 Message(self, text="""
1879 Or you can just select `Quit' to exit the launcher now.
1880 """, width=600).pack(side=TOP)
1881 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1883 def configure(self):
1884 self.configbutton.configure(state=DISABLED)
1885 Configurator(self.outfile, Toplevel(),
1886 lambda self=self: self.configbutton.configure(state=NORMAL),
1889 RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1892 RunWindow("fetchmail -d0", Toplevel(), self)
1897 # Functions for turning a dictionary into an instantiated object tree.
1899 def intersect(list1, list2):
1900 # Compute set intersection of lists
1907 def setdiff(list1, list2):
1908 # Compute set difference of lists
1915 def copy_instance(toclass, fromdict):
1916 # Initialize a class object of given type from a conformant dictionary.
1917 for fld in fromdict.keys():
1918 if not fld in dictmembers:
1919 dictmembers.append(fld)
1920 # The `optional' fields are the ones we can ignore for purposes of
1921 # conformability checking; they'll still get copied if they are
1922 # present in the dictionary.
1923 optional = ('interface', 'monitor',
1924 'netsec', 'esmtpname', 'esmtppassword',
1925 'ssl', 'sslkey', 'sslcert', 'sslproto', 'sslcertck',
1926 'sslcertpath', 'sslfingerprint', 'showdots')
1927 class_sig = setdiff(toclass.__dict__.keys(), optional)
1929 dict_keys = setdiff(fromdict.keys(), optional)
1931 common = intersect(class_sig, dict_keys)
1932 if 'typemap' in class_sig:
1933 class_sig.remove('typemap')
1934 if tuple(class_sig) != tuple(dict_keys):
1935 print "Fields don't match what fetchmailconf expected:"
1936 # print "Class signature: " + `class_sig`
1937 # print "Dictionary keys: " + `dict_keys`
1938 diff = setdiff(class_sig, common)
1940 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1941 diff = setdiff(dict_keys, common)
1943 print "Not matched in dictionary keys: " + `diff`
1946 for x in fromdict.keys():
1947 setattr(toclass, x, fromdict[x])
1950 # And this is the main sequence. How it works:
1952 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1953 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1954 # Run execfile on the file to pull fetchmailrc into Python global space.
1955 # You don't want static data, though; you want, instead, a tree of objects
1956 # with the same data members and added appropriate methods.
1958 # This is what the copy_instance function() is for. It tries to copy a
1959 # dictionary field by field into a class, aborting if the class and dictionary
1960 # have different data members (except for any typemap member in the class;
1961 # that one is strictly for use by the MyWidget supperclass).
1963 # Once the object tree is set up, require user to choose novice or expert
1964 # mode and instantiate an edit object for the configuration. Class methods
1965 # will take it all from there.
1967 # Options (not documented because they're for fetchmailconf debuggers only):
1968 # -d: Read the configuration and dump it to stdout before editing. Dump
1969 # the edited result to stdout as well.
1970 # -f: specify the run control file to read.
1972 if __name__ == '__main__':
1974 if not os.environ.has_key("DISPLAY"):
1975 print "fetchmailconf must be run under X"
1978 fetchmail_icon = """
1979 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1980 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1981 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1982 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1983 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1984 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1985 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1986 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1987 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1988 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1989 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1990 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1991 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1992 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1993 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1994 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1995 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1996 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1997 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1998 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1999 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
2000 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
2001 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
2002 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
2003 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
2004 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
2005 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
2006 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
2007 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
2008 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
2009 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
2010 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
2012 # The base64 data in the string above was generated by the following procedure:
2015 # print base64.encodestring(open("fetchmail.gif", "rb").read())
2019 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
2020 dump = rcfile = None;
2021 for (switch, val) in options:
2022 if (switch == '-d'):
2024 elif (switch == '-f'):
2027 # Get client host's FQDN
2028 hostname = socket.gethostbyaddr(socket.gethostname())[0]
2031 ConfigurationDefaults = Configuration()
2032 ServerDefaults = Server()
2033 UserDefaults = User()
2035 # Read the existing configuration. We set the umask to 077 to make sure
2036 # that group & other read/write permissions are shut off -- we wouldn't
2037 # want crackers to snoop password information out of the tempfile.
2038 tmpfile = tempfile.mktemp()
2040 cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
2042 cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
2047 print "`" + cmd + "' run failure, status " + `s`
2050 print "Unknown error while running fetchmail --configdump"
2057 print "Can't read configuration output of fetchmail --configdump."
2063 # The tricky part -- initializing objects from the configuration global
2064 # `Configuration' is the top level of the object tree we're going to mung.
2065 # The dictmembers list is used to track the set of fields the dictionary
2066 # contains; in particular, we can use it to tell whether things like the
2067 # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
2069 Fetchmailrc = Configuration()
2070 copy_instance(Fetchmailrc, fetchmailrc)
2071 Fetchmailrc.servers = [];
2072 for server in fetchmailrc['servers']:
2074 copy_instance(Newsite, server)
2075 Fetchmailrc.servers.append(Newsite)
2077 for user in server['users']:
2079 copy_instance(Newuser, user)
2080 Newsite.users.append(Newuser)
2082 # We may want to display the configuration and quit
2084 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
2086 # The theory here is that -f alone sets the rcfile location,
2087 # but -d and -f together mean the new configuration should go to stdout.
2088 if not rcfile and not dump:
2089 rcfile = os.environ["HOME"] + "/.fetchmailrc"
2091 # OK, now run the configuration edit
2092 root = MainWindow(rcfile)
2095 # The following sets edit modes for GNU EMACS