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