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