1#!/usr/bin/python
2#
3# NOTE: THIS PROGRAM DOES NOT WORK!
4# It is intended as a regression test source for the Python support in etags.
5# If you want a working version, you'll find it in the fetchmail distribution.
6#
7
8from Tkinter import *
9from Dialog import *
10import sys
11import time
12import os
13
14#
15# Define the data structures the GUIs will be tossing around
16#
17class Controls:
18    def __init__(self):
19	self.foreground = FALSE;	# Run in background
20	self.daemon = 300		# Default to 5-minute timeout
21	self.syslog = FALSE		# Use syslogd for logging?
22	self.logfile = None		# No logfile, initially
23
24    def __repr__(self):
25	str = "";
26	if self.syslog:
27	   str = str + ("set syslog\n")
28	elif self.logfile:
29	    str = str + ("set logfile \"%s\"\n" % (self.logfile,));
30	if not self.foreground and self.daemon:
31	    str = str + ("set daemon %s\n" % (self.daemon,))
32	return str + "\n"
33
34    def __str__(self):
35	return "[Server: " + repr(self) + "]"
36
37class Server:
38    def __init__(self):
39	self.pollname = None		# Poll label
40	self.via = None			# True name of host
41	self.active = TRUE		# Poll status
42	self.interval = 0		# Skip interval
43	self.protocol = 'auto'		# Default to auto protocol
44	self.port = 0			# Port number to use
45	self.uidl = FALSE		# Don't use RFC1725 UIDLs by default
46	self.auth = "password"		# Default to password authentication
47	self.timeout = 300		# 5-minute timeout
48	self.envelope = "Received"	# Envelope-address header
49	self.aka = []			# List of DNS aka names
50	self.dns = TRUE			# Enable DNS lookup on multidrop
51	self.localdomains = []		# Domains to be considered local
52	self.interface = None		# IP address and range
53	self.monitor = None		# IP address and range
54	self.userlist = []		# List of user entries for site
55	self.typemap = (
56	    ('pollname',  'String'),
57	    ('via',       'String'),
58	    ('active',    'Boolean'),
59	    ('interval',  'Int'),
60	    ('protocol',  'String'),
61	    ('interval',  'Int'),
62	    ('port',      'Int'),
63	    ('uidl',      'Boolean'),
64	    ('auth',      'String'),
65	    ('timeout',   'Int'),
66	    ('envelope',  'String'),
67	    # leave aka out
68	    ('dns',       'Boolean'),
69	    # leave localdomains out
70	    ('interface', 'String'),
71	    ('monitor',   'String'))
72
73    def dump(self, folded):
74	str = ""
75	if self.active:   str = str + "poll"
76	else:             str = str + "skip"
77	str = str + (" " + self.pollname)
78	if self.via != self.pollname:
79	    str = str + " via " + self.via
80	if self.protocol != ServerDefaults.protocol:
81	    str = str + " with proto " + self.protocol
82	if self.port != defaultports[self.protocol]:
83	    str = str + " port " + `self.port`
84	if self.timeout != ServerDefaults.timeout:
85	    str = str + " timeout " + `self.timeout`
86	if self.interval != ServerDefaults.interval:
87	    str = str + " interval " + `self.interval`
88	if self.envelope != ServerDefaults.envelope:
89	    str = str + " envelope " + self.envelope
90	if self.auth != ServerDefaults.auth:
91	    str = str + " auth " + self.auth
92	if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
93	    str = str + " and options"
94	if self.dns != ServerDefaults.dns:
95	    str = str + flag2str(self.dns, 'dns')
96	if self.uidl != ServerDefaults.uidl:
97	    str = str + flag2str(self.uidl, 'uidl')
98	if folded:        str = str + "\n\t"
99	else:             str = str + " "
100
101	if self.aka:
102	     str = str + "aka"
103	     for x in self.aka:
104		str = str + " " + x
105	if self.aka and self.localdomains: str = str + " "
106	if self.localdomains:
107	     str = str + ("localdomains")
108	     for x in self.localdomains:
109		str = str + " " + x
110        if (self.aka or self.localdomains):
111	    if folded:
112		str = str + "\n\t"
113	    else:
114		str = str + " "
115
116	if self.interface: str = str + " interface " + self.interface
117	if self.monitor: str = str + " monitor " + self.monitor
118	if (self.interface or self.monitor):
119	    if folded:
120		str = str + "\n"
121
122	if str[-1] == "\t": str = str[0:-1]
123	return str;
124
125    def __repr__(self):
126	return self.dump(TRUE)
127
128    def __str__(self):
129	return "[Server: " + self.dump(FALSE) + "]"
130
131class User:
132    def __init__(self):
133	self.username = ""		# Remote username
134	self.localnames = None		# Local names
135	self.password = ""		# Password for mail account access
136	self.smpthost = 'localhost'	# Host to forward to
137	self.mda = ""			# Mail Delivery Agent
138	self.preconnect = ""		# Connection setup
139	self.postconnect = ""		# Connection wrapup
140	self.keep = FALSE		# Keep messages
141	self.flush = FALSE		# Flush messages
142	self.fetchall = FALSE		# Fetch old messages
143	self.rewrite = TRUE		# Rewrite message headers
144	self.forcecr = FALSE		# Force LF -> CR/LF
145	self.stripcr = FALSE		# Strip CR
146	self.pass8bits = FALSE		# Force BODY=7BIT
147	self.dropstatus = FALSE		# Force BODY=7BIT
148	self.limit = 0			# Message size limit
149	self.fetchlimit = 0		# Max messages fetched per batch
150	self.batchlimit = 0		# Max message forwarded per batch
151	self.typemap = (
152	    ('username',    'String')
153	    ('folder',      'String')
154	    # leave out localnames
155	    ('password',    'String')
156	    ('smtphost',    'String')
157	    ('preconnect',  'String')
158	    ('postconnect', 'String')
159	    ('mda',         'String')
160	    ('keep',        'Boolean')
161	    ('flush',       'Boolean')
162	    ('fetchall',    'Boolean')
163	    ('rewrite',     'Boolean')
164	    ('forcecr',     'Boolean')
165	    ('stripcr',     'Boolean')
166	    ('pass8bits',   'Boolean')
167	    ('dropstatus',  'Boolean')
168	    ('limit',       'Int')
169	    ('fetchlimit',  'Int')
170	    ('batchlimit',  'Int'))
171
172    def __repr__(self):
173	str = ""
174	str = str + "user " + self.user;
175	if self.password: str = str + "with password " + self.password
176	if self.localnames:
177	     str = str + "localnames"
178	     for x in self.localnames:
179		str = str + " " + x
180	if (self.keep or self.flush or self.fetchall or self.rewrite or
181          self.forcecr or self.stripcr or self.pass8bits or self.dropstatus):
182	    str = str + " options"
183	if self.keep != UserDefaults.keep:
184	    str = str + flag2str(self.keep, 'keep')
185	if self.flush != UserDefaults.flush:
186	    str = str + flag2str(self.flush, 'flush')
187	if self.fetchall != UserDefaults.fetchall:
188	    str = str + flag2str(self.fetchall, 'fetchall')
189	if self.rewrite != UserDefaults.rewrite:
190	    str = str + flag2str(self.rewrite, 'rewrite')
191	if self.forcecr != UserDefaults.forcecr:
192	    str = str + flag2str(self.forcecr, 'forcecr')
193	if self.stripcr != UserDefaults.stripcr:
194	    str = str + flag2str(self.stripcr, 'stripcr')
195	if self.pass8bits != UserDefaults.pass8bits:
196	    str = str + flag2str(self.pass8bits, 'pass8bits')
197	if self.dropstatus != UserDefaults.dropstatus:
198	    str = str + flag2str(self.dropstatus, 'dropstatus')
199	if self.limit != UserDefaults.limit:
200	    str = str + " limit " + `self.limit`
201	if self.fetchlimit != UserDefaults.fetchlimit:
202	    str = str + " fetchlimit " + `self.fetchlimit`
203	if self.batchlimit != UserDefaults.batchlimit:
204	    str = str + " batchlimit " + `self.batchlimit`
205
206    def __str__(self):
207	return "[User: " + repr(self) + "]"
208
209#
210# Helper code
211#
212
213defaultports = {"auto":0,
214    		"POP2":109,
215		"POP3":110, "APOP":110, "KPOP":1109, "IMAP":143,
216		"IMAP-K4":143,
217		"ETRN":25}
218
219protolist = ("auto", "POP2", "POP3", "APOP", "KPOP", "IMAP", "IMAP-K4", "ETRN")
220
221authlist = ("password", "kerberos")
222
223def flag2str(value, string):
224# make a string representation of a .fetchmailrc flag or negated flag
225    str = ""
226    if value != None:
227	str = str + (" ")
228	if value == FALSE: str = str + ("no ")
229	str = str + string;
230    return str
231
232class LabeledEntry(Frame):
233# widget consisting of entry field with caption to left
234    def bind(self, key, action):
235	self.E.bind(key, action)
236    def focus_set(self):
237	self.E.focus_set()
238    def __init__(self, Master, text, textvar, width):
239	Frame.__init__(self, Master)
240	self.L = Label(self, {'text':text, 'width':width, 'anchor':'w'})
241	self.E = Entry(self, {'textvar':textvar})
242	self.L.pack({'side':'left'})
243	self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
244
245def ButtonBar(frame, legend, ref, alternatives, command):
246# horizontal bar of radio buttons, caption to left, picking from a string list
247    bar = Frame(frame)
248    Label(bar, text=legend).pack(side=LEFT)
249    for alt in alternatives:
250	Radiobutton(bar,
251		{'text':alt, 'variable':ref, 'value':alt, 'command':command}).pack(side=LEFT)
252    bar.pack(side=TOP);
253    return bar
254
255def helpwin(helpdict):
256# help message window with a self-destruct button
257    helpwin = Toplevel()
258    helpwin.title(helpdict['title'])
259    helpwin.iconname(helpdict['title'])
260    Label(helpwin, text=helpdict['banner']).pack()
261    textwin = Message(helpwin, text=helpdict['text'], width=600)
262    textwin.pack()
263    Button(helpwin, text='Done',
264	   command=lambda x=helpwin: Widget.destroy(x),
265	   relief=SUNKEN, bd=2).pack()
266
267class ListEdit(Frame):
268# edit a list of values (duplicates not allowed) with a supplied editor hook
269    def __init__(self, newlegend, list, editor, master):
270	self.editor = editor
271	self.list = list
272
273	# Set up a widget to accept new sites
274	self.newval = StringVar(master)
275	newwin = LabeledEntry(master, newlegend, self.newval, '16')
276	newwin.bind('<Double-1>', self.handleNew)
277	newwin.bind('<Return>', self.handleNew)
278	newwin.pack(side=TOP, fill=X, anchor=E)
279
280	# Create the sitelist for site-configuration selection
281	listframe = Frame(master)
282	scroll = Scrollbar(listframe)
283	listwidget = Listbox(listframe, height=0, selectmode='browse')
284	if list:
285	    for dnsname in list:
286		listwidget.insert('end', dnsname)
287	listframe.pack(side=TOP, expand=YES, fill=BOTH)
288	listwidget.config(yscrollcommand=scroll.set, relief=SUNKEN)
289	listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
290	scroll.config(command=listwidget.yview, relief=SUNKEN)
291	scroll.pack(side=RIGHT, fill=BOTH)
292	listwidget.config(selectmode=SINGLE, setgrid=TRUE)
293	listwidget.bind('<Double-1>', self.handleList);
294	listwidget.bind('<Return>', self.handleList);
295	self.listwidget = listwidget
296
297	bf = Frame(master);
298	if self.editor:
299	    Button(bf, text='Edit',   command=self.editItem).pack(side=LEFT)
300	Button(bf, text='Delete', command=self.deleteItem).pack(side=RIGHT)
301	bf.pack(fill=X)
302
303    def handleList(self, event):
304	self.editItem();
305
306    def handleNew(self, event):
307	item = self.newval.get()
308	entire = self.listwidget.get(0, self.listwidget.index('end'));
309	if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
310	    self.listwidget.insert('end', item)
311	    if self.list != None: self.list.append(item)
312	self.newval.set('')
313
314    def editItem(self):
315	index = self.listwidget.curselection()[0]
316	if index and self.editor:
317	    label = self.listwidget.get(index);
318	    apply(self.editor, (label,))
319
320    def deleteItem(self):
321	index = self.listwidget.curselection()[0]
322	if index:
323	    self.listwidget.delete(index)
324	    if self.list != None: del self.list[index]
325
326def ConfirmQuit(frame, context):
327    ans = Dialog(frame,
328		 title = 'Quit?',
329		 text = 'Really quit ' + context + ' without saving?',
330		 bitmap = 'question',
331		 strings = ('Yes', 'No'),
332		 default = 1)
333    return ans.num == 0
334#
335# First, code to set the global fetchmail run controls.
336#
337
338confighelp = {
339    'title' : 'Fetchmail configurator help',
340    'banner': 'Configurator help',
341    'text' : """
342In the `Configurator Controls' panel, you can:
343
344Press `Save' to save the new fetchmail configuration you have created.
345Press `Quit' to exit without saving.
346Press `Help' to bring up this help message.
347
348In the `Configurator Controls' panel, you can set the following options that
349control how fetchmail runs:
350
351Poll interval
352        Number of seconds to wait between polls in the background.
353        Ignored if the `Run in Foreground?' option is on.
354
355Logfile
356        If empty, emit progress and error messages to stderr.
357        Otherwise this gives the name of the files to write to.
358        This field is ignored if the "Log to syslog?" option is on.
359
360In the `Remote Mail Configurations' panel, you can:
361
3621. Enter the name of a new remote mail server you want fetchmail to query.
363
364To do this, simply enter a label for the poll configuration in the
365`New Server:' box.  The label should be a DNS name of the server (unless
366you are using ssh or some other tunneling method and will fill in the `via'
367option on the site configuration screen).
368
3692. Change the configuration of an existing site.
370
371To do this, find the site's label in the listbox and double-click it.
372This will take you to a site configuration dialogue.
373"""}
374
375class ControlEdit(Frame):
376    def PostControls(self):
377	self.foreground = BooleanVar(self)
378	self.foreground.set(self.controls.foreground)
379	self.daemon = StringVar(self)
380	self.daemon.set(`self.controls.daemon`)
381	self.syslog = BooleanVar(self)
382	self.syslog.set(self.controls.syslog);
383	self.logfile = StringVar(self)
384	if self.controls.logfile: self.logfile.set(self.controls.logfile);
385
386	gf = Frame(self, relief=RAISED, bd = 5)
387
388	Label(gf,
389		text='Fetchmail Run Controls',
390		bd=2).pack(side=TOP, pady=10)
391
392	df = Frame(gf, relief=RAISED, bd=2)
393
394	# Run in foreground?
395	Checkbutton(df,
396		{'text':'Run in foreground?',
397		'variable':self.foreground,
398		'relief':GROOVE}).pack(side=LEFT,anchor=W)
399
400	# Set the poll interval
401	de = LabeledEntry(df, '     Poll interval:', self.daemon, '14')
402	de.pack(side=RIGHT, anchor=E)
403
404	df.pack();
405
406	sf = Frame(gf, relief=RAISED, bd=2)
407
408	# Use syslog for logging?
409	Checkbutton(sf,
410		{'text':'Log to syslog?',
411		'variable':self.syslog,
412		'relief':GROOVE}).pack(side=LEFT, anchor=W)
413
414	# Set the logfile
415	log = LabeledEntry(sf, '     Logfile:', self.logfile, '14')
416	log.pack(side=RIGHT, anchor=E)
417
418	sf.pack(fill=X)
419	gf.pack(fill=X)
420
421    def GatherControls(self):
422	self.controls.daemon = self.daemon.get()
423	self.controls.foreground = self.foreground.get()
424	self.controls.logfile = self.logfile.get()
425	self.controls.syslog = self.syslog.get()
426
427#
428# Server editing stuff.
429#
430serverhelp = {
431    'title' : 'Server options help',
432    'banner': 'Server Options',
433    'text' : """
434The server options screen controls fetchmail
435options that apply to one of your mailservers.
436
437Once you have a mailserver configuration set
438up as you like it, you can select `Save' to
439store it in the server list maintained in
440the main configuration window.
441
442If you wish to discard changes to a server
443configuration, select `Quit'.
444"""}
445
446controlhelp = {
447    'title' : 'Run Control help',
448    'banner': 'Run Controls',
449    'text' : """
450If the `Poll normally' checkbox is on, the host is polled as part of
451the normal operation of fetchmail when it is run with no arguments.
452If it is off, fetchmail will only query this host when it is given as
453a command-line argument.
454
455The `True name of server' box should specify the actual DNS name
456to query. By default this is the same as the poll name.
457
458Normally each host described in the file is queried once each
459poll cycle. If `Cycles to skip between polls' is greater than 0,
460that's the number of poll cycles that are skipped between the
461times this post is actually polled.
462
463The `Server timeout' is the number of seconds fetchmail will wait
464for a reply from the mailserver before concluding it is hung and
465giving up.
466"""}
467
468protohelp = {
469    'title' : 'Protocol and Port help',
470    'banner': 'Protocol and Port',
471    'text' : """
472These options control the remote-mail protocol
473and TCP/IP service port used to query this
474server.
475
476The `Protocol' button bar offers you a choice of
477all the different protocols available.  The `auto'
478protocol is a special mode that probes the host
479ports for POP3 and IMAP to see if either is
480available.
481
482Normally the TCP/IP service port to use is
483dictated by the protocol choice.  The `Port'
484field lets you set a non-standard port.
485"""}
486
487sechelp = {
488    'title' : 'Security option help',
489    'banner': 'Security',
490    'text' : """
491These options control the security procedure used
492to protect mail transfer
493
494Normally the mail fetch is validated using an
495ordinary password logon.  If your server speaks
496MIT Kerberos IV it is possible to pre-authenticate
497the exxchange with a Kerberos ticket.
498
499The `interface' and `monitor' options are available
500only for Linux systems.  See the fetchmail manual page
501for details on these.
502"""}
503
504multihelp = {
505    'title' : 'Multidrop option help',
506    'banner': 'Multidrop',
507    'text' : """
508These options are only useful with multidrop mode.
509See the manual page for extended discussion.
510"""}
511
512class ServerEdit(Frame):
513    def __init__(self, host, sitelist, master=None):
514	Frame.__init__(self, master)
515	Pack.config(self)
516	self.master.title('Fetchmail host ' + host);
517	self.master.iconname('Fetchmail host ' + host);
518	self.server = Server()
519	self.server.pollname = host
520	self.server.via = host
521	self.sitelist = sitelist
522	self.post()
523	self.createWidgets(host)
524
525    def post(self):
526	# we can't abstract this away, execs would happen in the wrong scope
527	for x in self.server.typemap:
528	    target = "self." + x[0]
529	    source = "self.server." + x[0]
530	    if x[1] == 'Boolean':
531		exec target + " = BooleanVar(self)"
532		if eval(source):
533		    exec target + ".set(" + source + ")"
534	    elif x[1] == 'String':
535		exec target + " = StringVar(self)"
536		if eval(source):
537		    exec target + ".set(" + source + ")"
538	    elif x[1] == 'Int':
539		exec target + " = IntVar(self)"
540		if eval(source):
541		    exec target + ".set(" + source + ")"
542
543    def gather(self):
544	for x in self.server.typemap:
545	    setattr(self.server, x[0], getattr(self, x[0]).get())
546
547    def nosave(self):
548	if ConfirmQuit(self, 'server option editing'):
549	    Widget.destroy(self.master)
550
551    def save(self):
552	self.gather()
553	self.sitelist.append(self.server)
554	Widget.destroy(self.master)
555
556    def refreshPort(self):
557	proto = self.protocol.get()
558	self.port.set(defaultports[proto])
559	if not proto in ("POP3", "APOP", "KPOP"): self.uidl = FALSE
560
561    def createWidgets(self, host):
562	topwin = Frame(self, relief=RAISED, bd=5)
563	Label(topwin, text="Server options for " + host).pack(side=TOP,pady=10)
564	Button(topwin, text='Save', fg='blue',
565		command=self.save).pack(side=LEFT)
566	Button(topwin, text='Quit', fg='blue',
567		command=self.nosave).pack(side=LEFT)
568	Button(topwin, text='Help', fg='blue',
569	       command=lambda: helpwin(serverhelp)).pack(side=RIGHT)
570	topwin.pack(fill=X)
571
572	ctlwin = Frame(self, relief=RAISED, bd=5)
573	Label(ctlwin, text="Run Controls").pack(side=TOP)
574	Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
575	LabeledEntry(ctlwin, 'True name of ' + host + ':',
576		      self.via, '30').pack(side=TOP, fill=X)
577	LabeledEntry(ctlwin, 'Cycles to skip between polls:',
578		      self.interval, '30').pack(side=TOP, fill=X)
579	LabeledEntry(ctlwin, 'Server timeout (seconds):',
580		      self.timeout, '30').pack(side=TOP, fill=X)
581	Button(ctlwin, text='Help', fg='blue',
582	       command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
583	ctlwin.pack(fill=X)
584
585	protwin = Frame(self, relief=RAISED, bd=5)
586	Label(protwin, text="Protocol and Port").pack(side=TOP)
587	pb = ButtonBar(protwin, 'Protocol:', self.protocol, protolist, self.refreshPort)
588	LabeledEntry(protwin, 'TCP/IP service port to query:',
589		      self.port, '30').pack(side=TOP, fill=X)
590	Checkbutton(protwin,
591		text="Track seen POP3 messages with client-side UIDL list?",
592		variable=self.uidl).pack(side=TOP)
593	Button(protwin, text='Help', fg='blue',
594	       command=lambda: helpwin(protohelp)).pack(side=RIGHT)
595	protwin.pack(fill=X)
596
597	secwin = Frame(self, relief=RAISED, bd=5)
598	Label(secwin, text="Security").pack(side=TOP)
599	ButtonBar(secwin, 'Authorization mode:',
600		  self.auth, authlist, None).pack(side=TOP)
601
602	if os.popen("uname").readlines()[0] == 'Linux\n':
603	    LabeledEntry(secwin, 'Interface to check before polling:',
604			 self.interface, '30').pack(side=TOP, fill=X)
605	    LabeledEntry(secwin, 'IP addresses to watch for activity:',
606			 self.monitor, '30').pack(side=TOP, fill=X)
607
608	Button(secwin, text='Help', fg='blue',
609	       command=lambda: helpwin(sechelp)).pack(side=RIGHT)
610	secwin.pack(fill=X)
611
612	mdropwin = Frame(self, relief=RAISED, bd=5)
613	Label(mdropwin, text="Multidrop options").pack(side=TOP)
614	LabeledEntry(mdropwin, 'Envelope address header:',
615		      self.envelope, '30').pack(side=TOP, fill=X)
616	Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
617		    variable=self.dns).pack(side=TOP)
618	Label(mdropwin, text="DNS aliases").pack(side=TOP)
619	ListEdit("New site alias: ", self.server.aka, None, mdropwin)
620	Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
621	ListEdit("New local domain: ", self.server.localdomains, None,mdropwin)
622	Button(mdropwin, text='Help', fg='blue',
623	       command=lambda: helpwin(multihelp)).pack(side=RIGHT)
624	mdropwin.pack(fill=X)
625
626	userwin = Frame(self, relief=RAISED, bd=5)
627	Label(userwin, text="User entries for " + host).pack(side=TOP)
628	ListEdit("New user: ", None, self.edituser, userwin)
629	userwin.pack(fill=X)
630
631    def edituser(self, user):
632	UserEdit(user, self.server.userlist, Toplevel())
633
634#
635# User editing stuff
636#
637
638userhelp = {
639    'title' : 'User option help',
640    'banner': 'User options',
641    'text' : """
642FIXME
643"""}
644
645class UserEdit(Frame):
646    def __init__(self, user, userlist, master=None):
647	Frame.__init__(self, master)
648	Pack.config(self)
649	self.master.title('Fetchmail user ' + user);
650	self.master.iconname('Fetchmail user ' + user);
651	self.user = User()
652	self.user.remote = user
653	self.user.localnames = [user]
654	self.userlist = userlist
655	self.post()
656	self.createWidgets(user)
657
658    def post(self):
659	# we can't abstract this away, execs would happen in the wrong scope
660	for x in self.user.typemap:
661	    target = "self." + x[0]
662	    source = "self.user." + x[0]
663	    if x[1] == 'Boolean':
664		exec target + " = BooleanVar(self)"
665		if eval(source):
666		    exec target + ".set(" + source + ")"
667	    elif x[1] == 'String':
668		exec target + " = StringVar(self)"
669		if eval(source):
670		    exec target + ".set(" + source + ")"
671	    elif x[1] == 'Int':
672		exec target + " = IntVar(self)"
673		if eval(source):
674		    exec target + ".set(" + source + ")"
675
676    def gather(self):
677	for x in self.user.typemap:
678	    setattr(self.user, x[0], getattr(self, x[0]).get())
679
680    def nosave(self):
681	if ConfirmQuit(self, 'user option editing'):
682	    Widget.destroy(self.master)
683
684    def save(self):
685	self.gather()
686	self.userlist.append(self.user)
687	Widget.destroy(self.master)
688
689    def createWidgets(self):
690	topwin = Frame(self, relief=RAISED, bd=5)
691	Label(topwin,
692	      text="User options for " + self.user.remote).pack(side=TOP,pady=10)
693	Button(topwin, text='Save', fg='blue',
694		command=self.save).pack(side=LEFT)
695	Button(topwin, text='Quit', fg='blue',
696		command=self.nosave).pack(side=LEFT)
697	Button(topwin, text='Help', fg='blue',
698	       command=lambda: helpwin(userhelp)).pack(side=RIGHT)
699	topwin.pack(fill=X)
700
701	secwin = Frame(self, relief=RAISED, bd=5)
702	Label(secwin, text="Authentication").pack(side=TOP)
703	LabeledEntry(mdropwin, 'Password:',
704		      self.password, '30').pack(side=TOP, fill=X)
705	LabeledEntry(mdropwin, 'Remote folder:',
706		     self.folder, '30').pack(side=TOP, fill=X)
707	secwin.pack(fill=X)
708
709	names = Frame(self, relief=RAISED, bd=5)
710	Label(names, text="Local names").pack(side=TOP)
711	ListEdit("New local name: ", self.localnames, None, names)
712	names.pack(fill=X)
713
714	targwin = Frame(self, relief=RAISED, bd=5)
715	Label(targwin, text="Forwarding Options").pack(side=TOP)
716	LabeledEntry(targwin, 'System to forward to:',
717		     self.smtphost, '30').pack(side=TOP, fill=X)
718	LabeledEntry(targwin, 'Connection setup command:',
719		     self.preconnect, '30').pack(side=TOP, fill=X)
720	LabeledEntry(targwin, 'Connection wrapup command:',
721		     self.postconnect, '30').pack(side=TOP, fill=X)
722	LabeledEntry(targwin, 'Local delivery agent:',
723		     self.mda, '30').pack(side=TOP, fill=X)
724	targwin.pack(fill=X)
725
726	optwin = Frame(self, relief=RAISED, bd=5)
727	Checkbutton(optwin, "Suppress deletion of messages after reading",
728		    self.keep)
729	Checkbutton(optwin, "Flush seen messages before retrieval",
730		    self.flush)
731	Checkbutton(optwin, "Fetch old messages as well as new",
732		    self.fetchall)
733	Checkbutton(optwin, "Rewrite To/Cc/Bcc messages to enable reply",
734		    self.rewrite)
735	Checkbutton(optwin, "Force CR/LF at end of each line",
736		    self.forcecr)
737	Checkbutton(optwin, "Strip CR from end of eacgh line",
738		    self.stripcr)
739	Checkbutton(optwin, "Pass 8 bits even theough SMTP says 7BIT",
740		    self.pass8bits)
741	Checkbutton(optwin, "Drop Status lines from forwarded messages",
742		    self.dropstatus)
743	optwin.pack(fill=X)
744
745	limwin = Frame(self, relief=RAISED, bd=5)
746	Label(limwin, text="Resource Limits").pack(side=TOP)
747	LabeledEntry(limwin, 'Message size limit:',
748		      self.limit, '30').pack(side=TOP, fill=X)
749	LabeledEntry(limwin, 'Maximum messages to fetch each poll:',
750		      self.fetchlimit, '30').pack(side=TOP, fill=X)
751	LabeledEntry(limwin, 'Maximum messages to forward each poll:',
752		      self.batchlimit, '30').pack(side=TOP, fill=X)
753	limwin.pack(fill=X)
754
755#
756# Configure drives the configuration dialogue.  It may call multiple
757# instances of ServerEdit to do its job.
758#
759
760class Configure(Frame, ControlEdit):
761    def __init__(self, master=None):
762	Frame.__init__(self, master)
763	self.master.title('fetchmail configurator');
764	self.master.iconname('fetchmail configurator');
765	Pack.config(self)
766	self.MakeDispose()
767	self.controls = Controls()
768	self.PostControls()
769	self.MakeSitelist(master)
770	self.sites = []
771
772    def MakeDispose(self):
773	# Set the disposal of the given configuration
774	dispose = Frame(self, relief=RAISED, bd=5);
775	Label(dispose,
776		text='Configurator Controls',
777		bd=2).pack(side=TOP, pady=10)
778	Button(dispose, text='Save', fg='blue',
779		command=self.save).pack(side=LEFT)
780	Button(dispose, text='Quit', fg='blue',
781		command=self.nosave).pack(side=LEFT)
782	Button(dispose, text='Help', fg='blue',
783	       command=lambda: helpwin(confighelp)).pack(side=RIGHT)
784	dispose.pack(side=TOP, fill=X);
785
786    def MakeSitelist(self, master):
787	lf = Frame(master, relief=RAISED, bd=5)
788	Label(lf,
789	      text='Remote Mail Server Configurations',
790	      bd=2).pack(side=TOP, pady=10)
791	ListEdit('New Server:', None, self.editsite, lf)
792	lf.pack(fill=X)
793
794    def editsite(self, site):
795	ServerEdit(site, self.sites, Toplevel())
796
797    def save(self):
798	self.GatherControls()
799	sys.stdout.write("# Configuration created %s\n" % time.ctime(time.time()))
800	sys.stdout.write(`self.controls`)
801	for site in self.sites:
802	    sys.stdout.write(`site`)
803	    for user in self.sites.userlist:
804		sys.stdout.write(`user`)
805	self.quit()
806
807    def nosave(self):
808	if ConfirmQuit(self, "configuration editor"):
809	    self.quit()
810
811if __name__ == '__main__':
812    ServerDefaults = Server()
813    UserDefaults = User()
814    Configure().mainloop()
815
816# The following sets edit modes for GNU EMACS
817# Local Variables:
818# mode:python
819# End:
820