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