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