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