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
8 # TO DO: Arrange for save and quit buttons to clean up all frames dependent
14 import sys, time, os, string, socket, getopt
17 # Define the data structures the GUIs will be tossing around
21 self.poll_interval = 0 # Normally, run in foreground
22 self.syslog = FALSE # Use syslogd for logging?
23 self.logfile = None # No logfile, initially
24 self.idfile = os.environ["HOME"] + "/.fetchids" # Default idfile, initially
25 self.postmaster = None # No last-resort address, initially
26 self.invisible = FALSE # Suppress Received line & spoof?
27 self.servers = [] # List of included sites
28 Configuration.typemap = (
29 ('poll_interval', 'Int'),
30 ('syslog', 'Boolean'),
31 ('logfile', 'String'),
33 ('postmaster', 'String'),
34 ('invisible', 'Boolean'))
38 if self.syslog != ConfigurationDefaults.syslog:
39 str = str + ("set syslog\n")
41 str = str + ("set logfile \"%s\"\n" % (self.logfile,));
42 if self.idfile != ConfigurationDefaults.idfile:
43 str = str + ("set idfile \"%s\"\n" % (self.idfile,));
44 if self.postmaster != ConfigurationDefaults.postmaster:
45 str = str + ("set postmaster \"%s\"\n" % (self.postmaster,));
46 if self.poll_interval > 0:
47 str = str + "set daemon " + `self.poll_interval` + "\n"
48 for site in self.servers:
49 str = str + repr(site)
53 return "[Configuration: " + repr(self) + "]"
57 self.pollname = None # Poll label
58 self.via = None # True name of host
59 self.active = TRUE # Poll status
60 self.interval = 0 # Skip interval
61 self.protocol = 'auto' # Default to auto protocol
62 self.port = 0 # Port number to use
63 self.uidl = FALSE # Don't use RFC1725 UIDLs by default
64 self.auth = 'password' # Default to password authentication
65 self.timeout = 300 # 5-minute timeout
66 self.envelope = 'Received' # Envelope-address header
67 self.envskip = 0 # Number of envelope headers to skip
68 self.qvirtual = None # Name prefix to strip
69 self.aka = [] # List of DNS aka names
70 self.dns = TRUE # Enable DNS lookup on multidrop
71 self.localdomains = [] # Domains to be considered local
72 self.interface = None # IP address and range
73 self.monitor = None # IP address and range
74 self.netsec = None # IPV6 security options
75 self.users = [] # List of user entries for site
77 ('pollname', 'String'),
79 ('active', 'Boolean'),
81 ('protocol', 'String'),
87 ('envelope', 'String'),
89 ('qvirtual', 'String'),
92 # leave localdomains out
93 ('interface', 'String'),
94 ('monitor', 'String'),
97 def dump(self, folded):
99 if self.active: str = str + "poll"
100 else: str = str + "skip"
101 str = str + (" " + self.pollname)
103 str = str + (" via \"%s\"\n" % (self.via,));
104 if self.protocol != ServerDefaults.protocol:
105 str = str + " with proto " + self.protocol
106 if self.port != defaultports[self.protocol] and self.port != 0:
107 str = str + " port " + `self.port`
108 if self.timeout != ServerDefaults.timeout:
109 str = str + " timeout " + `self.timeout`
110 if self.interval != ServerDefaults.interval:
111 str = str + " interval " + `self.interval`
112 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
114 str = str + " envelope " + self.envskip + " " + self.envelope
116 str = str + " envelope " + self.envelope
118 str = str + (" qvirtual \"%s\"\n" % (self.qvirtual,));
119 if self.auth != ServerDefaults.auth:
120 str = str + " auth " + self.auth
121 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
122 str = str + " and options"
123 if self.dns != ServerDefaults.dns:
124 str = str + flag2str(self.dns, 'dns')
125 if self.uidl != ServerDefaults.uidl:
126 str = str + flag2str(self.uidl, 'uidl')
127 if folded: str = str + "\n "
128 else: str = str + " "
134 if self.aka and self.localdomains: str = str + " "
135 if self.localdomains:
136 str = str + ("localdomains")
137 for x in self.localdomains:
139 if (self.aka or self.localdomains):
146 str = str + "interface \"" + self.interface + "\""
148 str = str + "monitor \"" + self.monitor + "\""
150 str = str + "netsec \"" + self.netsec + "\""
151 if self.interface or self.monitor or self.netsec:
155 if str[-1] == " ": str = str[0:-1]
157 for user in self.users:
158 str = str + repr(user)
163 return self.dump(TRUE)
166 return "[Server: " + self.dump(FALSE) + "]"
170 self.remote = os.environ["USER"]# Remote username
171 self.localnames = [self.remote,]# Local names
172 self.password = None # Password for mail account access
173 self.mailboxes = [] # Remote folders to retrieve from
174 self.smtphunt = [] # Hosts to forward to
175 self.smtpaddress = None; # Append this to MAIL FROM line
176 self.preconnect = None # Connection setup
177 self.postconnect = None # Connection wrapup
178 self.mda = None # Mail Delivery Agent
179 self.antispam = "571 550 501" # Listener's spam-block code
180 self.keep = FALSE # Keep messages
181 self.flush = FALSE # Flush messages
182 self.fetchall = FALSE # Fetch old messages
183 self.rewrite = TRUE # Rewrite message headers
184 self.forcecr = FALSE # Force LF -> CR/LF
185 self.stripcr = FALSE # Strip CR
186 self.pass8bits = FALSE # Force BODY=7BIT
187 self.mimedecode = FALSE # Undo MIME armoring
188 self.dropstatus = FALSE # Drop incoming Status lines
189 self.limit = 0 # Message size limit
190 self.fetchlimit = 0 # Max messages fetched per batch
191 self.batchlimit = 0 # Max message forwarded per batch
192 self.expunge = 1 # Interval between expunges (IMAP)
194 ('remote', 'String'),
195 # leave out mailboxes and localnames
196 ('password', 'String'),
198 ('smtpaddress', 'String'),
199 ('preconnect', 'String'),
200 ('postconnect', 'String'),
202 ('antispam', 'String'),
204 ('flush', 'Boolean'),
205 ('fetchall', 'Boolean'),
206 ('rewrite', 'Boolean'),
207 ('forcecr', 'Boolean'),
208 ('stripcr', 'Boolean'),
209 ('pass8bits', 'Boolean'),
210 ('mimedecode', 'Boolean'),
211 ('dropstatus', 'Boolean'),
213 ('fetchlimit', 'Int'),
214 ('batchlimit', 'Int'),
219 str = str + "user \"" + self.remote + "\" there ";
221 str = str + "with password \"" + self.password + '" '
224 for x in self.localnames:
227 if (self.keep != UserDefaults.keep
228 or self.flush != UserDefaults.flush
229 or self.fetchall != UserDefaults.fetchall
230 or self.rewrite != UserDefaults.rewrite
231 or self.forcecr != UserDefaults.forcecr
232 or self.stripcr != UserDefaults.stripcr
233 or self.pass8bits != UserDefaults.pass8bits
234 or self.mimedecode != UserDefaults.mimedecode
235 or self.dropstatus != UserDefaults.dropstatus):
236 str = str + " options"
237 if self.keep != UserDefaults.keep:
238 str = str + flag2str(self.keep, 'keep')
239 if self.flush != UserDefaults.flush:
240 str = str + flag2str(self.flush, 'flush')
241 if self.fetchall != UserDefaults.fetchall:
242 str = str + flag2str(self.fetchall, 'fetchall')
243 if self.rewrite != UserDefaults.rewrite:
244 str = str + flag2str(self.rewrite, 'rewrite')
245 if self.forcecr != UserDefaults.forcecr:
246 str = str + flag2str(self.forcecr, 'forcecr')
247 if self.stripcr != UserDefaults.stripcr:
248 str = str + flag2str(self.stripcr, 'stripcr')
249 if self.pass8bits != UserDefaults.pass8bits:
250 str = str + flag2str(self.pass8bits, 'pass8bits')
251 if self.mimedecode != UserDefaults.mimedecode:
252 str = str + flag2str(self.mimedecode, 'mimedecode')
253 if self.dropstatus != UserDefaults.dropstatus:
254 str = str + flag2str(self.dropstatus, 'dropstatus')
255 if self.limit != UserDefaults.limit:
256 str = str + " limit " + `self.limit`
257 if self.fetchlimit != UserDefaults.fetchlimit:
258 str = str + " fetchlimit " + `self.fetchlimit`
259 if self.batchlimit != UserDefaults.batchlimit:
260 str = str + " batchlimit " + `self.batchlimit`
261 if self.expunge != UserDefaults.expunge:
262 str = str + " expunge " + `self.expunge`
264 trimmed = self.smtphunt;
265 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
266 trimmed = trimmed[0:len(trimmed) - 1]
267 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
268 trimmed = trimmed[0:len(trimmed) - 1]
270 str = str + " smtphost "
275 str = str + " folder"
276 for x in self.mailboxes:
279 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda'):
280 if getattr(self, fld):
281 str = str + " %s \"%s\"\n" % (fld, `getattr(self, fld)`)
282 if self.antispam != UserDefaults.antispam:
283 str = str + " antispam " + self.antispam + "\n"
287 return "[User: " + repr(self) + "]"
293 defaultports = {"auto":0,
302 protolist = ("auto", "POP2", "POP3", "APOP", "KPOP", "IMAP", "IMAP-K4", "ETRN")
304 authlist = ("password", "kerberos")
307 'title' : 'List Selection Help',
308 'banner': 'List Selection',
310 You must select an item in the list box (by clicking on it).
313 def flag2str(value, string):
314 # make a string representation of a .fetchmailrc flag or negated flag
318 if value == FALSE: str = str + ("no ")
322 class LabeledEntry(Frame):
323 # widget consisting of entry field with caption to left
324 def bind(self, key, action):
325 self.E.bind(key, action)
328 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
329 Frame.__init__(self, Master)
330 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
331 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
332 self.L.pack({'side':'left'})
333 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
335 def ButtonBar(frame, legend, ref, alternatives, depth, command):
336 # array of radio buttons, caption to left, picking from a string list
338 width = len(alternatives) / depth;
339 Label(bar, text=legend).pack(side=LEFT)
340 for column in range(width):
341 subframe = Frame(bar)
342 for row in range(depth):
343 ind = width * row + column
344 Radiobutton(subframe,
345 {'text':alternatives[ind],
347 'value':alternatives[ind],
348 'command':command}).pack(side=TOP, anchor=W)
349 subframe.pack(side=LEFT)
353 def helpwin(helpdict):
354 # help message window with a self-destruct button
356 helpwin.title(helpdict['title'])
357 helpwin.iconname(helpdict['title'])
358 Label(helpwin, text=helpdict['banner']).pack()
359 textwin = Message(helpwin, text=helpdict['text'], width=600)
361 Button(helpwin, text='Done',
362 command=lambda x=helpwin: Widget.destroy(x),
363 relief=SUNKEN, bd=2).pack()
365 def make_icon_window(base, image):
367 # Some older pythons will error out on this
368 icon_image = PhotoImage(data=image)
369 icon_window = Toplevel()
370 Label(icon_window, image=icon_image, bg='black').pack()
371 base.master.iconwindow(icon_window)
372 # Avoid TkInter brain death. PhotoImage objects go out of
373 # scope when the enclosing function returns. Therefore
374 # we have to explicitly link them to something.
375 base.keepalive.append(icon_image)
379 class ListEdit(Frame):
380 # edit a list of values (duplicates not allowed) with a supplied editor hook
381 def __init__(self, newlegend, list, editor, master, helptxt):
385 # Set up a widget to accept new elements
386 self.newval = StringVar(master)
387 newwin = LabeledEntry(master, newlegend, self.newval, '12')
388 newwin.bind('<Double-1>', self.handleNew)
389 newwin.bind('<Return>', self.handleNew)
390 newwin.pack(side=TOP, fill=X, anchor=E)
392 # Edit the existing list
393 listframe = Frame(master)
394 scroll = Scrollbar(listframe)
395 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
398 self.listwidget.insert(END, x)
399 listframe.pack(side=TOP, expand=YES, fill=BOTH)
400 self.listwidget.config(yscrollcommand=scroll.set, relief=SUNKEN)
401 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
402 scroll.config(command=self.listwidget.yview, relief=SUNKEN)
403 scroll.pack(side=RIGHT, fill=BOTH)
404 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
405 self.listwidget.bind('<Double-1>', self.handleList);
406 self.listwidget.bind('<Return>', self.handleList);
410 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
411 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
413 self.helptxt = helptxt
414 Button(bf, text='Help', fg='blue',
415 command=self.help).pack(side=RIGHT)
419 helpwin(self.helptxt)
421 def handleList(self, event):
424 def handleNew(self, event):
425 item = self.newval.get()
426 entire = self.listwidget.get(0, self.listwidget.index('end'));
427 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
428 self.listwidget.insert('end', item)
429 if self.list != None: self.list.append(item)
433 select = self.listwidget.curselection()
438 if index and self.editor:
439 label = self.listwidget.get(index);
440 apply(self.editor, (label,))
442 def deleteItem(self):
443 select = self.listwidget.curselection()
447 index = string.atoi(select[0])
449 self.listwidget.delete(index)
450 if self.list != None: del self.list[index]
452 def ConfirmQuit(frame, context):
455 text = 'Really quit ' + context + ' without saving?',
457 strings = ('Yes', 'No'),
461 def dispose_window(master, legend, help):
462 dispose = Frame(master, relief=RAISED, bd=5)
463 Label(dispose, text=legend).pack(side=TOP,pady=10)
464 Button(dispose, text='Save', fg='blue',
465 command=master.save).pack(side=LEFT)
466 Button(dispose, text='Quit', fg='blue',
467 command=master.nosave).pack(side=LEFT)
468 Button(dispose, text='Help', fg='blue',
469 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
474 # Common methods for Tkinter widgets -- deals with Tkinter declaration
475 def post(self, widgetclass, field):
476 for x in widgetclass.typemap:
477 if x[1] == 'Boolean':
478 setattr(self, x[0], BooleanVar(self))
479 elif x[1] == 'String':
480 setattr(self, x[0], StringVar(self))
482 setattr(self, x[0], IntVar(self))
483 source = getattr(getattr(self, field), x[0])
485 getattr(self, x[0]).set(source)
487 def fetch(self, widgetclass, field):
488 for x in widgetclass.typemap:
489 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
492 # First, code to set the global fetchmail run controls.
495 configure_novice_help = {
496 'title' : 'Fetchmail novice configurator help',
497 'banner': 'Novice configurator help',
499 In the `Novice Configurator Controls' panel, you can:
501 Press `Save' to save the new fetchmail configuration you have created.
502 Press `Quit' to exit without saving.
503 Press `Help' to bring up this help message.
505 In the `Novice Configuration' panels, you will set up the basic data
506 needed to create a simple fetchmail setup. These include:
508 1. The name of the remote site you want to query.
510 2. Your login name on that site.
512 3. Your password on that site.
514 4. A protocol to use (POP, IMAP, ETRN, etc.)
516 5. A polling interval.
518 6. Options to fetch old messages as well as new, uor to suppress
519 deletion of fetched message.
521 The novice-configuration code will assume that you want to forward mail
522 to a local sendmail listener with no special options.
525 configure_expert_help = {
526 'title' : 'Fetchmail expert configurator help',
527 'banner': 'Expert configurator help',
529 In the `Expert Configurator Controls' panel, you can:
531 Press `Save' to save the new fetchmail configuration you have edited.
532 Press `Quit' to exit without saving.
533 Press `Help' to bring up this help message.
535 In the `Run Controls' panel, you can set the following options that
536 control how fetchmail runs:
539 Number of seconds to wait between polls in the background.
540 If zero, fetchmail will run in foreground.
543 If empty, emit progress and error messages to stderr.
544 Otherwise this gives the name of the files to write to.
545 This field is ignored if the "Log to syslog?" option is on.
548 If empty, store seen-message IDs in .fetchids under user's home
549 directory. If nonempty, use given file name.
552 Who to send multidrop mail to as a last resort if no address can
553 be matched. Normally empty; in this case, fetchmail treats the
554 invoking user as the address of last resort unless that user is
555 root. If that user is root, fetchmail sends to `postmaster'.
558 If false (the default) fetchmail generates a Received line into
559 each message and generates a HELO from the machine it is running on.
560 If true, fetchmail generates no Received line and HELOs as if it were
563 In the `Remote Mail Configurations' panel, you can:
565 1. Enter the name of a new remote mail server you want fetchmail to query.
567 To do this, simply enter a label for the poll configuration in the
568 `New Server:' box. The label should be a DNS name of the server (unless
569 you are using ssh or some other tunneling method and will fill in the `via'
570 option on the site configuration screen).
572 2. Change the configuration of an existing site.
574 To do this, find the site's label in the listbox and double-click it.
575 This will take you to a site configuration dialogue.
579 class ConfigurationEdit(Frame, MyWidget):
580 def __init__(self, configuration, outfile, master=None):
581 self.configuration = configuration
582 self.outfile = outfile
583 self.container = master
584 ConfigurationEdit.mode_to_help = {
585 'novice':configure_novice_help, 'expert':configure_expert_help
588 def edit(self, mode):
590 Frame.__init__(self, self.container)
591 self.master.title('fetchmail ' + self.mode + ' configurator');
592 self.master.iconname('fetchmail ' + self.mode + ' configurator');
593 self.keepalive = [] # Use this to anchor the PhotoImage object
594 make_icon_window(self, fetchmail_gif)
596 self.post(Configuration, 'configuration')
599 'Configurator ' + self.mode + ' Controls',
600 ConfigurationEdit.mode_to_help[self.mode])
602 gf = Frame(self, relief=RAISED, bd = 5)
604 text='Fetchmail Run Controls',
605 bd=2).pack(side=TOP, pady=10)
609 # Set the poll interval
610 de = LabeledEntry(df, ' Poll interval:', self.poll_interval, '14')
611 de.pack(side=RIGHT, anchor=E)
614 if self.mode != 'novice':
617 {'text':'Log to syslog?',
618 'variable':self.syslog,
619 'relief':GROOVE}).pack(side=LEFT, anchor=W)
620 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
621 log.pack(side=RIGHT, anchor=E)
625 {'text':'Invisible mode?',
626 'variable':self.invisible,
627 'relief':GROOVE}).pack(side=LEFT, anchor=W)
629 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
630 log.pack(side=RIGHT, anchor=E)
632 log = LabeledEntry(gf, ' Postmaster:', self.postmaster, '14')
633 log.pack(side=RIGHT, anchor=E)
637 # Expert mode allows us to edit multiple sites
638 lf = Frame(self, relief=RAISED, bd=5)
640 text='Remote Mail Server Configurations',
641 bd=2).pack(side=TOP, pady=10)
642 ListEdit('New Server:',
643 map(lambda x: x.pollname, self.configuration.servers),
644 lambda site, m=self.mode, s=self.configuration.servers:
645 ServerEdit(site, s).edit(m, Toplevel()),
650 if ConfirmQuit(self, self.mode + " configuration editor"):
654 self.fetch(Configuration, 'configuration')
658 elif not os.path.isfile(self.outfile) or Dialog(self,
659 title = 'Overwrite existing run control file?',
660 text = 'Really overwrite existing run control file?',
662 strings = ('Yes', 'No'),
663 default = 1).num == 0:
664 fm = open(self.outfile, 'w')
666 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
667 fm.write(`self.configuration`)
668 os.chmod(self.outfile, 0600)
672 # Server editing stuff.
675 'title' : 'Remote site help',
676 'banner': 'Remote sites',
678 When you add a site name to the list here,
679 you initialize an entry telling fetchmail
680 how to poll a new site.
682 When you select a sitename (by double-
683 clicking it, or by single-clicking to
684 select and then clicking the Edit button),
685 you will open a window to configure that
690 'title' : 'Server options help',
691 'banner': 'Server Options',
693 The server options screen controls fetchmail
694 options that apply to one of your mailservers.
696 Once you have a mailserver configuration set
697 up as you like it, you can select `Save' to
698 store it in the server list maintained in
699 the main configuration window.
701 If you wish to discard changes to a server
702 configuration, select `Quit'.
706 'title' : 'Run Control help',
707 'banner': 'Run Controls',
709 If the `Poll normally' checkbox is on, the host is polled as part of
710 the normal operation of fetchmail when it is run with no arguments.
711 If it is off, fetchmail will only query this host when it is given as
712 a command-line argument.
714 The `True name of server' box should specify the actual DNS name
715 to query. By default this is the same as the poll name.
717 Normally each host described in the file is queried once each
718 poll cycle. If `Cycles to skip between polls' is greater than 0,
719 that's the number of poll cycles that are skipped between the
720 times this post is actually polled.
722 The `Server timeout' is the number of seconds fetchmail will wait
723 for a reply from the mailserver before concluding it is hung and
728 'title' : 'Protocol and Port help',
729 'banner': 'Protocol and Port',
731 These options control the remote-mail protocol
732 and TCP/IP service port used to query this
735 If you click the `Probe for supported protocols'
736 button, fetchmail will try to find you the most
737 capable server on the selected host (this will
738 only work if you're conncted to the Internet).
739 The probe only checks for ordinary IMAP and POP
740 protocols; fortunately these are the most
741 frequently supported.
743 The `Protocol' button bar offers you a choice of
744 all the different protocols available. The `auto'
745 protocol is the default mode; it probes the host
746 ports for POP3 and IMAP to see if either is
749 Normally the TCP/IP service port to use is
750 dictated by the protocol choice. The `Port'
751 field (only present in expert mode) lets you
752 set a non-standard port.
756 'title' : 'Security option help',
757 'banner': 'Security',
759 The `interface' option, if given, specifies the only
760 device through which fetchmail is permitted to connect
761 to servers. Specifying this may protect you from a
762 spoofing attack if your client machine has more than
763 one IP gateway address and some of the gateways are
766 The `monitor' option allows you to specify a range
767 of IP addresses to monitor for activity. If these
768 addresses are not active, fetchmail will not poll.
769 This option may be used to prevent fetchmail from
770 triggering an expensive dial-out if the interface
771 is not already active.
773 The `interface' and `monitor' options are available
774 only for Linux systems. See the fetchmail manual page
775 for details on these.
777 The `netsec' option will be configurable only if fetchmail
778 was compiled with IPV6 support. If you need to use it,
779 you probably know what to do.
783 'title' : 'Multidrop option help',
784 'banner': 'Multidrop',
786 These options are only useful with multidrop mode.
787 See the manual page for extended discussion.
791 'title' : 'User list help',
792 'banner': 'User list',
794 When you add a user name to the list here,
795 you initialize an entry telling fetchmail
796 to poll the site on behalf of the new user.
798 When you select a username (by double-
799 clicking it, or by single-clicking to
800 select and then clicking the Edit button),
801 you will open a window to configure the
802 user's options on that site.
805 class ServerEdit(Frame, MyWidget):
806 def __init__(self, host, servers):
809 if site.pollname == host:
811 if (self.server == None):
812 self.server = Server()
813 self.server.pollname = host
814 self.server.via = None
815 servers.append(self.server)
817 def edit(self, mode, master=None):
818 Frame.__init__(self, master)
820 self.master.title('Fetchmail host ' + self.server.pollname);
821 self.master.iconname('Fetchmail host ' + self.server.pollname);
822 self.post(Server, 'server')
823 self.makeWidgets(self.server.pollname, mode)
824 self.keepalive = [] # Use this to anchor the PhotoImage object
825 make_icon_window(self, fetchmail_gif)
831 if ConfirmQuit(self, 'server option editing'):
832 Widget.destroy(self.master)
835 self.fetch(Server, 'server')
836 Widget.destroy(self.master)
838 def refreshPort(self):
839 proto = self.protocol.get()
840 self.port.set(defaultports[proto])
841 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
843 def makeWidgets(self, host, mode):
844 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
846 leftwin = Frame(self);
850 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
851 Label(ctlwin, text="Run Controls").pack(side=TOP)
852 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
853 LabeledEntry(ctlwin, 'True name of ' + host + ':',
854 self.via, leftwidth).pack(side=TOP, fill=X)
855 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
856 self.interval, leftwidth).pack(side=TOP, fill=X)
857 LabeledEntry(ctlwin, 'Server timeout (seconds):',
858 self.timeout, leftwidth).pack(side=TOP, fill=X)
859 Button(ctlwin, text='Help', fg='blue',
860 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
863 protwin = Frame(leftwin, relief=RAISED, bd=5)
864 Label(protwin, text="Protocol").pack(side=TOP)
865 ButtonBar(protwin, '',
866 self.protocol, protolist, 2,
869 LabeledEntry(protwin, 'On server TCP/IP port:',
870 self.port, leftwidth).pack(side=TOP, fill=X)
873 text="POP3: track `seen' with client-side UIDLs?",
874 variable=self.uidl).pack(side=TOP)
875 Button(protwin, text='Probe for supported protocols', fg='blue',
876 command=self.autoprobe).pack(side=LEFT)
877 Button(protwin, text='Help', fg='blue',
878 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
881 userwin = Frame(leftwin, relief=RAISED, bd=5)
882 Label(userwin, text="User entries for " + host).pack(side=TOP)
883 ListEdit("New user: ",
884 map(lambda x: x.remote, self.server.users),
885 lambda u, m=mode, s=self.server: UserEdit(u,s).edit(m, Toplevel()),
889 leftwin.pack(side=LEFT, anchor=N, fill=X);
892 rightwin = Frame(self);
894 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
895 Label(mdropwin, text="Multidrop options").pack(side=TOP)
896 LabeledEntry(mdropwin, 'Envelope address header:',
897 self.envelope, '22').pack(side=TOP, fill=X)
898 LabeledEntry(mdropwin, 'Envelope headers to skip:',
899 self.envskip, '22').pack(side=TOP, fill=X)
900 LabeledEntry(mdropwin, 'Name prefix to strip:',
901 self.qvirtual, '22').pack(side=TOP, fill=X)
902 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
903 variable=self.dns).pack(side=TOP)
904 Label(mdropwin, text="DNS aliases").pack(side=TOP)
905 ListEdit("New alias: ", self.server.aka, None, mdropwin, None)
906 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
907 ListEdit("New domain: ",
908 self.server.localdomains, None, mdropwin, multihelp)
909 mdropwin.pack(fill=X)
911 if 'interface' in dictmembers or 'monitor' in dictmembers or 'netsec' in dictmembers:
912 secwin = Frame(rightwin, relief=RAISED, bd=5)
913 Label(secwin, text="Security").pack(side=TOP)
914 # Don't actually let users set this. KPOP sets it implicitly
915 # ButtonBar(secwin, 'Authorization mode:',
916 # self.auth, authlist, 1, None).pack(side=TOP)
917 if 'interface' in dictmembers:
918 LabeledEntry(secwin, 'Interface to check before poll:',
919 self.interface, leftwidth).pack(side=TOP, fill=X)
920 if 'monitor' in dictmembers:
921 LabeledEntry(secwin, 'IP range to monitor:',
922 self.monitor, leftwidth).pack(side=TOP, fill=X)
923 if 'netsec' in dictmembers:
924 LabeledEntry(secwin, 'IPV6 security options:',
925 self.netsec, leftwidth).pack(side=TOP, fill=X)
926 Button(secwin, text='Help', fg='blue',
927 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
930 rightwin.pack(side=LEFT, anchor=N);
933 # Note: this only handles case (1) near fetchmail.c:892
934 # We're assuming people smart enough to set up ssh tunneling
935 # won't need autoprobing.
936 if self.server.via != None:
937 realhost = self.server.via
939 realhost = self.server.pollname
941 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
942 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
944 sock.connect(realhost, port)
945 greetline = sock.recv(1024)
952 if greetline == None:
953 title = "Autoprobe of " + realhost + " failed"
955 Fetchmailconf didn't find any mailservers active.
956 This could mean the host doesn't support any,
957 or that your Internet connection is down, or
958 that the host is so slow that the probe timed
959 out before getting a response.
963 # OK, now try to recognize potential problems
965 if protocol == "POP2":
966 warnings = warnings + """
967 It appears you have somehow found a mailserver running only POP2.
968 Congratulations. Have you considered a career in archaeology?
970 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
971 Unless the first line of your fetchmail -V output includes the string "POP2",
972 you'll have to build it from sources yourself with the configure
973 switch --enable-POP2.
976 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.003") > 0:
977 warnings = warnings + """
978 This appears to be an old version of the UC Davis POP server. These are
979 dangerously unreliable (among other problems, they may drop your mailbox
980 on the floor if your connection is interrupted during the session).
982 It is strongly recommended that you find a better POP3 server. The fetchmail
983 FAQ includes pointers to good ones.
986 if string.find(greetline, "usa.net") > 0:
987 warnings = warnings + """
988 You appear to be using USA.NET's free mail service. Their POP3 servers
989 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
990 fetchmail can compensate. They seem to require that fetchall be switched on
991 (otherwise you won't necessarily see all your mail, not even new mail).
992 They also botch the TOP command the fetchmail normally uses for retrieval
993 (it only retrieves about 10 lines rather than the number specified).
994 Turning on fetchall will disable the use of TOP.
996 Therefore, it is strongly recommended that you turn on `fetchall' on all
997 user entries associated with this server.
1000 if string.find(greetline, "QPOP") > 0:
1001 warnings = warnings + """
1002 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1003 knows all about qpopper.
1006 closebrak = string.find(greetline, ">")
1007 if closebrak > 0 and greetline[closebrak+1] == "\r":
1008 warnings = warnings + """
1009 It looks like you could use APOP on this server and avoid sending it your
1010 password in clear. You should talk to the mailserver administrator about
1014 if string.find(greetline, "csi.com") > 0:
1015 warnings = warnings + """
1016 It appears you're talking to CompuServe. You can use their special RPA
1017 service for authentication, but only if your fetchmail -V output's first
1018 line contains the string "RPA". This is not included in stock fetchmail
1019 binaries; to compile it in, rebuild from sources with the configure
1020 option --enable-RPA.
1022 if string.find(greetline, "IMAP2bis") > 0:
1023 warnings = warnings + """
1024 IMAP2bis servers have a minor problem; they can't peek at messages without
1025 marking them seen. If you take a line hit during the retrieval, the
1026 interrupted message may get left on the server, marked seen.
1028 To work around this, it is recommended that you set the `fetchall'
1029 option on all user entries associated with this server, so any stuck
1030 mail will be retrieved next time around.
1034 warnings = warnings + """
1035 Fetchmail doesn't know anything special about this server type.
1038 # Display success window with warnings
1039 title = "Autoprobe of " + realhost + " succeeded"
1040 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1041 self.protocol.set(protocol)
1042 confwin.title(title)
1043 confwin.iconname(title)
1044 Label(confwin, text=title).pack()
1045 Message(confwin, text=confirm, width=600).pack()
1046 Button(confwin, text='Done',
1047 command=lambda x=confwin: Widget.destroy(x),
1048 relief=SUNKEN, bd=2).pack()
1051 # User editing stuff
1055 'title' : 'User option help',
1056 'banner': 'User options',
1058 You may use this panel to set options
1059 that may differ between individual
1062 Once you have a user configuration set
1063 up as you like it, you can select `Save' to
1064 store it in the server list maintained in
1065 the main configuration window.
1067 If you wish to discard the changes you have
1068 made to user options, select `Quit'.
1072 'title' : 'Local name help',
1073 'banner': 'Local names',
1075 The local name(s) in a user entry are the
1076 people on the client machine who should
1077 receive mail from the poll described.
1079 Note: if a user entry has more than one
1080 local name, messages will be retrieved
1081 in multidrop mode. This complicates
1082 the configuration issues; see the manual
1083 page section on multidrop mode.
1086 class UserEdit(Frame, MyWidget):
1087 def __init__(self, username, server):
1088 self.server = server
1090 for user in server.users:
1091 if user.remote == username:
1093 if self.user == None:
1095 self.user.remote = username
1096 self.user.localnames = [username]
1097 server.users.append(self.user)
1099 def edit(self, mode, master=None):
1100 Frame.__init__(self, master)
1102 self.master.title('Fetchmail user ' + self.user.remote
1103 + ' querying ' + self.server.pollname);
1104 self.master.iconname('Fetchmail user ' + self.user.remote);
1105 self.post(User, 'user')
1106 self.makeWidgets(mode, self.server.pollname)
1107 self.keepalive = [] # Use this to anchor the PhotoImage object
1108 make_icon_window(self, fetchmail_gif)
1111 # self.wait_window()
1114 if ConfirmQuit(self, 'user option editing'):
1115 Widget.destroy(self.master)
1118 self.fetch(User, 'user')
1119 Widget.destroy(self.master)
1121 def makeWidgets(self, mode, servername):
1122 dispose_window(self,
1123 "User options for " + self.user.remote + " querying " + servername,
1126 if mode != 'novice':
1127 leftwin = Frame(self);
1131 secwin = Frame(leftwin, relief=RAISED, bd=5)
1132 Label(secwin, text="Authentication").pack(side=TOP)
1133 LabeledEntry(secwin, 'Password:',
1134 self.password, '12').pack(side=TOP, fill=X)
1135 secwin.pack(fill=X, anchor=N)
1137 names = Frame(leftwin, relief=RAISED, bd=5)
1138 Label(names, text="Local names").pack(side=TOP)
1139 ListEdit("New name: ",
1140 self.user.localnames, None, names, localhelp)
1141 names.pack(fill=X, anchor=N)
1143 if mode != 'novice':
1144 targwin = Frame(leftwin, relief=RAISED, bd=5)
1145 Label(targwin, text="Forwarding Options").pack(side=TOP)
1146 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1147 ListEdit("New listener:", self.user.smtphunt, None, targwin, None)
1148 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1149 self.smtpaddress, '26').pack(side=TOP, fill=X)
1150 LabeledEntry(targwin, 'Connection setup command:',
1151 self.preconnect, '26').pack(side=TOP, fill=X)
1152 LabeledEntry(targwin, 'Connection wrapup command:',
1153 self.postconnect, '26').pack(side=TOP, fill=X)
1154 LabeledEntry(targwin, 'Local delivery agent:',
1155 self.mda, '26').pack(side=TOP, fill=X)
1156 LabeledEntry(targwin, 'Listener spam-block codes:',
1157 self.antispam, '26').pack(side=TOP, fill=X)
1158 targwin.pack(fill=X, anchor=N)
1160 if mode != 'novice':
1161 leftwin.pack(side=LEFT, fill=X, anchor=N)
1162 rightwin = Frame(self)
1166 optwin = Frame(rightwin, relief=RAISED, bd=5)
1167 Label(optwin, text="Processing Options").pack(side=TOP)
1168 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1169 variable=self.keep).pack(side=TOP, anchor=W)
1170 Checkbutton(optwin, text="Fetch old messages as well as new",
1171 variable=self.fetchall).pack(side=TOP, anchor=W)
1172 if mode != 'novice':
1173 Checkbutton(optwin, text="Flush seen messages before retrieval",
1174 variable=self.flush).pack(side=TOP, anchor=W)
1175 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1176 variable=self.rewrite).pack(side=TOP, anchor=W)
1177 Checkbutton(optwin, text="Force CR/LF at end of each line",
1178 variable=self.forcecr).pack(side=TOP, anchor=W)
1179 Checkbutton(optwin, text="Strip CR from end of eacgh line",
1180 variable=self.stripcr).pack(side=TOP, anchor=W)
1181 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1182 variable=self.pass8bits).pack(side=TOP, anchor=W)
1183 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1184 variable=self.mimedecode).pack(side=TOP, anchor=W)
1185 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1186 variable=self.dropstatus).pack(side=TOP, anchor=W)
1189 if mode != 'novice':
1190 limwin = Frame(rightwin, relief=RAISED, bd=5)
1191 Label(limwin, text="Resource Limits").pack(side=TOP)
1192 LabeledEntry(limwin, 'Message size limit:',
1193 self.limit, '30').pack(side=TOP, fill=X)
1194 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1195 self.fetchlimit, '30').pack(side=TOP, fill=X)
1196 LabeledEntry(limwin, 'Max messages to forward per poll:',
1197 self.batchlimit, '30').pack(side=TOP, fill=X)
1198 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1199 self.expunge, '30').pack(side=TOP, fill=X)
1202 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1203 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1204 ListEdit("New folder:", self.user.mailboxes, None, foldwin, None)
1205 foldwin.pack(fill=X, anchor=N)
1207 if mode != 'novice':
1208 rightwin.pack(side=LEFT)
1214 # Top-level window that offers either novice or expert mode
1215 # (but not both at once; it disappears when one is selected).
1218 class MainWindow(Frame):
1219 def __init__(self, outfile, master=None):
1220 Frame.__init__(self, master)
1221 self.outfile = outfile
1222 self.master.title('fetchmail configurator main');
1223 self.master.iconname('fetchmail configurator main');
1226 text='Fetchmailconf ' + version,
1227 bd=2).pack(side=TOP, pady=10)
1228 self.keepalive = [] # Use this to anchor the PhotoImage object
1229 make_icon_window(self, fetchmail_gif)
1231 Message(self, text="""
1232 Use `Novice Configuration' for basic fetchmail setup;
1233 with this, you can easily set up a single-drop connection
1234 to one remote mail server.
1235 """, width=600).pack(side=TOP)
1236 Button(self, text='Novice Configuration',
1237 fg='blue', command=self.novice).pack()
1239 Message(self, text="""
1240 Use `Expert Configuration' for advanced fetchmail setup,
1241 including multiple-site or multidrop connections.
1242 """, width=600).pack(side=TOP)
1243 Button(self, text='Expert Configuration',
1244 fg='blue', command=self.expert).pack()
1246 Message(self, text="""
1247 Or you can just select `Quit' to leave the configurator now.
1248 """, width=600).pack(side=TOP)
1249 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1253 ConfigurationEdit(Fetchmailrc, self.outfile).edit('novice')
1257 ConfigurationEdit(Fetchmailrc, self.outfile).edit('expert')
1262 # Functions for turning a dictionary into an instantiated object tree.
1264 def intersect(list1, list2):
1265 # Compute set intersection of lists
1272 def setdiff(list1, list2):
1273 # Compute set difference of lists
1280 def copy_instance(toclass, fromdict):
1281 # Initialize a class object of given type from a conformant dictionary.
1282 for fld in fromdict.keys():
1283 if not fld in dictmembers:
1284 dictmembers.append(fld)
1285 # The `optional' fields are the ones we can ignore for purposes of
1286 # conformability checking; they'll still get copied if they are
1287 # present in the dictionary.
1288 optional = ('interface', 'monitor', 'netsec');
1289 class_sig = setdiff(toclass.__dict__.keys(), optional)
1291 dict_keys = setdiff(fromdict.keys(), optional)
1293 common = intersect(class_sig, dict_keys)
1294 if 'typemap' in class_sig:
1295 class_sig.remove('typemap')
1296 if tuple(class_sig) != tuple(dict_keys):
1297 print "Fields don't match what fetchmailconf expected:"
1298 # print "Class signature: " + `class_sig`
1299 # print "Dictionary keys: " + `dict_keys`
1300 diff = setdiff(class_sig, common)
1302 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1303 diff = setdiff(dict_keys, common)
1305 print "Not matched in dictionary keys: " + `diff`
1309 setattr(toclass, x, fromdict[x])
1312 # And this is the main sequence. How it works:
1314 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1315 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1316 # Run execfile on the file to pull fetchmailrc into Python global space.
1317 # You don't want static data, though; you want, instead, a tree of objects
1318 # with the same data members and added appropriate methods.
1320 # This is what the copy_instance function() is for. It tries to copy a
1321 # dictionary field by field into a class, aborting if the class and dictionary
1322 # have different data members (except for any typemap member in the class;
1323 # that one is strictly for use by the MyWidget supperclass).
1325 # Once the object tree is set up, require user to choose novice or expert
1326 # mode and instantiate an edit object for the configuration. Class methods
1327 # will take it all from there.
1329 # Options (not documented because they're for fetchmailconf debuggers only):
1330 # -d: Read the configuration and dump it to stdout before editing. Dump
1331 # the edited result to stdout as well.
1332 # -f: specify the run control file to read.
1334 if __name__ == '__main__':
1337 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1338 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1339 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1340 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1341 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1342 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1343 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1344 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1345 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1346 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1347 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1348 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1349 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1350 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1351 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1352 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1353 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1354 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1355 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1356 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1357 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1358 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1359 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1360 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1361 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1362 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1363 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1364 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1365 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1366 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1367 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1368 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1372 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1373 dump = rcfile = None;
1374 for (switch, val) in options:
1375 if (switch == '-d'):
1377 elif (switch == '-f'):
1380 # Get client host's FQDN
1381 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1384 ConfigurationDefaults = Configuration()
1385 ServerDefaults = Server()
1386 UserDefaults = User()
1388 # Read the existing configuration
1389 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1391 cmd = "fetchmail -f " + rcfile + " --configdump >" + tmpfile
1393 cmd = "fetchmail --configdump >" + tmpfile
1398 print "`" + cmd + "' run failure, status " + `s`
1401 print "Unknown error while running fetchmail --configdump"
1408 print "Can't read configuration output of fetchmail --configdump."
1414 # The tricky part -- initializing objects from the configuration global
1415 # `Configuration' is the top level of the object tree we're going to mung.
1416 # The dictmembers list is used to track the set of fields the dictionary
1417 # contains; in particular, we can use it to tell whether things like the
1418 # monitor, interface, and netsec fields are present.
1420 Fetchmailrc = Configuration()
1421 copy_instance(Fetchmailrc, fetchmailrc)
1422 Fetchmailrc.servers = [];
1423 for server in fetchmailrc['servers']:
1425 copy_instance(Newsite, server)
1426 Fetchmailrc.servers.append(Newsite)
1428 for user in server['users']:
1430 copy_instance(Newuser, user)
1431 Newsite.users.append(Newuser)
1433 # We may want to display the configuration and quit
1435 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1437 # The theory here is that -f alone sets the rcfile location,
1438 # but -d and -f together mean the new configuration should go to stdout.
1439 if not rcfile and not dump:
1440 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1442 # OK, now run the configuration edit
1443 root = MainWindow(rcfile)
1446 # The following sets edit modes for GNU EMACS