]> Pileus Git - ~andy/fetchmail/blob - fetchmailconf
ef295ce68c4fdce7a08bfff03af5922497f7e9bd
[~andy/fetchmail] / fetchmailconf
1 #!/usr/bin/env python
2 #
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
7 version = "1.33"
8
9 from Tkinter import *
10 from Dialog import *
11 import sys, time, os, string, socket, getopt
12
13 #
14 # Define the data structures the GUIs will be tossing around
15 #
16 class Configuration:
17     def __init__(self):
18         self.poll_interval = 0          # Normally, run in foreground
19         self.logfile = None             # No logfile, initially
20         self.idfile = os.environ["HOME"] + "/.fetchids"         # Default idfile, initially
21         self.postmaster = None          # No last-resort address, initially
22         self.bouncemail = TRUE          # Bounce errors to users
23         self.properties = None          # No exiguous properties
24         self.invisible = FALSE          # Suppress Received line & spoof?
25         self.syslog = FALSE             # Use syslogd for logging?
26         self.servers = []               # List of included sites
27         Configuration.typemap = (
28             ('poll_interval',   'Int'),
29             ('logfile',         'String'),
30             ('idfile',          'String'),
31             ('postmaster',      'String'),
32             ('bouncemail',      'Boolean'),
33             ('properties',      'String'),
34             ('syslog',          'Boolean'),
35             ('invisible',       'Boolean'))
36
37     def __repr__(self):
38         str = "";
39         if self.syslog != ConfigurationDefaults.syslog:
40            str = str + ("set syslog\n")
41         elif self.logfile:
42             str = str + ("set logfile \"%s\"\n" % (self.logfile,));
43         if self.idfile != ConfigurationDefaults.idfile:
44             str = str + ("set idfile \"%s\"\n" % (self.idfile,));
45         if self.postmaster != ConfigurationDefaults.postmaster:
46             str = str + ("set postmaster \"%s\"\n" % (self.postmaster,));
47         if self.bouncemail:
48             str = str + ("set bouncemail\n")
49         else:
50             str = str + ("set nobouncemail\n")
51         if self.properties != ConfigurationDefaults.properties:
52             str = str + ("set properties \"%s\"\n" % (self.properties,));
53         if self.poll_interval > 0:
54             str = str + "set daemon " + `self.poll_interval` + "\n"
55         for site in self.servers:
56             str = str + repr(site)
57         return str
58
59     def __delitem__(self, name):
60         for si in range(len(self.servers)):
61             if self.servers[si].pollname == name:
62                 del self.servers[si]
63                 break
64
65     def __str__(self):
66         return "[Configuration: " + repr(self) + "]"
67
68 class Server:
69     def __init__(self):
70         self.pollname = None            # Poll label
71         self.via = None                 # True name of host
72         self.active = TRUE              # Poll status
73         self.interval = 0               # Skip interval
74         self.protocol = 'auto'          # Default to auto protocol
75         self.port = 0                   # Port number to use
76         self.uidl = FALSE               # Don't use RFC1725 UIDLs by default
77         self.preauth = 'password'       # Default to password authentication
78         self.timeout = 300              # 5-minute timeout
79         self.envelope = 'Received'      # Envelope-address header
80         self.envskip = 0                # Number of envelope headers to skip
81         self.qvirtual = None            # Name prefix to strip
82         self.aka = []                   # List of DNS aka names
83         self.dns = TRUE                 # Enable DNS lookup on multidrop
84         self.localdomains = []          # Domains to be considered local
85         self.interface = None           # IP address and range
86         self.monitor = None             # IP address and range
87         self.plugin = None              # Plugin command for going to server
88         self.plugout = None             # Plugin command for going to listener
89         self.netsec = None              # IPV6 security options
90         self.users = []                 # List of user entries for site
91         self.principal = None           # Kerberos principal
92         Server.typemap = (
93             ('pollname',  'String'),
94             ('via',       'String'),
95             ('active',    'Boolean'),
96             ('interval',  'Int'),
97             ('protocol',  'String'),
98             ('port',      'Int'),
99             ('uidl',      'Boolean'),
100             ('preauth',      'String'),
101             ('timeout',   'Int'),
102             ('envelope',  'String'),
103             ('envskip',   'Int'),
104             ('qvirtual',  'String'),
105             # leave aka out
106             ('dns',       'Boolean'),
107             # leave localdomains out
108             ('interface', 'String'),
109             ('monitor',   'String'),
110             ('plugin',   'String'),
111             ('plugout',  'String'),
112             ('netsec',   'String'),
113             ('principal',   'String'))
114
115     def dump(self, folded):
116         res = ""
117         if self.active:   res = res + "poll"
118         else:             res = res + "skip"
119         res = res + (" " + self.pollname)
120         if self.via:
121             res = res + (" via " + str(self.via) + "\n");
122         if self.protocol != ServerDefaults.protocol:
123             res = res + " with proto " + self.protocol 
124         if self.port != defaultports[self.protocol] and self.port != 0:
125             res = res + " port " + `self.port`
126         if self.timeout != ServerDefaults.timeout:
127             res = res + " timeout " + `self.timeout`
128         if self.interval != ServerDefaults.interval: 
129             res = res + " interval " + `self.interval`
130         if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
131             if self.envskip:
132                 res = res + " envelope " + `self.envskip` + " " + self.envelope
133             else:
134                 res = res + " envelope " + self.envelope
135         if self.qvirtual:
136             res = res + (" qvirtual " + str(self.qvirtual) + "\n");
137         if self.preauth != ServerDefaults.preauth:
138             res = res + " preauth " + self.preauth
139         if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
140             res = res + " and options"
141         if self.dns != ServerDefaults.dns:
142             res = res + flag2str(self.dns, 'dns')
143         if self.uidl != ServerDefaults.uidl:
144             res = res + flag2str(self.uidl, 'uidl')
145         if folded:        res = res + "\n    "
146         else:             res = res + " "
147
148         if self.aka:
149              res = res + "aka"
150              for x in self.aka:
151                 res = res + " " + x
152         if self.aka and self.localdomains: res = res + " "
153         if self.localdomains:
154              res = res + ("localdomains")
155              for x in self.localdomains:
156                 res = res + " " + x
157         if (self.aka or self.localdomains):
158             if folded:
159                 res = res + "\n    "
160             else:
161                 res = res + " "
162
163         if self.interface:
164             res = res + "interface " + str(self.interface)
165         if self.monitor:
166             res = res + "monitor " + str(self.monitor)
167         if self.netsec:
168             res = res + "netsec " + str(self.netsec)
169         if self.interface or self.monitor or self.netsec:
170             if folded:
171                 res = res + "\n"
172
173         if res[-1] == " ": res = res[0:-1]
174
175         for user in self.users:
176             res = res + repr(user)
177         res = res + "\n"
178         return res;
179
180     def __delitem__(self, name):
181         for ui in range(len(self.users)):
182             if self.users[ui].remote == name:
183                 del self.users[ui]
184                 break
185
186     def __repr__(self):
187         return self.dump(TRUE)
188
189     def __str__(self):
190         return "[Server: " + self.dump(FALSE) + "]"
191
192 class User:
193     def __init__(self):
194         if os.environ.has_key("USER"):
195             self.remote = os.environ["USER"]    # Remote username
196         elif os.environ.has_key("LOGNAME"):
197             self.remote = os.environ["LOGNAME"]
198         else:
199             print "Can't get your username!"
200             sys.exit(1)
201         self.localnames = [self.remote,]# Local names
202         self.password = None            # Password for mail account access
203         self.mailboxes = []             # Remote folders to retrieve from
204         self.smtphunt = []              # Hosts to forward to
205         self.smtpaddress = None         # Append this to MAIL FROM line
206         self.smtpname = None            # Use this for RCPT TO
207         self.preconnect = None          # Connection setup
208         self.postconnect = None         # Connection wrapup
209         self.mda = None                 # Mail Delivery Agent
210         self.bsmtp = None               # BSMTP output file
211         self.lmtp = FALSE               # Use LMTP rather than SMTP?
212         self.antispam = "571 550 501"   # Listener's spam-block code
213         self.keep = FALSE               # Keep messages
214         self.flush = FALSE              # Flush messages
215         self.fetchall = FALSE           # Fetch old messages
216         self.rewrite = TRUE             # Rewrite message headers
217         self.forcecr = FALSE            # Force LF -> CR/LF
218         self.stripcr = FALSE            # Strip CR
219         self.pass8bits = FALSE          # Force BODY=7BIT
220         self.mimedecode = FALSE         # Undo MIME armoring
221         self.dropstatus = FALSE         # Drop incoming Status lines
222         self.dropdelivered = FALSE      # Drop incoming Delivered-To lines
223         self.idle = FALSE               # IDLE after poll
224         self.limit = 0                  # Message size limit
225         self.warnings = 0               # Size warning interval
226         self.fetchlimit = 0             # Max messages fetched per batch
227         self.batchlimit = 0             # Max message forwarded per batch
228         self.expunge = 0                # Interval between expunges (IMAP)
229         self.ssl = 0                    # Enable Seccure Socket Layer
230         self.sslkey = None              # SSL key filename
231         self.sslcert = None             # SSL certificate filename
232         self.properties = None          # Extension properties
233         User.typemap = (
234             ('remote',      'String'),
235             # leave out mailboxes and localnames
236             ('password',    'String'),
237             # Leave out smtphunt
238             ('smtpaddress', 'String'),
239             ('smtpname', 'String'),
240             ('preconnect',  'String'),
241             ('postconnect', 'String'),
242             ('mda',         'String'),
243             ('bsmtp',       'String'),
244             ('lmtp',        'Boolean'),
245             ('antispam',    'String'),
246             ('keep',        'Boolean'),
247             ('flush',       'Boolean'),
248             ('fetchall',    'Boolean'),
249             ('rewrite',     'Boolean'),
250             ('forcecr',     'Boolean'),
251             ('stripcr',     'Boolean'),
252             ('pass8bits',   'Boolean'),
253             ('mimedecode',  'Boolean'),
254             ('dropstatus',  'Boolean'),
255             ('dropdelivered', 'Boolean'),
256             ('idle',        'Boolean'),
257             ('limit',       'Int'),
258             ('warnings',    'Int'),
259             ('fetchlimit',  'Int'),
260             ('batchlimit',  'Int'),
261             ('expunge',     'Int'),
262             ('ssl',         'Boolean'),
263             ('sslkey',      'String'),
264             ('sslcert',     'String'),
265             ('properties',  'String'))
266
267     def __repr__(self):
268         res = "    "
269         res = res + "user " + `self.remote` + " there ";
270         if self.password:
271             res = res + "with password " + `self.password` + " "
272         if self.localnames:
273             res = res + "is"
274             for x in self.localnames:
275                 res = res + " " + x
276             res = res + " here"
277         if (self.keep != UserDefaults.keep
278                 or self.flush != UserDefaults.flush
279                 or self.fetchall != UserDefaults.fetchall
280                 or self.rewrite != UserDefaults.rewrite 
281                 or self.forcecr != UserDefaults.forcecr 
282                 or self.stripcr != UserDefaults.stripcr 
283                 or self.pass8bits != UserDefaults.pass8bits
284                 or self.mimedecode != UserDefaults.mimedecode
285                 or self.dropstatus != UserDefaults.dropstatus
286                 or self.dropdelivered != UserDefaults.dropdelivered
287                 or self.idle != UserDefaults.idle):
288             res = res + " options"
289         if self.keep != UserDefaults.keep:
290             res = res + flag2str(self.keep, 'keep')
291         if self.flush != UserDefaults.flush:
292             res = res + flag2str(self.flush, 'flush')
293         if self.fetchall != UserDefaults.fetchall:
294             res = res + flag2str(self.fetchall, 'fetchall')
295         if self.rewrite != UserDefaults.rewrite:
296             res = res + flag2str(self.rewrite, 'rewrite')
297         if self.forcecr != UserDefaults.forcecr:
298             res = res + flag2str(self.forcecr, 'forcecr')
299         if self.stripcr != UserDefaults.stripcr:
300             res = res + flag2str(self.stripcr, 'stripcr')
301         if self.pass8bits != UserDefaults.pass8bits:
302             res = res + flag2str(self.pass8bits, 'pass8bits')
303         if self.mimedecode != UserDefaults.mimedecode:
304             res = res + flag2str(self.mimedecode, 'mimedecode')
305         if self.dropstatus != UserDefaults.dropstatus:
306             res = res + flag2str(self.dropstatus, 'dropstatus')
307         if self.dropdelivered != UserDefaults.dropdelivered:
308             res = res + flag2str(self.dropdelivered, 'dropdelivered')
309         if self.idle != UserDefaults.idle:
310             res = res + flag2str(self.idle, 'idle')
311         if self.limit != UserDefaults.limit:
312             res = res + " limit " + `self.limit`
313         if self.warnings != UserDefaults.warnings:
314             res = res + " warnings " + `self.warnings`
315         if self.fetchlimit != UserDefaults.fetchlimit:
316             res = res + " fetchlimit " + `self.fetchlimit`
317         if self.batchlimit != UserDefaults.batchlimit:
318             res = res + " batchlimit " + `self.batchlimit`
319         if self.ssl and self.ssl != UserDefaults.ssl:
320             res = res + flag2str(self.ssl, 'ssl')
321         if self.sslkey and self.sslkey != UserDefaults.sslkey:
322             res = res + " sslkey " + `self.sslkey`
323         if self.sslcert and self.sslcert != UserDefaults.sslcert:
324             res = res + " sslcert " + `self.sslcert`
325         if self.principal and self.principal != UserDefaults.principal:
326             res = res + " principal " + `self.principal`
327         if self.expunge != UserDefaults.expunge:
328             res = res + " expunge " + `self.expunge`
329         res = res + "\n"
330         trimmed = self.smtphunt;
331         if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
332             trimmed = trimmed[0:len(trimmed) - 1]
333         if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
334             trimmed = trimmed[0:len(trimmed) - 1]
335         if trimmed != []:
336             res = res + "    smtphost "
337             for x in trimmed:
338                 res = res + " " + x
339                 res = res + "\n"
340         if self.mailboxes:
341              res = res + "    folder"
342              for x in self.mailboxes:
343                 res = res + " " + x
344              res = res + "\n"
345         for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
346             if getattr(self, fld):
347                 res = res + " %s %s\n" % (fld, `getattr(self, fld)`)
348         if self.lmtp != UserDefaults.lmtp:
349             res = res + flag2str(self.lmtp, 'lmtp')
350         if self.antispam != UserDefaults.antispam:
351             res = res + "    antispam " + self.antispam + "\n"
352         return res;
353
354     def __str__(self):
355         return "[User: " + repr(self) + "]"
356
357 #
358 # Helper code
359 #
360
361 defaultports = {"auto":0,
362                 "POP2":109, 
363                 "POP3":110,
364                 "APOP":110,
365                 "KPOP":1109,
366                 "IMAP":143,
367                 "IMAP-GSS":143,
368                 "IMAP-K4":143,
369                 "ETRN":25}
370
371 preauthlist = ("password", "kerberos", "ssh")
372
373 listboxhelp = {
374     'title' : 'List Selection Help',
375     'banner': 'List Selection',
376     'text' : """
377 You must select an item in the list box (by clicking on it). 
378 """}
379
380 def flag2str(value, string):
381 # make a string representation of a .fetchmailrc flag or negated flag
382     str = ""
383     if value != None:
384         str = str + (" ")
385         if value == FALSE: str = str + ("no ")
386         str = str + string;
387     return str
388
389 class LabeledEntry(Frame):
390 # widget consisting of entry field with caption to left
391     def bind(self, key, action):
392         self.E.bind(key, action)
393     def focus_set(self):
394         self.E.focus_set()
395     def __init__(self, Master, text, textvar, lwidth, ewidth=12):
396         Frame.__init__(self, Master)
397         self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
398         self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
399         self.L.pack({'side':'left'})
400         self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
401
402 def ButtonBar(frame, legend, ref, alternatives, depth, command):
403 # array of radio buttons, caption to left, picking from a string list
404     bar = Frame(frame)
405     width = len(alternatives) / depth;
406     Label(bar, text=legend).pack(side=LEFT)
407     for column in range(width):
408         subframe = Frame(bar)
409         for row in range(depth):
410             ind = width * row + column
411             Radiobutton(subframe,
412                         {'text':alternatives[ind], 
413                          'variable':ref,
414                          'value':alternatives[ind],
415                          'command':command}).pack(side=TOP, anchor=W)
416         subframe.pack(side=LEFT)
417     bar.pack(side=TOP);
418     return bar
419
420 def helpwin(helpdict):
421 # help message window with a self-destruct button
422     helpwin = Toplevel()
423     helpwin.title(helpdict['title']) 
424     helpwin.iconname(helpdict['title'])
425     Label(helpwin, text=helpdict['banner']).pack()
426     textframe = Frame(helpwin)
427     scroll = Scrollbar(textframe)
428     helpwin.textwidget = Text(textframe, setgrid=TRUE)
429     textframe.pack(side=TOP, expand=YES, fill=BOTH)
430     helpwin.textwidget.config(yscrollcommand=scroll.set)
431     helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
432     scroll.config(command=helpwin.textwidget.yview)
433     scroll.pack(side=RIGHT, fill=BOTH)
434     helpwin.textwidget.insert(END, helpdict['text']);
435     Button(helpwin, text='Done', 
436            command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
437     textframe.pack(side=TOP)
438
439 def make_icon_window(base, image):
440     try:
441         # Some older pythons will error out on this
442         icon_image = PhotoImage(data=image)
443         icon_window = Toplevel()
444         Label(icon_window, image=icon_image, bg='black').pack()
445         base.master.iconwindow(icon_window)
446         # Avoid TkInter brain death. PhotoImage objects go out of
447         # scope when the enclosing function returns.  Therefore
448         # we have to explicitly link them to something.
449         base.keepalive.append(icon_image)
450     except:
451         pass
452
453 class ListEdit(Frame):
454 # edit a list of values (duplicates not allowed) with a supplied editor hook 
455     def __init__(self, newlegend, list, editor, deletor, master, helptxt):
456         self.editor = editor
457         self.deletor = deletor
458         self.list = list
459
460         # Set up a widget to accept new elements
461         self.newval = StringVar(master)
462         newwin = LabeledEntry(master, newlegend, self.newval, '12')
463         newwin.bind('<Double-1>', self.handleNew)
464         newwin.bind('<Return>', self.handleNew)
465         newwin.pack(side=TOP, fill=X, anchor=E)
466
467         # Edit the existing list
468         listframe = Frame(master)
469         scroll = Scrollbar(listframe)
470         self.listwidget = Listbox(listframe, height=0, selectmode='browse')
471         if self.list:
472             for x in self.list:
473                 self.listwidget.insert(END, x)
474         listframe.pack(side=TOP, expand=YES, fill=BOTH)
475         self.listwidget.config(yscrollcommand=scroll.set)
476         self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
477         scroll.config(command=self.listwidget.yview)
478         scroll.pack(side=RIGHT, fill=BOTH)
479         self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
480         self.listwidget.bind('<Double-1>', self.handleList);
481         self.listwidget.bind('<Return>', self.handleList);
482
483         bf = Frame(master);
484         if self.editor:
485             Button(bf, text='Edit',   command=self.editItem).pack(side=LEFT)
486         Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
487         if helptxt:
488             self.helptxt = helptxt
489             Button(bf, text='Help', fg='blue',
490                    command=self.help).pack(side=RIGHT)
491         bf.pack(fill=X)
492
493     def help(self):
494         helpwin(self.helptxt)
495
496     def handleList(self, event):
497         self.editItem();
498
499     def handleNew(self, event):
500         item = self.newval.get()
501         if item:
502             entire = self.listwidget.get(0, self.listwidget.index('end'));
503             if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
504                 self.listwidget.insert('end', item)
505                 if self.list != None: self.list.append(item)
506                 if self.editor:
507                     apply(self.editor, (item,))
508             self.newval.set('')
509
510     def editItem(self):
511         select = self.listwidget.curselection()
512         if not select:
513             helpwin(listboxhelp)
514         else:
515             index = select[0]
516             if index and self.editor:
517                 label = self.listwidget.get(index);
518                 if self.editor:
519                     apply(self.editor, (label,))
520
521     def deleteItem(self):
522         select = self.listwidget.curselection()
523         if not select:
524             helpwin(listboxhelp)
525         else:
526             index = string.atoi(select[0])
527             label = self.listwidget.get(index);
528             self.listwidget.delete(index)
529             if self.list != None:
530                 del self.list[index]
531             if self.deletor != None:
532                 apply(self.deletor, (label,))
533
534 def ConfirmQuit(frame, context):
535     ans = Dialog(frame, 
536                  title = 'Quit?',
537                  text = 'Really quit ' + context + ' without saving?',
538                  bitmap = 'question',
539                  strings = ('Yes', 'No'),
540                  default = 1)
541     return ans.num == 0
542
543 def dispose_window(master, legend, help, savelegend='OK'):
544     dispose = Frame(master, relief=RAISED, bd=5)
545     Label(dispose, text=legend).pack(side=TOP,pady=10)
546     Button(dispose, text=savelegend, fg='blue',
547            command=master.save).pack(side=LEFT)
548     Button(dispose, text='Quit', fg='blue',
549            command=master.nosave).pack(side=LEFT)
550     Button(dispose, text='Help', fg='blue',
551            command=lambda x=help: helpwin(x)).pack(side=RIGHT)
552     dispose.pack(fill=X)
553     return dispose
554
555 class MyWidget:
556 # Common methods for Tkinter widgets -- deals with Tkinter declaration
557     def post(self, widgetclass, field):
558         for x in widgetclass.typemap:
559             if x[1] == 'Boolean':
560                 setattr(self, x[0], BooleanVar(self))
561             elif x[1] == 'String':
562                 setattr(self, x[0], StringVar(self))
563             elif x[1] == 'Int':
564                 setattr(self, x[0], IntVar(self))
565             source = getattr(getattr(self, field), x[0])
566             if source:
567                 getattr(self, x[0]).set(source)
568
569     def fetch(self, widgetclass, field):
570         for x in widgetclass.typemap:
571             setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
572
573 #
574 # First, code to set the global fetchmail run controls.
575 #
576
577 configure_novice_help = {
578     'title' : 'Fetchmail novice configurator help',
579     'banner': 'Novice configurator help',
580     'text' : """
581 In the `Novice Configurator Controls' panel, you can:
582
583 Press `Save' to save the new fetchmail configuration you have created.
584 Press `Quit' to exit without saving.
585 Press `Help' to bring up this help message.
586
587 In the `Novice Configuration' panels, you will set up the basic data
588 needed to create a simple fetchmail setup.  These include:
589
590 1. The name of the remote site you want to query.
591
592 2. Your login name on that site.
593
594 3. Your password on that site.
595
596 4. A protocol to use (POP, IMAP, ETRN, etc.)
597
598 5. A polling interval.
599
600 6. Options to fetch old messages as well as new, uor to suppress
601    deletion of fetched message.
602
603 The novice-configuration code will assume that you want to forward mail
604 to a local sendmail listener with no special options.
605 """}
606
607 configure_expert_help = {
608     'title' : 'Fetchmail expert configurator help',
609     'banner': 'Expert configurator help',
610     'text' : """
611 In the `Expert Configurator Controls' panel, you can:
612
613 Press `Save' to save the new fetchmail configuration you have edited.
614 Press `Quit' to exit without saving.
615 Press `Help' to bring up this help message.
616
617 In the `Run Controls' panel, you can set the following options that
618 control how fetchmail runs:
619
620 Poll interval
621         Number of seconds to wait between polls in the background.
622         If zero, fetchmail will run in foreground.
623
624 Logfile
625         If empty, emit progress and error messages to stderr.
626         Otherwise this gives the name of the files to write to.
627         This field is ignored if the "Log to syslog?" option is on.
628
629 Idfile
630         If empty, store seen-message IDs in .fetchids under user's home
631         directory.  If nonempty, use given file name.
632
633 Postmaster
634         Who to send multidrop mail to as a last resort if no address can
635         be matched.  Normally empty; in this case, fetchmail treats the
636         invoking user as the address of last resort unless that user is
637         root.  If that user is root, fetchmail sends to `postmaster'.
638
639 Bounces to sender?
640         If this option is on (the default) error mail goes to the sender.
641         Otherwise it goes to the postmaster.
642
643 Invisible
644         If false (the default) fetchmail generates a Received line into
645         each message and generates a HELO from the machine it is running on.
646         If true, fetchmail generates no Received line and HELOs as if it were
647         the remote site.
648
649 In the `Remote Mail Configurations' panel, you can:
650
651 1. Enter the name of a new remote mail server you want fetchmail to query.
652
653 To do this, simply enter a label for the poll configuration in the
654 `New Server:' box.  The label should be a DNS name of the server (unless
655 you are using ssh or some other tunneling method and will fill in the `via'
656 option on the site configuration screen).
657
658 2. Change the configuration of an existing site.
659
660 To do this, find the site's label in the listbox and double-click it.
661 This will take you to a site configuration dialogue.
662 """}
663
664
665 class ConfigurationEdit(Frame, MyWidget):
666     def __init__(self, configuration, outfile, master, onexit):
667         self.subwidgets = {}
668         self.configuration = configuration
669         self.outfile = outfile
670         self.container = master
671         self.onexit = onexit
672         ConfigurationEdit.mode_to_help = {
673             'novice':configure_novice_help, 'expert':configure_expert_help
674             }
675
676     def server_edit(self, sitename):
677         self.subwidgets[sitename] = ServerEdit(sitename, self).edit(self.mode, Toplevel())
678
679     def server_delete(self, sitename):
680         try:
681             for user in self.subwidgets.keys():
682                 user.destruct()
683             del self.configuration[sitename]
684         except:
685             pass
686
687     def edit(self, mode):
688         self.mode = mode
689         Frame.__init__(self, self.container)
690         self.master.title('fetchmail ' + self.mode + ' configurator');
691         self.master.iconname('fetchmail ' + self.mode + ' configurator');
692         self.master.protocol('WM_DELETE_WINDOW', self.nosave)
693         self.keepalive = []     # Use this to anchor the PhotoImage object
694         make_icon_window(self, fetchmail_icon)
695         Pack.config(self)
696         self.post(Configuration, 'configuration')
697
698         dispose_window(self,
699                        'Configurator ' + self.mode + ' Controls',
700                        ConfigurationEdit.mode_to_help[self.mode],
701                        'Save')
702
703         gf = Frame(self, relief=RAISED, bd = 5)
704         Label(gf,
705                 text='Fetchmail Run Controls', 
706                 bd=2).pack(side=TOP, pady=10)
707
708         df = Frame(gf)
709
710         ff = Frame(df)
711         if self.mode != 'novice':
712             # Set the postmaster
713             log = LabeledEntry(ff, '     Postmaster:', self.postmaster, '14')
714             log.pack(side=RIGHT, anchor=E)
715
716         # Set the poll interval
717         de = LabeledEntry(ff, '     Poll interval:', self.poll_interval, '14')
718         de.pack(side=RIGHT, anchor=E)
719         ff.pack()
720
721         df.pack()
722
723         if self.mode != 'novice':
724             pf = Frame(gf)
725             Checkbutton(pf,
726                 {'text':'Bounces to sender?',
727                 'variable':self.bouncemail,
728                 'relief':GROOVE}).pack(side=LEFT, anchor=W)
729             pf.pack(fill=X)
730
731             sf = Frame(gf)
732             Checkbutton(sf,
733                 {'text':'Log to syslog?',
734                 'variable':self.syslog,
735                 'relief':GROOVE}).pack(side=LEFT, anchor=W)
736             log = LabeledEntry(sf, '     Logfile:', self.logfile, '14')
737             log.pack(side=RIGHT, anchor=E)
738             sf.pack(fill=X)
739
740             Checkbutton(gf,
741                 {'text':'Invisible mode?',
742                 'variable':self.invisible,
743                  'relief':GROOVE}).pack(side=LEFT, anchor=W)
744             # Set the idfile
745             log = LabeledEntry(gf, '     Idfile:', self.idfile, '14')
746             log.pack(side=RIGHT, anchor=E)
747
748         gf.pack(fill=X)
749
750         # Expert mode allows us to edit multiple sites
751         lf = Frame(self, relief=RAISED, bd=5)
752         Label(lf,
753               text='Remote Mail Server Configurations', 
754               bd=2).pack(side=TOP, pady=10)
755         ListEdit('New Server:', 
756                 map(lambda x: x.pollname, self.configuration.servers),
757                 lambda site, self=self: self.server_edit(site),
758                 lambda site, self=self: self.server_delete(site),
759                 lf, remotehelp)
760         lf.pack(fill=X)
761
762     def destruct(self):
763         for sitename in self.subwidgets.keys():
764             self.subwidgets[sitename].destruct()        
765         self.master.destroy()
766         self.onexit()
767
768     def nosave(self):
769         if ConfirmQuit(self, self.mode + " configuration editor"):
770             self.destruct()
771
772     def save(self):
773         for sitename in self.subwidgets.keys():
774             self.subwidgets[sitename].save()
775         self.fetch(Configuration, 'configuration')
776         fm = None
777         if not self.outfile:
778             fm = sys.stdout
779         elif not os.path.isfile(self.outfile) or Dialog(self, 
780                  title = 'Overwrite existing run control file?',
781                  text = 'Really overwrite existing run control file?',
782                  bitmap = 'question',
783                  strings = ('Yes', 'No'),
784                  default = 1).num == 0:
785             fm = open(self.outfile, 'w')
786         if fm:
787             fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
788             fm.write(`self.configuration`)
789             if self.outfile:
790                 fm.close()
791             if fm != sys.stdout:
792                 os.chmod(self.outfile, 0600)
793             self.destruct()
794
795 #
796 # Server editing stuff.
797 #
798 remotehelp = {
799     'title' : 'Remote site help',
800     'banner': 'Remote sites',
801     'text' : """
802 When you add a site name to the list here, 
803 you initialize an entry telling fetchmail
804 how to poll a new site.
805
806 When you select a sitename (by double-
807 clicking it, or by single-clicking to
808 select and then clicking the Edit button),
809 you will open a window to configure that
810 site.
811 """}
812
813 serverhelp = {
814     'title' : 'Server options help',
815     'banner': 'Server Options',
816     'text' : """
817 The server options screen controls fetchmail 
818 options that apply to one of your mailservers.
819
820 Once you have a mailserver configuration set
821 up as you like it, you can select `OK' to
822 store it in the server list maintained in
823 the main configuration window.
824
825 If you wish to discard changes to a server 
826 configuration, select `Quit'.
827 """}
828
829 controlhelp = {
830     'title' : 'Run Control help',
831     'banner': 'Run Controls',
832     'text' : """
833 If the `Poll normally' checkbox is on, the host is polled as part of
834 the normal operation of fetchmail when it is run with no arguments.
835 If it is off, fetchmail will only query this host when it is given as
836 a command-line argument.
837
838 The `True name of server' box should specify the actual DNS name
839 to query. By default this is the same as the poll name.
840
841 Normally each host described in the file is queried once each 
842 poll cycle. If `Cycles to skip between polls' is greater than 0,
843 that's the number of poll cycles that are skipped between the
844 times this post is actually polled.
845
846 The `Server timeout' is the number of seconds fetchmail will wait
847 for a reply from the mailserver before concluding it is hung and
848 giving up.
849 """}
850
851 protohelp = {
852     'title' : 'Protocol and Port help',
853     'banner': 'Protocol and Port',
854     'text' : """
855 These options control the remote-mail protocol
856 and TCP/IP service port used to query this
857 server.
858
859 If you click the `Probe for supported protocols'
860 button, fetchmail will try to find you the most
861 capable server on the selected host (this will
862 only work if you're conncted to the Internet).
863 The probe only checks for ordinary IMAP and POP
864 protocols; fortunately these are the most
865 frequently supported.
866
867 The `Protocol' button bar offers you a choice of
868 all the different protocols available.  The `auto'
869 protocol is the default mode; it probes the host
870 ports for POP3 and IMAP to see if either is
871 available.
872
873 Normally the TCP/IP service port to use is 
874 dictated by the protocol choice.  The `Port'
875 field (only present in expert mode) lets you
876 set a non-standard port.
877 """}
878
879 sechelp = {
880     'title' : 'Security option help',
881     'banner': 'Security',
882     'text' : """
883 The `interface' option allows you to specify a range
884 of IP addresses to monitor for activity.  If these
885 addresses are not active, fetchmail will not poll.
886 Specifying this may protect you from a spoofing attack
887 if your client machine has more than one IP gateway
888 address and some of the gateways are to insecure nets.
889
890 The `monitor' option, if given, specifies the only
891 device through which fetchmail is permitted to connect
892 to servers.  This option may be used to prevent
893 fetchmail from triggering an expensive dial-out if the
894 interface is not already active.
895
896 The `interface' and `monitor' options are available
897 only for Linux and freeBSD systems.  See the fetchmail
898 manual page for details on these.
899
900 The ssl option enables SSL communication with a mailserver
901 supporting Secure Sockets Layer. The sslkey and sslcert options
902 declare key and certificate files for use with SSL.
903
904 The `netsec' option will be configurable only if fetchmail
905 was compiled with IPV6 support.  If you need to use it,
906 you probably know what to do.
907 """}
908
909 multihelp = {
910     'title' : 'Multidrop option help',
911     'banner': 'Multidrop',
912     'text' : """
913 These options are only useful with multidrop mode.
914 See the manual page for extended discussion.
915 """}
916
917 suserhelp = {
918     'title' : 'User list help',
919     'banner': 'User list',
920     'text' : """
921 When you add a user name to the list here, 
922 you initialize an entry telling fetchmail
923 to poll the site on behalf of the new user.
924
925 When you select a username (by double-
926 clicking it, or by single-clicking to
927 select and then clicking the Edit button),
928 you will open a window to configure the
929 user's options on that site.
930 """}
931
932 class ServerEdit(Frame, MyWidget):
933     def __init__(self, host, parent):
934         self.parent = parent
935         self.server = None
936         self.subwidgets = {}
937         for site in parent.configuration.servers:
938             if site.pollname == host:
939                 self.server = site
940         if (self.server == None):
941                 self.server = Server()
942                 self.server.pollname = host
943                 self.server.via = None
944                 parent.configuration.servers.append(self.server)
945
946     def edit(self, mode, master=None):
947         Frame.__init__(self, master)
948         Pack.config(self)
949         self.master.title('Fetchmail host ' + self.server.pollname);
950         self.master.iconname('Fetchmail host ' + self.server.pollname);
951         self.post(Server, 'server')
952         self.makeWidgets(self.server.pollname, mode)
953         self.keepalive = []     # Use this to anchor the PhotoImage object
954         make_icon_window(self, fetchmail_icon)
955 #       self.grab_set()
956 #       self.focus_set()
957 #       self.wait_window()
958         return self
959
960     def destruct(self):
961         for username in self.subwidgets.keys():
962             self.subwidgets[username].destruct()        
963         del self.parent.subwidgets[self.server.pollname]
964         Widget.destroy(self.master)
965
966     def nosave(self):
967         if ConfirmQuit(self, 'server option editing'):
968             self.destruct()
969
970     def save(self):
971         self.fetch(Server, 'server')
972         for username in self.subwidgets.keys():
973             self.subwidgets[username].save()        
974         self.destruct()
975
976     def refreshPort(self):
977         proto = self.protocol.get()
978         # We used to only reset the port if it had a default (zero) value.
979         # This turns out to be a bad idea especially in Novice mode -- if
980         # you set POP3 and then set IMAP, the port invisibly remained 110.
981         # Now we reset unconditionally on the theory that if you're setting
982         # a custom port number you should be in expert mode and playing
983         # close enough attention to notice this...
984         self.port.set(defaultports[proto])
985         if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED 
986
987     def user_edit(self, username, mode):
988         self.subwidgets[username] = UserEdit(username, self).edit(mode, Toplevel())
989
990     def user_delete(self, username):
991         if self.subwidgets.has_key(username):
992             self.subwidgets[username].destruct()
993         del self.server[username]
994
995     def makeWidgets(self, host, mode):
996         topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
997
998         leftwin = Frame(self);
999         leftwidth = '25';
1000
1001         if mode != 'novice':
1002             ctlwin = Frame(leftwin, relief=RAISED, bd=5)
1003             Label(ctlwin, text="Run Controls").pack(side=TOP)
1004             Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
1005             LabeledEntry(ctlwin, 'True name of ' + host + ':',
1006                       self.via, leftwidth).pack(side=TOP, fill=X)
1007             LabeledEntry(ctlwin, 'Cycles to skip between polls:',
1008                       self.interval, leftwidth).pack(side=TOP, fill=X)
1009             LabeledEntry(ctlwin, 'Server timeout (seconds):',
1010                       self.timeout, leftwidth).pack(side=TOP, fill=X)
1011             Button(ctlwin, text='Help', fg='blue',
1012                command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
1013             ctlwin.pack(fill=X)
1014
1015         # Compute the available protocols from the compile-time options
1016         protolist = ['auto']
1017         if 'pop2' in feature_options:
1018             protolist.append("POP2")
1019         if 'pop3' in feature_options:
1020             protolist = protolist + ["POP3", "APOP", "KPOP"]
1021         if 'sdps' in feature_options:
1022             protolist.append("SDPS")
1023         if 'imap' in feature_options:
1024             protolist.append("IMAP")
1025         if 'imap-gss' in feature_options:
1026             protolist.append("IMAP-GSS")
1027         if 'imap-k4' in feature_options:
1028             protolist.append("IMAP-K4")
1029         if 'etrn' in feature_options:
1030             protolist.append("ETRN")
1031
1032         protwin = Frame(leftwin, relief=RAISED, bd=5)
1033         Label(protwin, text="Protocol").pack(side=TOP)
1034         ButtonBar(protwin, '',
1035                   self.protocol, protolist, 2,
1036                   self.refreshPort) 
1037         if mode != 'novice':
1038             LabeledEntry(protwin, 'On server TCP/IP port:',
1039                       self.port, leftwidth).pack(side=TOP, fill=X)
1040             self.refreshPort()
1041             Checkbutton(protwin,
1042                 text="POP3: track `seen' with client-side UIDLs?",
1043                 variable=self.uidl).pack(side=TOP)   
1044         Button(protwin, text='Probe for supported protocols', fg='blue',
1045                command=self.autoprobe).pack(side=LEFT)
1046         Button(protwin, text='Help', fg='blue',
1047                command=lambda: helpwin(protohelp)).pack(side=RIGHT)
1048         protwin.pack(fill=X)
1049
1050         userwin = Frame(leftwin, relief=RAISED, bd=5)
1051         Label(userwin, text="User entries for " + host).pack(side=TOP)
1052         ListEdit("New user: ",
1053                  map(lambda x: x.remote, self.server.users),
1054                  lambda u, m=mode, s=self: s.user_edit(u, m),
1055                  lambda u, s=self: s.user_delete(u),
1056                  userwin, suserhelp)
1057         userwin.pack(fill=X)
1058
1059         leftwin.pack(side=LEFT, anchor=N, fill=X);
1060
1061         if mode != 'novice':
1062             rightwin = Frame(self);
1063
1064             mdropwin = Frame(rightwin, relief=RAISED, bd=5)
1065             Label(mdropwin, text="Multidrop options").pack(side=TOP)
1066             LabeledEntry(mdropwin, 'Envelope address header:',
1067                       self.envelope, '22').pack(side=TOP, fill=X)
1068             LabeledEntry(mdropwin, 'Envelope headers to skip:',
1069                       self.envskip, '22').pack(side=TOP, fill=X)
1070             LabeledEntry(mdropwin, 'Name prefix to strip:',
1071                       self.qvirtual, '22').pack(side=TOP, fill=X)
1072             Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
1073                     variable=self.dns).pack(side=TOP)
1074             Label(mdropwin, text="DNS aliases").pack(side=TOP)
1075             ListEdit("New alias: ", self.server.aka, None, None, mdropwin, None)
1076             Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
1077             ListEdit("New domain: ",
1078                  self.server.localdomains, None, None, mdropwin, multihelp)
1079             mdropwin.pack(fill=X)
1080
1081             if os_type == 'linux' or os_type == 'freebsd' or 'netsec' in feature_options:
1082                 secwin = Frame(rightwin, relief=RAISED, bd=5)
1083                 Label(secwin, text="Security").pack(side=TOP)
1084                 # Don't actually let users set this.  KPOP sets it implicitly
1085                 #       ButtonBar(secwin, 'Preauthorization mode:',
1086                 #                 self.preauth, preauthlist, 1, None).pack(side=TOP)
1087                 if os_type == 'linux' or os_type == 'freebsd'  or 'interface' in dictmembers:
1088                     LabeledEntry(secwin, 'IP range to check before poll:',
1089                          self.interface, leftwidth).pack(side=TOP, fill=X)
1090                 if os_type == 'linux' or os_type == 'freebsd' or 'monitor' in dictmembers:
1091                     LabeledEntry(secwin, 'Interface to monitor:',
1092                          self.monitor, leftwidth).pack(side=TOP, fill=X)
1093                 if 'netsec' in feature_options or 'netsec' in dictmembers:
1094                     LabeledEntry(secwin, 'IPV6 security options:',
1095                          self.netsec, leftwidth).pack(side=TOP, fill=X)
1096                 Button(secwin, text='Help', fg='blue',
1097                        command=lambda: helpwin(sechelp)).pack(side=RIGHT)
1098                 secwin.pack(fill=X)
1099
1100             rightwin.pack(side=LEFT, anchor=N);
1101
1102     def autoprobe(self):
1103         # Note: this only handles case (1) near fetchmail.c:1032
1104         # We're assuming people smart enough to set up ssh tunneling
1105         # won't need autoprobing.
1106         if self.server.via:
1107             realhost = self.server.via
1108         else:
1109             realhost = self.server.pollname
1110         greetline = None
1111         for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
1112             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1113             try:
1114                 sock.connect(realhost, port)
1115                 greetline = sock.recv(1024)
1116                 sock.close()
1117             except:
1118                 pass
1119             else:
1120                 break
1121         confwin = Toplevel()
1122         if greetline == None:
1123             title = "Autoprobe of " + realhost + " failed"
1124             confirm = """
1125 Fetchmailconf didn't find any mailservers active.
1126 This could mean the host doesn't support any,
1127 or that your Internet connection is down, or
1128 that the host is so slow that the probe timed
1129 out before getting a response.
1130 """
1131         else:
1132             warnings = ''
1133             # OK, now try to recognize potential problems
1134
1135             if protocol == "POP2":
1136                 warnings = warnings + """
1137 It appears you have somehow found a mailserver running only POP2.
1138 Congratulations.  Have you considered a career in archaeology?
1139
1140 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1141 Unless the first line of your fetchmail -V output includes the string "POP2",
1142 you'll have to build it from sources yourself with the configure
1143 switch --enable-POP2.
1144
1145 """
1146
1147 ### POP3 servers start here
1148
1149             if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1150                 warnings = warnings + """
1151 This appears to be an old version of the UC Davis POP server.  These are
1152 dangerously unreliable (among other problems, they may drop your mailbox
1153 on the floor if your connection is interrupted during the session).
1154
1155 It is strongly recommended that you find a better POP3 server.  The fetchmail
1156 FAQ includes pointers to good ones.
1157
1158 """
1159 # Steve VanDevender <stevev@efn.org> writes:
1160 # The only system I have seen this happen with is cucipop-1.31
1161 # under SunOS 4.1.4.  cucipop-1.31 runs fine on at least Solaris
1162 # 2.x and probably quite a few other systems.  It appears to be a
1163 # bug or bad interaction with the SunOS realloc() -- it turns out
1164 # that internally cucipop does allocate a certain data structure in
1165 # multiples of 16, using realloc() to bump it up to the next
1166 # multiple if it needs more.
1167
1168 # The distinctive symptom is that when there are 16 messages in the
1169 # inbox, you can RETR and DELE all 16 messages successfully, but on
1170 # QUIT cucipop returns something like "-ERR Error locking your
1171 # mailbox" and aborts without updating it.
1172
1173 # The cucipop banner looks like:
1174
1175 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1176 #
1177             if string.find(greetline, "Cubic Circle") > 0:
1178                 warnings = warnings + """
1179 I see your server is running cucipop.  Better make sure the server box
1180 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1181 under that version, and doesn't cope with the result gracefully.  Newer
1182 SunOS and Solaris machines run cucipop OK.
1183
1184 """
1185             if string.find(greetline, "David POP3 Server") > 0:
1186                 warnings = warnings + """
1187 This POP3 serrver is badly broken.  You should get rid of it -- and the
1188 brain-dead NT operating system it rode in on.
1189
1190 """
1191 # The greeting line on the server known to be buggy is:
1192 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1193 #
1194             if string.find(greetline, "FTGate") > 0:
1195                 warnings = warnings + """
1196 This POP server has a weird bug; it says OK twice in response to TOP.
1197 Its response to RETR is normal, so use the `fetchall' option.
1198
1199 """
1200             if string.find(greetline, "OpenMail") > 0:
1201                 warnings = warnings + """
1202 You appear to be using some version of HP OpenMail.  Many versions of
1203 OpenMail do not process the "TOP" command correctly; the symptom is that
1204 only the header and first line of each message is retrieved.  To work
1205 around this bug, turn on `fetchall' on all user entries associated with
1206 this server.  
1207
1208 """
1209             if string.find(greetline, "POP-Max") > 0:
1210                 warnings = warnings + """
1211 The Mail Max POP3 server screws up on mail with attachments.  It
1212 reports the message size with attachments included, but doesn't
1213 download them on a RETR or TOP (this violates the IMAP RFCs).  It also
1214 doesn't implement TOP correctly.  You should get rid of it -- and the
1215 brain-dead NT server it rode in on.
1216
1217 """
1218             if string.find(greetline, "POP3 Server Ready") > 0:
1219                 warnings = warnings + """
1220 Some server that uses this greeting line has been observed to choke on
1221 TOP %d 99999999.  Use the fetchall option. if necessary, to force RETR.
1222
1223 """
1224             if string.find(greetline, "QPOP") > 0:
1225                 warnings = warnings + """
1226 This appears to be a version of Eudora qpopper.  That's good.  Fetchmail
1227 knows all about qpopper.  However, be aware that the 2.53 version of
1228 qpopper does something odd that causes fetchmail to hang with a socket
1229 error on very large messages.  This is probably not a fetchmail bug, as
1230 it has been observed with fetchpop.  The fix is to upgrade to qpopper
1231 3.0beta or a more recent version.  Better yet, switch to IMAP.
1232
1233 """
1234             if string.find(greetline, " sprynet.com") > 0:
1235                 warnings = warnings + """
1236 You appear to be using a SpryNet server.  In mid-1999 it was reported that
1237 the SpryNet TOP command marks messages seen.  Therefore, for proper error
1238 recovery in the event of a line drop, it is strongly recommended that you
1239 turn on `fetchall' on all user entries associated with this server.  
1240
1241 """
1242             if string.find(greetline, "TEMS POP3") > 0:
1243                 warnings = warnings + """
1244 Your POP3 server has "TEMS" in its header line.  At least one such
1245 server does not process the "TOP" command correctly; the symptom is
1246 that fetchmail hangs when trying to retrieve mail.  To work around
1247 this bug, turn on `fetchall' on all user entries associated with this
1248 server.
1249
1250 """
1251             if string.find(greetline, " spray.se") > 0:
1252                 warnings = warnings + """
1253 Your POP3 server has "spray.se" in its header line.  In May 2000 at
1254 least one such server did not process the "TOP" command correctly; the
1255 symptom is that messages are treated as headerless.  To work around
1256 this bug, turn on `fetchall' on all user entries associated with this
1257 server.
1258
1259 """
1260             if string.find(greetline, " usa.net") > 0:
1261                 warnings = warnings + """
1262 You appear to be using USA.NET's free mail service.  Their POP3 servers
1263 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1264 fetchmail can compensate.  They seem to require that fetchall be switched on
1265 (otherwise you won't necessarily see all your mail, not even new mail).
1266 They also botch the TOP command the fetchmail normally uses for retrieval
1267 (it only retrieves about 10 lines rather than the number specified).
1268 Turning on fetchall will disable the use of TOP.
1269
1270 Therefore, it is strongly recommended that you turn on `fetchall' on all
1271 user entries associated with this server.  
1272
1273 """
1274             if string.find(greetline, " Novonyx POP3") > 0:
1275                 warnings = warnings + """
1276 Your mailserver is running Novonyx POP3.  This server, at least as of
1277 version 2.17, seems to have problems handling and reporting seen bits.
1278 You may have to use the fetchall option.
1279
1280 """
1281
1282 ### IMAP servers start here
1283
1284             if string.find(greetline, "GroupWise") > 0:
1285                 warnings = warnings + """
1286 The Novell GroupWise IMAP server would be better named GroupFoolish;
1287 it is (according to the designer of IMAP) unusably broken.  Among
1288 other things, it doesn't include a required content length in its
1289 BODY[TEXT] response.<p>
1290
1291 Fetchmail works around this problem, but we strongly recommend voting
1292 with your dollars for a server that isn't brain-dead.  If you stick
1293 with code as shoddy as GroupWise seems to be, you will probably pay
1294 for it with other problems.<p>
1295
1296 """
1297             if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1298                 warnings = warnings + """
1299 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1300 set the messages's Seen flag.  As a result, if you use the keep option the
1301 same messages will be downloaded over and over.
1302
1303 """
1304             if string.find(greetline, "InterChange") > 0:
1305                 warnings = warnings + """
1306 The InterChange IMAP server screws up on mail with attachments.  It
1307 doesn't fetch them if you give it a BODY[TEXT] request, though it
1308 does if you request RFC822.TEXT.  According to the IMAP RFCs and their
1309 maintainer these should be equivalent -- and we can't drop the
1310 BODY[TEXT] form because M$ Exchange (quite legally under RFC2062)
1311 rejects it.
1312
1313 """
1314             if string.find(greetline, "Imail") > 0:
1315                 warnings = warnings + """
1316 We've seen a bug report indicating that this IMAP server (at least as of
1317 version 5.0.7) returns an invalid body size for messages with MIME
1318 attachments; the effect is to drop the attachments on the floor.  We
1319 recommend you upgrade to a non-broken IMAP server.
1320
1321 """
1322             if string.find(greetline, "Domino IMAP4") > 0:
1323                 warnings = warnings + """
1324 Your IMAP server appears to be Lotus Domino.  This server, at least up
1325 to version 4.6.2a, has a bug in its generation of MIME boundaries (see
1326 the details in the fetchmail FAQ).  As a result, even MIME aware MUAs
1327 will see attachments as part of the message text.  If your Domino server's
1328 POP3 facility is enabled, we recommend you fall back on it.
1329
1330 """
1331
1332 ### Checks for protocol variants start here
1333
1334             closebrak = string.find(greetline, ">")
1335             if  closebrak > 0 and greetline[closebrak+1] == "\r":
1336                 warnings = warnings + """
1337 It looks like you could use APOP on this server and avoid sending it your
1338 password in clear.  You should talk to the mailserver administrator about
1339 this.
1340
1341 """
1342             if string.find(greetline, "IMAP2bis") > 0:
1343                 warnings = warnings + """
1344 IMAP2bis servers have a minor problem; they can't peek at messages without
1345 marking them seen.  If you take a line hit during the retrieval, the 
1346 interrupted message may get left on the server, marked seen.
1347
1348 To work around this, it is recommended that you set the `fetchall'
1349 option on all user entries associated with this server, so any stuck
1350 mail will be retrieved next time around.
1351
1352 To fix this bug, upgrade to an IMAP4 server.  The fetchmail FAQ includes
1353 a pointer to an open-source implementation.
1354
1355 """
1356             if string.find(greetline, "IMAP4rev1") > 0:
1357                 warnings = warnings + """
1358 I see an IMAP4rev1 server.  Excellent.  This is (a) the best kind of
1359 remote-mail server, and (b) the one the fetchmail author uses.  Fetchmail
1360 has therefore been extremely well tested with this class of server.
1361
1362 """
1363             if warnings == '':
1364                 warnings = warnings + """
1365 Fetchmail doesn't know anything special about this server type.
1366
1367 """
1368
1369             # Display success window with warnings
1370             title = "Autoprobe of " + realhost + " succeeded"
1371             confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1372             self.protocol.set(protocol)
1373         confwin.title(title) 
1374         confwin.iconname(title)
1375         Label(confwin, text=title).pack()
1376         Message(confwin, text=confirm, width=600).pack()
1377         Button(confwin, text='Done', 
1378                    command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1379         
1380 #
1381 # User editing stuff
1382 #
1383
1384 userhelp = {
1385     'title' : 'User option help',
1386     'banner': 'User options',
1387     'text' : """
1388 You may use this panel to set options
1389 that may differ between individual
1390 users on your site.
1391
1392 Once you have a user configuration set
1393 up as you like it, you can select `OK' to
1394 store it in the user list maintained in
1395 the site configuration window.
1396
1397 If you wish to discard the changes you have
1398 made to user options, select `Quit'.
1399 """}
1400
1401 localhelp = {
1402     'title' : 'Local name help',
1403     'banner': 'Local names',
1404     'text' : """
1405 The local name(s) in a user entry are the
1406 people on the client machine who should
1407 receive mail from the poll described.
1408
1409 Note: if a user entry has more than one
1410 local name, messages will be retrieved
1411 in multidrop mode.  This complicates
1412 the configuration issues; see the manual
1413 page section on multidrop mode.
1414 """}
1415
1416 class UserEdit(Frame, MyWidget):
1417     def __init__(self, username, parent):
1418         self.parent = parent
1419         self.user = None
1420         for user in parent.server.users:
1421             if user.remote == username:
1422                 self.user = user
1423         if self.user == None:
1424             self.user = User()
1425             self.user.remote = username
1426             self.user.localnames = [username]
1427             parent.server.users.append(self.user)
1428
1429     def edit(self, mode, master=None):
1430         Frame.__init__(self, master)
1431         Pack.config(self)
1432         self.master.title('Fetchmail user ' + self.user.remote
1433                           + ' querying ' + self.parent.server.pollname);
1434         self.master.iconname('Fetchmail user ' + self.user.remote);
1435         self.post(User, 'user')
1436         self.makeWidgets(mode, self.parent.server.pollname)
1437         self.keepalive = []     # Use this to anchor the PhotoImage object
1438         make_icon_window(self, fetchmail_icon)
1439 #       self.grab_set()
1440 #       self.focus_set()
1441 #       self.wait_window()
1442         return self
1443
1444     def destruct(self):
1445         # Yes, this test can fail -- if you delete the parent window.
1446         if self.parent.subwidgets.has_key(self.user.remote):
1447             del self.parent.subwidgets[self.user.remote]
1448         Widget.destroy(self.master)
1449
1450     def nosave(self):
1451         if ConfirmQuit(self, 'user option editing'):
1452             self.destruct()
1453
1454     def save(self):
1455         self.fetch(User, 'user')
1456         self.destruct()
1457
1458     def makeWidgets(self, mode, servername):
1459         dispose_window(self,
1460                         "User options for " + self.user.remote + " querying " + servername,
1461                         userhelp)
1462
1463         if mode != 'novice':
1464             leftwin = Frame(self);
1465         else:
1466             leftwin = self
1467
1468         secwin = Frame(leftwin, relief=RAISED, bd=5)
1469         Label(secwin, text="Authentication").pack(side=TOP)
1470         LabeledEntry(secwin, 'Password:',
1471                       self.password, '12').pack(side=TOP, fill=X)
1472         secwin.pack(fill=X, anchor=N)
1473
1474         if 'ssl' in feature_options or 'ssl' in dictmembers:
1475             sslwin = Frame(leftwin, relief=RAISED, bd=5)
1476             Checkbutton(sslwin, text="Use SSL?",
1477                         variable=self.ssl).pack(side=TOP, fill=X)
1478             LabeledEntry(sslwin, 'SSL key:',
1479                          self.sslkey, '14').pack(side=TOP, fill=X)
1480             LabeledEntry(sslwin, 'SSL certificate:',
1481                          self.sslcert, '14').pack(side=TOP, fill=X)
1482             sslwin.pack(fill=X, anchor=N)
1483
1484         # Someday this should handle Kerberos 5 too
1485         if 'imap-k4':
1486             LabeledEntry(secwin, 'Principal:',
1487                          self.principal, '12').pack(side=TOP, fill=X)
1488
1489         names = Frame(leftwin, relief=RAISED, bd=5)
1490         Label(names, text="Local names").pack(side=TOP)
1491         ListEdit("New name: ",
1492                      self.user.localnames, None, None, names, localhelp)
1493         names.pack(fill=X, anchor=N)
1494
1495         if mode != 'novice':
1496             targwin = Frame(leftwin, relief=RAISED, bd=5)
1497             Label(targwin, text="Forwarding Options").pack(side=TOP)
1498             Label(targwin, text="Listeners to forward to").pack(side=TOP)
1499             ListEdit("New listener:",
1500                      self.user.smtphunt, None, None, targwin, None)
1501             LabeledEntry(targwin, 'Append to MAIL FROM line:',
1502                      self.smtpaddress, '26').pack(side=TOP, fill=X)
1503             LabeledEntry(targwin, 'Set RCPT To address:',
1504                      self.smtpname, '26').pack(side=TOP, fill=X)
1505             LabeledEntry(targwin, 'Connection setup command:',
1506                      self.preconnect, '26').pack(side=TOP, fill=X)
1507             LabeledEntry(targwin, 'Connection wrapup command:',
1508                      self.postconnect, '26').pack(side=TOP, fill=X)
1509             LabeledEntry(targwin, 'Local delivery agent:',
1510                      self.mda, '26').pack(side=TOP, fill=X)
1511             LabeledEntry(targwin, 'BSMTP output file:',
1512                      self.bsmtp, '26').pack(side=TOP, fill=X)
1513             LabeledEntry(targwin, 'Listener spam-block codes:',
1514                      self.antispam, '26').pack(side=TOP, fill=X)
1515             LabeledEntry(targwin, 'Pass-through properties:',
1516                      self.properties, '26').pack(side=TOP, fill=X)
1517             Checkbutton(targwin, text="Use LMTP?",
1518                         variable=self.lmtp).pack(side=TOP, fill=X)
1519             targwin.pack(fill=X, anchor=N)
1520
1521         if mode != 'novice':
1522             leftwin.pack(side=LEFT, fill=X, anchor=N)
1523             rightwin = Frame(self)
1524         else:
1525             rightwin = self
1526
1527         optwin = Frame(rightwin, relief=RAISED, bd=5)
1528         Label(optwin, text="Processing Options").pack(side=TOP)
1529         Checkbutton(optwin, text="Suppress deletion of messages after reading",
1530                     variable=self.keep).pack(side=TOP, anchor=W)
1531         Checkbutton(optwin, text="Fetch old messages as well as new",
1532                     variable=self.fetchall).pack(side=TOP, anchor=W)
1533         if mode != 'novice':
1534             Checkbutton(optwin, text="Flush seen messages before retrieval", 
1535                     variable=self.flush).pack(side=TOP, anchor=W)
1536             Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply", 
1537                     variable=self.rewrite).pack(side=TOP, anchor=W)
1538             Checkbutton(optwin, text="Force CR/LF at end of each line",
1539                     variable=self.forcecr).pack(side=TOP, anchor=W)
1540             Checkbutton(optwin, text="Strip CR from end of each line",
1541                     variable=self.stripcr).pack(side=TOP, anchor=W)
1542             Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1543                     variable=self.pass8bits).pack(side=TOP, anchor=W)
1544             Checkbutton(optwin, text="Undo MIME armoring on header and body",
1545                     variable=self.mimedecode).pack(side=TOP, anchor=W)
1546             Checkbutton(optwin, text="Drop Status lines from forwarded messages", 
1547                     variable=self.dropstatus).pack(side=TOP, anchor=W)
1548             Checkbutton(optwin, text="Drop Delivered-To lines from forwarded messages", 
1549                     variable=self.dropdelivered).pack(side=TOP, anchor=W)
1550         optwin.pack(fill=X)
1551
1552         if mode != 'novice':
1553             limwin = Frame(rightwin, relief=RAISED, bd=5)
1554             Label(limwin, text="Resource Limits").pack(side=TOP)
1555             LabeledEntry(limwin, 'Message size limit:',
1556                       self.limit, '30').pack(side=TOP, fill=X)
1557             LabeledEntry(limwin, 'Size warning interval:',
1558                       self.warnings, '30').pack(side=TOP, fill=X)
1559             LabeledEntry(limwin, 'Max messages to fetch per poll:',
1560                       self.fetchlimit, '30').pack(side=TOP, fill=X)
1561             LabeledEntry(limwin, 'Max messages to forward per poll:',
1562                       self.batchlimit, '30').pack(side=TOP, fill=X)
1563             if self.parent.server.protocol != 'ETRN':
1564                 LabeledEntry(limwin, 'Interval between expunges:',
1565                              self.expunge, '30').pack(side=TOP, fill=X)
1566             Checkbutton(limwin, text="Idle after each poll (IMAP only)", 
1567                     variable=self.idle).pack(side=TOP, anchor=W)
1568             limwin.pack(fill=X)
1569
1570             if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1571                 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1572                 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1573                 ListEdit("New folder:", self.user.mailboxes,
1574                          None, None, foldwin, None)
1575                 foldwin.pack(fill=X, anchor=N)
1576
1577         if mode != 'novice':
1578             rightwin.pack(side=LEFT)
1579         else:
1580             self.pack()
1581
1582
1583 #
1584 # Top-level window that offers either novice or expert mode
1585 # (but not both at once; it disappears when one is selected).
1586 #
1587
1588 class Configurator(Frame):
1589     def __init__(self, outfile, master, onexit, parent):
1590         Frame.__init__(self, master)
1591         self.outfile = outfile
1592         self.onexit = onexit
1593         self.parent = parent
1594         self.master.title('fetchmail configurator');
1595         self.master.iconname('fetchmail configurator');
1596         Pack.config(self)
1597         self.keepalive = []     # Use this to anchor the PhotoImage object
1598         make_icon_window(self, fetchmail_icon)
1599
1600         Message(self, text="""
1601 Use `Novice Configuration' for basic fetchmail setup;
1602 with this, you can easily set up a single-drop connection
1603 to one remote mail server.
1604 """, width=600).pack(side=TOP)
1605         Button(self, text='Novice Configuration',
1606                                 fg='blue', command=self.novice).pack()
1607
1608         Message(self, text="""
1609 Use `Expert Configuration' for advanced fetchmail setup,
1610 including multiple-site or multidrop connections.
1611 """, width=600).pack(side=TOP)
1612         Button(self, text='Expert Configuration',
1613                                 fg='blue', command=self.expert).pack()
1614
1615         Message(self, text="""
1616 Or you can just select `Quit' to leave the configurator now and
1617 return to the main panel.
1618 """, width=600).pack(side=TOP)
1619         Button(self, text='Quit', fg='blue', command=self.leave).pack()
1620         master.protocol("WM_DELETE_WINDOW", self.leave)
1621
1622     def novice(self):
1623         self.master.destroy()
1624         ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1625
1626     def expert(self):
1627         self.master.destroy()
1628         ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1629
1630     def leave(self):
1631         self.master.destroy()
1632         self.onexit()
1633
1634 # Run a command in a scrolling text widget, displaying its output
1635
1636 class RunWindow(Frame):
1637     def __init__(self, command, master, parent):
1638         Frame.__init__(self, master)
1639         self.master = master
1640         self.master.title('fetchmail run window');
1641         self.master.iconname('fetchmail run window');
1642         Pack.config(self)
1643         Label(self,
1644                 text="Running "+command, 
1645                 bd=2).pack(side=TOP, pady=10)
1646         self.keepalive = []     # Use this to anchor the PhotoImage object
1647         make_icon_window(self, fetchmail_icon)
1648
1649         # This is a scrolling text window
1650         textframe = Frame(self)
1651         scroll = Scrollbar(textframe)
1652         self.textwidget = Text(textframe, setgrid=TRUE)
1653         textframe.pack(side=TOP, expand=YES, fill=BOTH)
1654         self.textwidget.config(yscrollcommand=scroll.set)
1655         self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1656         scroll.config(command=self.textwidget.yview)
1657         scroll.pack(side=RIGHT, fill=BOTH)
1658         textframe.pack(side=TOP)
1659
1660         Button(self, text='Quit', fg='blue', command=self.leave).pack()
1661
1662         self.update()   # Draw widget before executing fetchmail
1663
1664         child_stdout = os.popen(command + " 2>&1", "r")
1665         while 1:
1666             ch = child_stdout.read(1)
1667             if not ch:
1668                 break
1669             self.textwidget.insert(END, ch)
1670         self.textwidget.insert(END, "Done.")
1671         self.textwidget.see(END);
1672
1673     def leave(self):
1674         Widget.destroy(self.master)
1675
1676 # Here's where we choose either configuration or launching
1677
1678 class MainWindow(Frame):
1679     def __init__(self, outfile, master=None):
1680         Frame.__init__(self, master)
1681         self.outfile = outfile
1682         self.master.title('fetchmail launcher');
1683         self.master.iconname('fetchmail launcher');
1684         Pack.config(self)
1685         Label(self,
1686                 text='Fetchmailconf ' + version, 
1687                 bd=2).pack(side=TOP, pady=10)
1688         self.keepalive = []     # Use this to anchor the PhotoImage object
1689         make_icon_window(self, fetchmail_icon)
1690         self.debug = 0
1691
1692         ## Test icon display with the following:
1693         # icon_image = PhotoImage(data=fetchmail_icon)
1694         # Label(self, image=icon_image).pack(side=TOP, pady=10)
1695         # self.keepalive.append(icon_image)
1696
1697         Message(self, text="""
1698 Use `Configure fetchmail' to tell fetchmail about the remote
1699 servers it should poll (the host name, your username there,
1700 whether to use POP or IMAP, and so forth).
1701 """, width=600).pack(side=TOP)
1702         self.configbutton = Button(self, text='Configure fetchmail',
1703                                 fg='blue', command=self.configure)
1704         self.configbutton.pack()
1705
1706         Message(self, text="""
1707 Use `Test fetchmail' to run fetchmail with debugging enabled.
1708 This is a good way to test out a new configuration.
1709 """, width=600).pack(side=TOP)
1710         Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1711
1712         Message(self, text="""
1713 Use `Run fetchmail' to run fetchmail in foreground.
1714 Progress  messages will be shown, but not debug messages.
1715 """, width=600).pack(side=TOP)
1716         Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1717
1718         Message(self, text="""
1719 Or you can just select `Quit' to exit the launcher now.
1720 """, width=600).pack(side=TOP)
1721         Button(self, text='Quit', fg='blue', command=self.leave).pack()
1722
1723     def configure(self):
1724         self.configbutton.configure(state=DISABLED)
1725         Configurator(self.outfile, Toplevel(),
1726                      lambda self=self: self.configbutton.configure(state=NORMAL),
1727                      self)
1728     def test(self):
1729         RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1730
1731     def run(self):
1732         RunWindow("fetchmail -d0", Toplevel(), self)
1733
1734     def leave(self):
1735         self.quit()
1736
1737 # Functions for turning a dictionary into an instantiated object tree.
1738
1739 def intersect(list1, list2):
1740 # Compute set intersection of lists
1741     res = []
1742     for x in list1:
1743         if x in list2:
1744             res.append(x)
1745     return res
1746
1747 def setdiff(list1, list2):
1748 # Compute set difference of lists
1749     res = []
1750     for x in list1:
1751         if not x in list2:
1752             res.append(x)
1753     return res
1754
1755 def copy_instance(toclass, fromdict):
1756 # Initialize a class object of given type from a conformant dictionary.
1757     for fld in fromdict.keys():
1758         if not fld in dictmembers:
1759             dictmembers.append(fld)
1760 # The `optional' fields are the ones we can ignore for purposes of
1761 # conformability checking; they'll still get copied if they are
1762 # present in the dictionary.
1763     optional = ('interface', 'monitor',
1764                 'netsec',
1765                 'ssl', 'sslkey', 'sslcert',
1766                 'showdots')
1767     class_sig = setdiff(toclass.__dict__.keys(), optional)
1768     class_sig.sort()
1769     dict_keys = setdiff(fromdict.keys(), optional)
1770     dict_keys.sort()
1771     common = intersect(class_sig, dict_keys)
1772     if 'typemap' in class_sig: 
1773         class_sig.remove('typemap')
1774     if tuple(class_sig) != tuple(dict_keys):
1775         print "Fields don't match what fetchmailconf expected:"
1776 #       print "Class signature: " + `class_sig`
1777 #       print "Dictionary keys: " + `dict_keys`
1778         diff = setdiff(class_sig, common)
1779         if diff:
1780             print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1781         diff = setdiff(dict_keys, common)
1782         if diff:
1783             print "Not matched in dictionary keys: " + `diff`
1784         sys.exit(1)
1785     else:
1786         for x in fromdict.keys():
1787             setattr(toclass, x, fromdict[x])
1788
1789 #
1790 # And this is the main sequence.  How it works:  
1791 #
1792 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1793 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1794 # Run execfile on the file to pull fetchmailrc into Python global space.
1795 # You don't want static data, though; you want, instead, a tree of objects
1796 # with the same data members and added appropriate methods.
1797 #
1798 # This is what the copy_instance function() is for.  It tries to copy a
1799 # dictionary field by field into a class, aborting if the class and dictionary
1800 # have different data members (except for any typemap member in the class;
1801 # that one is strictly for use by the MyWidget supperclass).
1802 #
1803 # Once the object tree is set up, require user to choose novice or expert
1804 # mode and instantiate an edit object for the configuration.  Class methods
1805 # will take it all from there.
1806 #
1807 # Options (not documented because they're for fetchmailconf debuggers only):
1808 # -d: Read the configuration and dump it to stdout before editing.  Dump
1809 #     the edited result to stdout as well.
1810 # -f: specify the run control file to read.
1811
1812 if __name__ == '__main__': 
1813
1814     if not os.environ.has_key("DISPLAY"):
1815         print "fetchmailconf must be run under X"
1816         sys.exit(1)
1817
1818     fetchmail_icon = """
1819 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1820 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1821 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1822 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1823 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1824 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1825 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1826 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1827 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1828 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1829 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1830 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1831 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1832 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1833 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1834 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1835 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1836 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1837 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1838 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1839 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1840 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1841 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1842 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1843 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1844 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1845 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1846 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1847 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1848 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1849 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1850 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1851 """
1852 # The base64 data in the string above was generated by the following procedure:
1853 #
1854 # import base64
1855 # print base64.encodestring(open("fetchmail.gif", "rb").read())
1856 #
1857
1858     # Process options
1859     (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1860     dump = rcfile = None;
1861     for (switch, val) in options:
1862         if (switch == '-d'):
1863             dump = TRUE
1864         elif (switch == '-f'):
1865             rcfile = val
1866
1867     # Get client host's FQDN
1868     hostname = socket.gethostbyaddr(socket.gethostname())[0]
1869
1870     # Compute defaults
1871     ConfigurationDefaults = Configuration()
1872     ServerDefaults = Server()
1873     UserDefaults = User()
1874
1875     # Read the existing configuration.  We set the umask to 077 to make sure
1876     # that group & other read/write permissions are shut off -- we wouldn't
1877     # want crackers to snoop password information out of the tempfile.
1878     tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1879     if rcfile:
1880         cmd = "umask 077; fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1881     else:
1882         cmd = "umask 077; fetchmail --configdump --nosyslog >" + tmpfile
1883         
1884     try:
1885         s = os.system(cmd)
1886         if s != 0:
1887             print "`" + cmd + "' run failure, status " + `s`
1888             raise SystemExit
1889     except:
1890         print "Unknown error while running fetchmail --configdump"
1891         os.remove(tmpfile)
1892         sys.exit(1)
1893
1894     try:
1895         execfile(tmpfile)
1896     except:
1897         print "Can't read configuration output of fetchmail --configdump."
1898         os.remove(tmpfile)
1899         sys.exit(1)
1900         
1901     os.remove(tmpfile)
1902
1903     # The tricky part -- initializing objects from the configuration global
1904     # `Configuration' is the top level of the object tree we're going to mung.
1905     # The dictmembers list is used to track the set of fields the dictionary
1906     # contains; in particular, we can use it to tell whether things like the
1907     # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1908     dictmembers = []
1909     Fetchmailrc = Configuration()
1910     copy_instance(Fetchmailrc, fetchmailrc)
1911     Fetchmailrc.servers = [];
1912     for server in fetchmailrc['servers']:
1913         Newsite = Server()
1914         copy_instance(Newsite, server)
1915         Fetchmailrc.servers.append(Newsite)
1916         Newsite.users = [];
1917         for user in server['users']:
1918             Newuser = User()
1919             copy_instance(Newuser, user)
1920             Newsite.users.append(Newuser)
1921
1922     # We may want to display the configuration and quit
1923     if dump:
1924         print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1925
1926     # The theory here is that -f alone sets the rcfile location,
1927     # but -d and -f together mean the new configuration should go to stdout.
1928     if not rcfile and not dump:
1929         rcfile = os.environ["HOME"] + "/.fetchmailrc"
1930
1931     # OK, now run the configuration edit
1932     root = MainWindow(rcfile)
1933     root.mainloop()
1934
1935 # The following sets edit modes for GNU EMACS
1936 # Local Variables:
1937 # mode:python
1938 # End: