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