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