]> Pileus Git - ~andy/fetchmail/blob - fetchmailconf
Added warning to differs.
[~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
1112 # The greeting line on the server known to be buggy is:
1113 # +OK POP3 server ready (running FTGate V2, 2, 1, 0 Jun 21 1999 09:55:01)
1114 #
1115             if string.find(greetline, "FTGate") > 0:
1116                 warnings = warnings + """
1117 This POP server has a weird bug; it says OK twice in response to TOP.
1118 Its response to RETR is normal, so use the `fetchall'.
1119
1120 """
1121
1122             if string.find(greetline, "POP-Max") > 0:
1123                 warnings = warnings + """
1124 The Mail Max server screws up on mail with attachments.  It reports the
1125 message size with attachments included, but doesn't downloasd them on a
1126 RETR or TOP.  You should get rid of it -- and the brain-dead NT server
1127 it rode in on. 
1128
1129 """
1130
1131             if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1132                 warnings = warnings + """
1133 This appears to be an old version of the UC Davis POP server.  These are
1134 dangerously unreliable (among other problems, they may drop your mailbox
1135 on the floor if your connection is interrupted during the session).
1136
1137 It is strongly recommended that you find a better POP3 server.  The fetchmail
1138 FAQ includes pointers to good ones.
1139
1140 """
1141             if string.find(greetline, "usa.net") > 0:
1142                 warnings = warnings + """
1143 You appear to be using USA.NET's free mail service.  Their POP3 servers
1144 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1145 fetchmail can compensate.  They seem to require that fetchall be switched on
1146 (otherwise you won't necessarily see all your mail, not even new mail).
1147 They also botch the TOP command the fetchmail normally uses for retrieval
1148 (it only retrieves about 10 lines rather than the number specified).
1149 Turning on fetchall will disable the use of TOP.
1150
1151 Therefore, it is strongly recommended that you turn on `fetchall' on all
1152 user entries associated with this server.  
1153
1154 """
1155             if string.find(greetline, "OpenMail") > 0:
1156                 warnings = warnings + """
1157 You appear to be using some version of HP OpenMail.  Many versions of
1158 OpenMail do not process the "TOP" command correctly; the symptom is that
1159 only the header and first line of each message is retrieved.  To work
1160 around this bug, turn on `fetchall' on all user entries associated with
1161 this server.  
1162
1163 """
1164             if string.find(greetline, "TEMS POP3") > 0:
1165                 warnings = warnings + """
1166 Your POP3 server has "TEMS" in its header line.  At least one such
1167 server does not process the "TOP" command correctly; the symptom is
1168 that fetchmail hangs when trying to retrieve mail.  To work around
1169 this bug, turn on `fetchall' on all user entries associated with this
1170 server.
1171
1172 """
1173             if string.find(greetline, "GroupWise") > 0:
1174                 warnings = warnings + """
1175 The Novell GroupWise IMAP server would be better named GroupFoolish;
1176 it is (according to the designer of IMAP) unusably broken.  Among
1177 other things, it doesn't include a required content length in its
1178 BODY[TEXT] response.<p>
1179
1180 Fetchmail works around this problem, but we strongly recommend voting
1181 with your dollars for a server that isn't brain-dead.  If you stick
1182 with code as shoddy as GroupWise seems to be, you will probably pay
1183 for it with other problems.<p>
1184
1185 """
1186             if string.find(greetline, "sprynet.com") > 0:
1187                 warnings = warnings + """
1188 You appear to be using a SpryNet server.  In mid-1999 it was reported that
1189 the SpryNet TOP command marks messages seen.  Therefore, for proper error
1190 recovery in the event of a line drop, it is strongly recommended that you
1191 turn on `fetchall' on all user entries associated with this server.  
1192
1193 """
1194
1195 # Steve VanDevender <stevev@efn.org> writes:
1196 # The only system I have seen this happen with is cucipop-1.31
1197 # under SunOS 4.1.4.  cucipop-1.31 runs fine on at least Solaris
1198 # 2.x and probably quite a few other systems.  It appears to be a
1199 # bug or bad interaction with the SunOS realloc() -- it turns out
1200 # that internally cucipop does allocate a certain data structure in
1201 # multiples of 16, using realloc() to bump it up to the next
1202 # multiple if it needs more.
1203
1204 # The distinctive symptom is that when there are 16 messages in the
1205 # inbox, you can RETR and DELE all 16 messages successfully, but on
1206 # QUIT cucipop returns something like "-ERR Error locking your
1207 # mailbox" and aborts without updating it.
1208
1209 # The cucipop banner looks like:
1210
1211 # +OK Cubic Circle's v1.31 1998/05/13 POP3 ready <6229000062f95036@wakko>
1212 #
1213             if string.find(greetline, "Cubic Circle") > 0:
1214                 warnings = warnings + """
1215 I see your server is running cucipop.  Better make sure the server box
1216 isn't a SunOS 4.1.4 machine; cucipop tickles a bug in SunOS realloc()
1217 under that version, and doesn't cope with the result gracefully.  Newer
1218 SunOS and Solaris machines run cucipop OK.
1219
1220 """
1221             if string.find(greetline, "QPOP") > 0:
1222                 warnings = warnings + """
1223 This appears to be a version of Eudora qpopper.  That's good.  Fetchmail
1224 knows all about qpopper.  However, be aware that the 2.53 version of
1225 qpopper does something odd that causes fetchmail to hang with a socket
1226 error on very large messages.  This is probably not a fetchmail bug, as
1227 it has been observed with fetchpop.  The fix is to upgrade to qpopper
1228 3.0beta or a more recent version.  Better yet, switch to IMAP.
1229
1230 """
1231             if string.find(greetline, "Imail") > 0:
1232                 warnings = warnings + """
1233 We've seen a bug report indicating that this IMAP server (at least as of
1234 version 5.0.7) returns an invalid body size for messages with MIME
1235 attachments; the effect is to drop the attachments on the floor.  We
1236 recommend you upgrade to a non-broken IMAP server.
1237
1238 """
1239             closebrak = string.find(greetline, ">")
1240             if  closebrak > 0 and greetline[closebrak+1] == "\r":
1241                 warnings = warnings + """
1242 It looks like you could use APOP on this server and avoid sending it your
1243 password in clear.  You should talk to the mailserver administrator about
1244 this.
1245
1246 """
1247             if string.find(greetline, "IMAP2bis") > 0:
1248                 warnings = warnings + """
1249 IMAP2bis servers have a minor problem; they can't peek at messages without
1250 marking them seen.  If you take a line hit during the retrieval, the 
1251 interrupted message may get left on the server, marked seen.
1252
1253 To work around this, it is recommended that you set the `fetchall'
1254 option on all user entries associated with this server, so any stuck
1255 mail will be retrieved next time around.
1256
1257 """
1258             if string.find(greetline, "POP3 Server Ready") > 0:
1259                 warnings = warnings + """
1260 Some server that uses this greeting line has been observed to choke on
1261 TOP %d 99999999.  Use the fetchall option. if necessary, to force RETR.
1262 """
1263
1264             if string.find(greetline, "Netscape IMAP4rev1 Service 3.6") > 0:
1265                 warnings = warnings + """
1266 This server violates the RFC2060 requirement that a BODY[TEXT] fetch should
1267 set the messages's Seen flag.  As a result, if you use the keep option the
1268 same messages will be downloaded over and over.
1269 """
1270
1271             if string.find(greetline, "IMAP4rev1") > 0:
1272                 warnings = warnings + """
1273 I see an IMAP4rev1 server.  Excellent.  This is (a) the best kind of
1274 remote-mail server, and (b) the one the fetchmail author uses.  Fetchmail
1275 has therefore been extremely well tested with this class of server.
1276 """
1277             if warnings == '':
1278                 warnings = warnings + """
1279 Fetchmail doesn't know anything special about this server type.
1280 """
1281
1282             # Display success window with warnings
1283             title = "Autoprobe of " + realhost + " succeeded"
1284             confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1285             self.protocol.set(protocol)
1286         confwin.title(title) 
1287         confwin.iconname(title)
1288         Label(confwin, text=title).pack()
1289         Message(confwin, text=confirm, width=600).pack()
1290         Button(confwin, text='Done', 
1291                    command=lambda x=confwin: Widget.destroy(x), bd=2).pack()
1292         
1293 #
1294 # User editing stuff
1295 #
1296
1297 userhelp = {
1298     'title' : 'User option help',
1299     'banner': 'User options',
1300     'text' : """
1301 You may use this panel to set options
1302 that may differ between individual
1303 users on your site.
1304
1305 Once you have a user configuration set
1306 up as you like it, you can select `OK' to
1307 store it in the user list maintained in
1308 the site configuration window.
1309
1310 If you wish to discard the changes you have
1311 made to user options, select `Quit'.
1312 """}
1313
1314 localhelp = {
1315     'title' : 'Local name help',
1316     'banner': 'Local names',
1317     'text' : """
1318 The local name(s) in a user entry are the
1319 people on the client machine who should
1320 receive mail from the poll described.
1321
1322 Note: if a user entry has more than one
1323 local name, messages will be retrieved
1324 in multidrop mode.  This complicates
1325 the configuration issues; see the manual
1326 page section on multidrop mode.
1327 """}
1328
1329 class UserEdit(Frame, MyWidget):
1330     def __init__(self, username, parent):
1331         self.parent = parent
1332         self.user = None
1333         for user in parent.server.users:
1334             if user.remote == username:
1335                 self.user = user
1336         if self.user == None:
1337             self.user = User()
1338             self.user.remote = username
1339             self.user.localnames = [username]
1340             parent.server.users.append(self.user)
1341
1342     def edit(self, mode, master=None):
1343         Frame.__init__(self, master)
1344         Pack.config(self)
1345         self.master.title('Fetchmail user ' + self.user.remote
1346                           + ' querying ' + self.parent.server.pollname);
1347         self.master.iconname('Fetchmail user ' + self.user.remote);
1348         self.post(User, 'user')
1349         self.makeWidgets(mode, self.parent.server.pollname)
1350         self.keepalive = []     # Use this to anchor the PhotoImage object
1351         make_icon_window(self, fetchmail_gif)
1352 #       self.grab_set()
1353 #       self.focus_set()
1354 #       self.wait_window()
1355         return self
1356
1357     def destruct(self):
1358         del self.parent.subwidgets[self.user.remote]
1359         Widget.destroy(self.master)
1360
1361     def nosave(self):
1362         if ConfirmQuit(self, 'user option editing'):
1363             self.destruct()
1364
1365     def save(self):
1366         self.fetch(User, 'user')
1367         self.destruct()
1368
1369     def makeWidgets(self, mode, servername):
1370         dispose_window(self,
1371                         "User options for " + self.user.remote + " querying " + servername,
1372                         userhelp)
1373
1374         if mode != 'novice':
1375             leftwin = Frame(self);
1376         else:
1377             leftwin = self
1378
1379         secwin = Frame(leftwin, relief=RAISED, bd=5)
1380         Label(secwin, text="Authentication").pack(side=TOP)
1381         LabeledEntry(secwin, 'Password:',
1382                       self.password, '12').pack(side=TOP, fill=X)
1383         secwin.pack(fill=X, anchor=N)
1384
1385         if 'ssl' in feature_options or 'ssl' in dictmembers:
1386             sslwin = Frame(leftwin, relief=RAISED, bd=5)
1387             Checkbutton(sslwin, text="Use SSL?",
1388                         variable=self.ssl).pack(side=TOP, fill=X)
1389             LabeledEntry(sslwin, 'SSL key:',
1390                          self.sslkey, '14').pack(side=TOP, fill=X)
1391             LabeledEntry(sslwin, 'SSL certificate:',
1392                          self.sslcert, '14').pack(side=TOP, fill=X)
1393             sslwin.pack(fill=X, anchor=N)
1394
1395         names = Frame(leftwin, relief=RAISED, bd=5)
1396         Label(names, text="Local names").pack(side=TOP)
1397         ListEdit("New name: ",
1398                      self.user.localnames, None, None, names, localhelp)
1399         names.pack(fill=X, anchor=N)
1400
1401         if mode != 'novice':
1402             targwin = Frame(leftwin, relief=RAISED, bd=5)
1403             Label(targwin, text="Forwarding Options").pack(side=TOP)
1404             Label(targwin, text="Listeners to forward to").pack(side=TOP)
1405             ListEdit("New listener:",
1406                      self.user.smtphunt, None, None, targwin, None)
1407             LabeledEntry(targwin, 'Append to MAIL FROM line:',
1408                      self.smtpaddress, '26').pack(side=TOP, fill=X)
1409             LabeledEntry(targwin, 'Connection setup command:',
1410                      self.preconnect, '26').pack(side=TOP, fill=X)
1411             LabeledEntry(targwin, 'Connection wrapup command:',
1412                      self.postconnect, '26').pack(side=TOP, fill=X)
1413             LabeledEntry(targwin, 'Local delivery agent:',
1414                      self.mda, '26').pack(side=TOP, fill=X)
1415             LabeledEntry(targwin, 'BSMTP output file:',
1416                      self.bsmtp, '26').pack(side=TOP, fill=X)
1417             LabeledEntry(targwin, 'Listener spam-block codes:',
1418                      self.antispam, '26').pack(side=TOP, fill=X)
1419             LabeledEntry(targwin, 'Pass-through properties:',
1420                      self.properties, '26').pack(side=TOP, fill=X)
1421             Checkbutton(targwin, text="Use LMTP?",
1422                         variable=self.lmtp).pack(side=TOP, fill=X)
1423             targwin.pack(fill=X, anchor=N)
1424
1425         if mode != 'novice':
1426             leftwin.pack(side=LEFT, fill=X, anchor=N)
1427             rightwin = Frame(self)
1428         else:
1429             rightwin = self
1430
1431         optwin = Frame(rightwin, relief=RAISED, bd=5)
1432         Label(optwin, text="Processing Options").pack(side=TOP)
1433         Checkbutton(optwin, text="Suppress deletion of messages after reading",
1434                     variable=self.keep).pack(side=TOP, anchor=W)
1435         Checkbutton(optwin, text="Fetch old messages as well as new",
1436                     variable=self.fetchall).pack(side=TOP, anchor=W)
1437         if mode != 'novice':
1438             Checkbutton(optwin, text="Flush seen messages before retrieval", 
1439                     variable=self.flush).pack(side=TOP, anchor=W)
1440             Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply", 
1441                     variable=self.rewrite).pack(side=TOP, anchor=W)
1442             Checkbutton(optwin, text="Force CR/LF at end of each line",
1443                     variable=self.forcecr).pack(side=TOP, anchor=W)
1444             Checkbutton(optwin, text="Strip CR from end of each line",
1445                     variable=self.stripcr).pack(side=TOP, anchor=W)
1446             Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1447                     variable=self.pass8bits).pack(side=TOP, anchor=W)
1448             Checkbutton(optwin, text="Undo MIME armoring on header and body",
1449                     variable=self.mimedecode).pack(side=TOP, anchor=W)
1450             Checkbutton(optwin, text="Drop Status lines from forwarded messages", 
1451                     variable=self.dropstatus).pack(side=TOP, anchor=W)
1452         optwin.pack(fill=X)
1453
1454         if mode != 'novice':
1455             limwin = Frame(rightwin, relief=RAISED, bd=5)
1456             Label(limwin, text="Resource Limits").pack(side=TOP)
1457             LabeledEntry(limwin, 'Message size limit:',
1458                       self.limit, '30').pack(side=TOP, fill=X)
1459             LabeledEntry(limwin, 'Size warning interval:',
1460                       self.warnings, '30').pack(side=TOP, fill=X)
1461             LabeledEntry(limwin, 'Max messages to fetch per poll:',
1462                       self.fetchlimit, '30').pack(side=TOP, fill=X)
1463             LabeledEntry(limwin, 'Max messages to forward per poll:',
1464                       self.batchlimit, '30').pack(side=TOP, fill=X)
1465             if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1466                 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1467                              self.expunge, '30').pack(side=TOP, fill=X)
1468             limwin.pack(fill=X)
1469
1470             if self.parent.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1471                 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1472                 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1473                 ListEdit("New folder:", self.user.mailboxes,
1474                          None, None, foldwin, None)
1475                 foldwin.pack(fill=X, anchor=N)
1476
1477         if mode != 'novice':
1478             rightwin.pack(side=LEFT)
1479         else:
1480             self.pack()
1481
1482
1483 #
1484 # Top-level window that offers either novice or expert mode
1485 # (but not both at once; it disappears when one is selected).
1486 #
1487
1488 class Configurator(Frame):
1489     def __init__(self, outfile, master, onexit, parent):
1490         Frame.__init__(self, master)
1491         self.outfile = outfile
1492         self.onexit = onexit
1493         self.parent = parent
1494         self.master.title('fetchmail configurator');
1495         self.master.iconname('fetchmail configurator');
1496         Pack.config(self)
1497         self.keepalive = []     # Use this to anchor the PhotoImage object
1498         make_icon_window(self, fetchmail_gif)
1499
1500         Message(self, text="""
1501 Use `Novice Configuration' for basic fetchmail setup;
1502 with this, you can easily set up a single-drop connection
1503 to one remote mail server.
1504 """, width=600).pack(side=TOP)
1505         Button(self, text='Novice Configuration',
1506                                 fg='blue', command=self.novice).pack()
1507
1508         Message(self, text="""
1509 Use `Expert Configuration' for advanced fetchmail setup,
1510 including multiple-site or multidrop connections.
1511 """, width=600).pack(side=TOP)
1512         Button(self, text='Expert Configuration',
1513                                 fg='blue', command=self.expert).pack()
1514
1515         Message(self, text="""
1516 Or you can just select `Quit' to leave the configurator now and
1517 return to the main panel.
1518 """, width=600).pack(side=TOP)
1519         Button(self, text='Quit', fg='blue', command=self.leave).pack()
1520         master.protocol("WM_DELETE_WINDOW", self.leave)
1521
1522     def novice(self):
1523         self.master.destroy()
1524         ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('novice')
1525
1526     def expert(self):
1527         self.master.destroy()
1528         ConfigurationEdit(Fetchmailrc, self.outfile, Toplevel(), self.onexit).edit('expert')
1529
1530     def leave(self):
1531         self.master.destroy()
1532         self.onexit()
1533
1534 # Run a command an a scrolling text widget, displaying its output
1535
1536 class RunWindow(Frame):
1537     def __init__(self, command, master, parent):
1538         Frame.__init__(self, master)
1539         self.master = master
1540         self.master.title('fetchmail run window');
1541         self.master.iconname('fetchmail run window');
1542         Pack.config(self)
1543         Label(self,
1544                 text="Running "+command, 
1545                 bd=2).pack(side=TOP, pady=10)
1546         self.keepalive = []     # Use this to anchor the PhotoImage object
1547         make_icon_window(self, fetchmail_gif)
1548
1549         # This is a scrolling text window
1550         textframe = Frame(self)
1551         scroll = Scrollbar(textframe)
1552         self.textwidget = Text(textframe, setgrid=TRUE)
1553         textframe.pack(side=TOP, expand=YES, fill=BOTH)
1554         self.textwidget.config(yscrollcommand=scroll.set)
1555         self.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
1556         scroll.config(command=self.textwidget.yview)
1557         scroll.pack(side=RIGHT, fill=BOTH)
1558         textframe.pack(side=TOP)
1559
1560         Button(self, text='Quit', fg='blue', command=self.leave).pack()
1561
1562         self.update()   # Draw widget before executing fetchmail
1563
1564         child_stdout = os.popen(command + " 2>&1", "r")
1565         while 1:
1566             ch = child_stdout.read(1)
1567             if not ch:
1568                 break
1569             self.textwidget.insert(END, ch)
1570         self.textwidget.insert(END, "Done.")
1571         self.textwidget.see(END);
1572
1573     def leave(self):
1574         Widget.destroy(self.master)
1575
1576 # Here's where we choose either configuration or launching
1577
1578 class MainWindow(Frame):
1579     def __init__(self, outfile, master=None):
1580         Frame.__init__(self, master)
1581         self.outfile = outfile
1582         self.master.title('fetchmail launcher');
1583         self.master.iconname('fetchmail launcher');
1584         Pack.config(self)
1585         Label(self,
1586                 text='Fetchmailconf ' + version, 
1587                 bd=2).pack(side=TOP, pady=10)
1588         self.keepalive = []     # Use this to anchor the PhotoImage object
1589         make_icon_window(self, fetchmail_gif)
1590         self.debug = 0
1591
1592         Message(self, text="""
1593 Use `Configure fetchmail' to tell fetchmail about the remote
1594 servers it should poll (the host name, your username there,
1595 whether to use POP or IMAP, and so forth).
1596 """, width=600).pack(side=TOP)
1597         self.configbutton = Button(self, text='Configure fetchmail',
1598                                 fg='blue', command=self.configure)
1599         self.configbutton.pack()
1600
1601         Message(self, text="""
1602 Use `Test fetchmail' to run fetchmail with debugging enabled.
1603 This is a good way to test out a new configuration.
1604 """, width=600).pack(side=TOP)
1605         Button(self, text='Test fetchmail',fg='blue', command=self.test).pack()
1606
1607         Message(self, text="""
1608 Use `Run fetchmail' to run fetchmail in foreground.
1609 Progress  messages will be shown, but not debug messages.
1610 """, width=600).pack(side=TOP)
1611         Button(self, text='Run fetchmail', fg='blue', command=self.run).pack()
1612
1613         Message(self, text="""
1614 Or you can just select `Quit' to exit the launcher now.
1615 """, width=600).pack(side=TOP)
1616         Button(self, text='Quit', fg='blue', command=self.leave).pack()
1617
1618     def configure(self):
1619         self.configbutton.configure(state=DISABLED)
1620         Configurator(self.outfile, Toplevel(),
1621                      lambda self=self: self.configbutton.configure(state=NORMAL),
1622                      self)
1623
1624     def test(self):
1625         RunWindow("fetchmail -d0 -v --nosyslog", Toplevel(), self)
1626
1627     def run(self):
1628         RunWindow("fetchmail -d0", Toplevel(), self)
1629
1630     def leave(self):
1631         self.quit()
1632
1633 # Functions for turning a dictionary into an instantiated object tree.
1634
1635 def intersect(list1, list2):
1636 # Compute set intersection of lists
1637     res = []
1638     for x in list1:
1639         if x in list2:
1640             res.append(x)
1641     return res
1642
1643 def setdiff(list1, list2):
1644 # Compute set difference of lists
1645     res = []
1646     for x in list1:
1647         if not x in list2:
1648             res.append(x)
1649     return res
1650
1651 def copy_instance(toclass, fromdict):
1652 # Initialize a class object of given type from a conformant dictionary.
1653     for fld in fromdict.keys():
1654         if not fld in dictmembers:
1655             dictmembers.append(fld)
1656 # The `optional' fields are the ones we can ignore for purposes of
1657 # conformability checking; they'll still get copied if they are
1658 # present in the dictionary.
1659     optional = ('interface', 'monitor', 'netsec', 'ssl', 'sslkey', 'sslcert')
1660     class_sig = setdiff(toclass.__dict__.keys(), optional)
1661     class_sig.sort()
1662     dict_keys = setdiff(fromdict.keys(), optional)
1663     dict_keys.sort()
1664     common = intersect(class_sig, dict_keys)
1665     if 'typemap' in class_sig: 
1666         class_sig.remove('typemap')
1667     if tuple(class_sig) != tuple(dict_keys):
1668         print "Fields don't match what fetchmailconf expected:"
1669 #       print "Class signature: " + `class_sig`
1670 #       print "Dictionary keys: " + `dict_keys`
1671         diff = setdiff(class_sig, common)
1672         if diff:
1673             print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1674         diff = setdiff(dict_keys, common)
1675         if diff:
1676             print "Not matched in dictionary keys: " + `diff`
1677         sys.exit(1)
1678     else:
1679         for x in fromdict.keys():
1680             setattr(toclass, x, fromdict[x])
1681
1682 #
1683 # And this is the main sequence.  How it works:  
1684 #
1685 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1686 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1687 # Run execfile on the file to pull fetchmailrc into Python global space.
1688 # You don't want static data, though; you want, instead, a tree of objects
1689 # with the same data members and added appropriate methods.
1690 #
1691 # This is what the copy_instance function() is for.  It tries to copy a
1692 # dictionary field by field into a class, aborting if the class and dictionary
1693 # have different data members (except for any typemap member in the class;
1694 # that one is strictly for use by the MyWidget supperclass).
1695 #
1696 # Once the object tree is set up, require user to choose novice or expert
1697 # mode and instantiate an edit object for the configuration.  Class methods
1698 # will take it all from there.
1699 #
1700 # Options (not documented because they're for fetchmailconf debuggers only):
1701 # -d: Read the configuration and dump it to stdout before editing.  Dump
1702 #     the edited result to stdout as well.
1703 # -f: specify the run control file to read.
1704
1705 if __name__ == '__main__': 
1706
1707     if not os.environ.has_key("DISPLAY"):
1708         print "fetchmailconf must be run under X"
1709         sys.exit(1)
1710
1711     fetchmail_gif = """
1712 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1713 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1714 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1715 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1716 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1717 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1718 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1719 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1720 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1721 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1722 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1723 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1724 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1725 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1726 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1727 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1728 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1729 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1730 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1731 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1732 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1733 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1734 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1735 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1736 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1737 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1738 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1739 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1740 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1741 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1742 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1743 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1744 """
1745 # Note on making icons: the above was generated by the following procedure:
1746 #
1747 # import base64
1748 # data = open("fetchmail.gif", "rb").read()
1749 # print "fetchmail_gif =\\"
1750 # print repr(base64.encodestring(data))
1751 #
1752
1753     # Process options
1754     (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1755     dump = rcfile = None;
1756     for (switch, val) in options:
1757         if (switch == '-d'):
1758             dump = TRUE
1759         elif (switch == '-f'):
1760             rcfile = val
1761
1762     # Get client host's FQDN
1763     hostname = socket.gethostbyaddr(socket.gethostname())[0]
1764
1765     # Compute defaults
1766     ConfigurationDefaults = Configuration()
1767     ServerDefaults = Server()
1768     UserDefaults = User()
1769
1770     # Read the existing configuration
1771     tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1772     if rcfile:
1773         cmd = "fetchmail -f " + rcfile + " --configdump --nosyslog >" + tmpfile
1774     else:
1775         cmd = "fetchmail --configdump --nosyslog >" + tmpfile
1776         
1777     try:
1778         s = os.system(cmd)
1779         if s != 0:
1780             print "`" + cmd + "' run failure, status " + `s`
1781             raise SystemExit
1782     except:
1783         print "Unknown error while running fetchmail --configdump"
1784         os.remove(tmpfile)
1785         sys.exit(1)
1786
1787     try:
1788         execfile(tmpfile)
1789     except:
1790         print "Can't read configuration output of fetchmail --configdump."
1791         os.remove(tmpfile)
1792         sys.exit(1)
1793         
1794     os.remove(tmpfile)
1795
1796     # The tricky part -- initializing objects from the configuration global
1797     # `Configuration' is the top level of the object tree we're going to mung.
1798     # The dictmembers list is used to track the set of fields the dictionary
1799     # contains; in particular, we can use it to tell whether things like the
1800     # monitor, interface, netsec, ssl, sslkey, or sslcert fields are present.
1801     dictmembers = []
1802     Fetchmailrc = Configuration()
1803     copy_instance(Fetchmailrc, fetchmailrc)
1804     Fetchmailrc.servers = [];
1805     for server in fetchmailrc['servers']:
1806         Newsite = Server()
1807         copy_instance(Newsite, server)
1808         Fetchmailrc.servers.append(Newsite)
1809         Newsite.users = [];
1810         for user in server['users']:
1811             Newuser = User()
1812             copy_instance(Newuser, user)
1813             Newsite.users.append(Newuser)
1814
1815     # We may want to display the configuration and quit
1816     if dump:
1817         print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1818
1819     # The theory here is that -f alone sets the rcfile location,
1820     # but -d and -f together mean the new configuration should go to stdout.
1821     if not rcfile and not dump:
1822         rcfile = os.environ["HOME"] + "/.fetchmailrc"
1823
1824     # OK, now run the configuration edit
1825     root = MainWindow(rcfile)
1826     root.mainloop()
1827
1828 # The following sets edit modes for GNU EMACS
1829 # Local Variables:
1830 # mode:python
1831 # End: