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.plugin = None # Plugin command for going to server
75 self.plugout = None # Plugin command for going to listener
76 self.netsec = None # IPV6 security options
77 self.users = [] # List of user entries for site
79 ('pollname', 'String'),
81 ('active', 'Boolean'),
83 ('protocol', 'String'),
89 ('envelope', 'String'),
91 ('qvirtual', 'String'),
94 # leave localdomains out
95 ('interface', 'String'),
96 ('monitor', 'String'),
98 ('plugout', 'String'),
101 def dump(self, folded):
103 if self.active: str = str + "poll"
104 else: str = str + "skip"
105 str = str + (" " + self.pollname)
107 str = str + (" via \"%s\"\n" % (self.via,));
108 if self.protocol != ServerDefaults.protocol:
109 str = str + " with proto " + self.protocol
110 if self.port != defaultports[self.protocol] and self.port != 0:
111 str = str + " port " + `self.port`
112 if self.timeout != ServerDefaults.timeout:
113 str = str + " timeout " + `self.timeout`
114 if self.interval != ServerDefaults.interval:
115 str = str + " interval " + `self.interval`
116 if self.envelope != ServerDefaults.envelope or self.envskip != ServerDefaults.envskip:
118 str = str + " envelope " + self.envskip + " " + self.envelope
120 str = str + " envelope " + self.envelope
122 str = str + (" qvirtual \"%s\"\n" % (self.qvirtual,));
123 if self.auth != ServerDefaults.auth:
124 str = str + " auth " + self.auth
125 if self.dns != ServerDefaults.dns or self.uidl != ServerDefaults.uidl:
126 str = str + " and options"
127 if self.dns != ServerDefaults.dns:
128 str = str + flag2str(self.dns, 'dns')
129 if self.uidl != ServerDefaults.uidl:
130 str = str + flag2str(self.uidl, 'uidl')
131 if folded: str = str + "\n "
132 else: str = str + " "
138 if self.aka and self.localdomains: str = str + " "
139 if self.localdomains:
140 str = str + ("localdomains")
141 for x in self.localdomains:
143 if (self.aka or self.localdomains):
150 str = str + "interface \"" + self.interface + "\""
152 str = str + "monitor \"" + self.monitor + "\""
154 str = str + "netsec \"" + self.netsec + "\""
155 if self.interface or self.monitor or self.netsec:
159 if str[-1] == " ": str = str[0:-1]
161 for user in self.users:
162 str = str + repr(user)
167 return self.dump(TRUE)
170 return "[Server: " + self.dump(FALSE) + "]"
174 if os.environ.has_key("USER"):
175 self.remote = os.environ["USER"] # Remote username
176 elif os.environ.has_key("LOGNAME"):
177 self.remote = os.environ["LOGNAME"]
179 print "Can't get your username!"
181 self.remote = os.environ["USER"]# Remote username
182 self.localnames = [self.remote,]# Local names
183 self.password = None # Password for mail account access
184 self.mailboxes = [] # Remote folders to retrieve from
185 self.smtphunt = [] # Hosts to forward to
186 self.smtpaddress = None # Append this to MAIL FROM line
187 self.preconnect = None # Connection setup
188 self.postconnect = None # Connection wrapup
189 self.mda = None # Mail Delivery Agent
190 self.bsmtp = None # BSMTP output file
191 self.lmtp = FALSE # Use LMTP rather than SMTP?
192 self.antispam = "571 550 501" # Listener's spam-block code
193 self.keep = FALSE # Keep messages
194 self.flush = FALSE # Flush messages
195 self.fetchall = FALSE # Fetch old messages
196 self.rewrite = TRUE # Rewrite message headers
197 self.forcecr = FALSE # Force LF -> CR/LF
198 self.stripcr = FALSE # Strip CR
199 self.pass8bits = FALSE # Force BODY=7BIT
200 self.mimedecode = FALSE # Undo MIME armoring
201 self.dropstatus = FALSE # Drop incoming Status lines
202 self.limit = 0 # Message size limit
203 self.warnings = 0 # Size warning interval
204 self.fetchlimit = 0 # Max messages fetched per batch
205 self.batchlimit = 0 # Max message forwarded per batch
206 self.expunge = 1 # Interval between expunges (IMAP)
207 self.properties = None # Extension properties
209 ('remote', 'String'),
210 # leave out mailboxes and localnames
211 ('password', 'String'),
213 ('smtpaddress', 'String'),
214 ('preconnect', 'String'),
215 ('postconnect', 'String'),
219 ('antispam', 'String'),
221 ('flush', 'Boolean'),
222 ('fetchall', 'Boolean'),
223 ('rewrite', 'Boolean'),
224 ('forcecr', 'Boolean'),
225 ('stripcr', 'Boolean'),
226 ('pass8bits', 'Boolean'),
227 ('mimedecode', 'Boolean'),
228 ('dropstatus', 'Boolean'),
231 ('fetchlimit', 'Int'),
232 ('batchlimit', 'Int'),
234 ('properties', 'String'))
238 str = str + "user \"" + self.remote + "\" there ";
240 str = str + "with password \"" + self.password + '" '
243 for x in self.localnames:
246 if (self.keep != UserDefaults.keep
247 or self.flush != UserDefaults.flush
248 or self.fetchall != UserDefaults.fetchall
249 or self.rewrite != UserDefaults.rewrite
250 or self.forcecr != UserDefaults.forcecr
251 or self.stripcr != UserDefaults.stripcr
252 or self.pass8bits != UserDefaults.pass8bits
253 or self.mimedecode != UserDefaults.mimedecode
254 or self.dropstatus != UserDefaults.dropstatus):
255 str = str + " options"
256 if self.keep != UserDefaults.keep:
257 str = str + flag2str(self.keep, 'keep')
258 if self.flush != UserDefaults.flush:
259 str = str + flag2str(self.flush, 'flush')
260 if self.fetchall != UserDefaults.fetchall:
261 str = str + flag2str(self.fetchall, 'fetchall')
262 if self.rewrite != UserDefaults.rewrite:
263 str = str + flag2str(self.rewrite, 'rewrite')
264 if self.forcecr != UserDefaults.forcecr:
265 str = str + flag2str(self.forcecr, 'forcecr')
266 if self.stripcr != UserDefaults.stripcr:
267 str = str + flag2str(self.stripcr, 'stripcr')
268 if self.pass8bits != UserDefaults.pass8bits:
269 str = str + flag2str(self.pass8bits, 'pass8bits')
270 if self.mimedecode != UserDefaults.mimedecode:
271 str = str + flag2str(self.mimedecode, 'mimedecode')
272 if self.dropstatus != UserDefaults.dropstatus:
273 str = str + flag2str(self.dropstatus, 'dropstatus')
274 if self.limit != UserDefaults.limit:
275 str = str + " limit " + `self.limit`
276 if self.warnings != UserDefaults.warnings:
277 str = str + " warnings " + `self.warnings`
278 if self.fetchlimit != UserDefaults.fetchlimit:
279 str = str + " fetchlimit " + `self.fetchlimit`
280 if self.batchlimit != UserDefaults.batchlimit:
281 str = str + " batchlimit " + `self.batchlimit`
282 if self.expunge != UserDefaults.expunge:
283 str = str + " expunge " + `self.expunge`
285 trimmed = self.smtphunt;
286 if trimmed != [] and trimmed[len(trimmed) - 1] == "localhost":
287 trimmed = trimmed[0:len(trimmed) - 1]
288 if trimmed != [] and trimmed[len(trimmed) - 1] == hostname:
289 trimmed = trimmed[0:len(trimmed) - 1]
291 str = str + " smtphost "
296 str = str + " folder"
297 for x in self.mailboxes:
300 for fld in ('smtpaddress', 'preconnect', 'postconnect', 'mda', 'bsmtp', 'properties'):
301 if getattr(self, fld):
302 str = str + " %s %s\n" % (fld, `getattr(self, fld)`)
303 if self.lmtp != UserDefaults.lmtp:
304 str = str + flag2str(self.lmtp, 'lmtp')
305 if self.antispam != UserDefaults.antispam:
306 str = str + " antispam " + self.antispam + "\n"
310 return "[User: " + repr(self) + "]"
316 defaultports = {"auto":0,
326 authlist = ("password", "kerberos")
329 'title' : 'List Selection Help',
330 'banner': 'List Selection',
332 You must select an item in the list box (by clicking on it).
335 def flag2str(value, string):
336 # make a string representation of a .fetchmailrc flag or negated flag
340 if value == FALSE: str = str + ("no ")
344 class LabeledEntry(Frame):
345 # widget consisting of entry field with caption to left
346 def bind(self, key, action):
347 self.E.bind(key, action)
350 def __init__(self, Master, text, textvar, lwidth, ewidth=12):
351 Frame.__init__(self, Master)
352 self.L = Label(self, {'text':text, 'width':lwidth, 'anchor':'w'})
353 self.E = Entry(self, {'textvar':textvar, 'width':ewidth})
354 self.L.pack({'side':'left'})
355 self.E.pack({'side':'left', 'expand':'1', 'fill':'x'})
357 def ButtonBar(frame, legend, ref, alternatives, depth, command):
358 # array of radio buttons, caption to left, picking from a string list
360 width = len(alternatives) / depth;
361 Label(bar, text=legend).pack(side=LEFT)
362 for column in range(width):
363 subframe = Frame(bar)
364 for row in range(depth):
365 ind = width * row + column
366 Radiobutton(subframe,
367 {'text':alternatives[ind],
369 'value':alternatives[ind],
370 'command':command}).pack(side=TOP, anchor=W)
371 subframe.pack(side=LEFT)
375 def helpwin(helpdict):
376 # help message window with a self-destruct button
378 helpwin.title(helpdict['title'])
379 helpwin.iconname(helpdict['title'])
380 Label(helpwin, text=helpdict['banner']).pack()
381 textwin = Message(helpwin, text=helpdict['text'], width=600)
383 Button(helpwin, text='Done',
384 command=lambda x=helpwin: Widget.destroy(x),
385 relief=SUNKEN, bd=2).pack()
387 def make_icon_window(base, image):
389 # Some older pythons will error out on this
390 icon_image = PhotoImage(data=image)
391 icon_window = Toplevel()
392 Label(icon_window, image=icon_image, bg='black').pack()
393 base.master.iconwindow(icon_window)
394 # Avoid TkInter brain death. PhotoImage objects go out of
395 # scope when the enclosing function returns. Therefore
396 # we have to explicitly link them to something.
397 base.keepalive.append(icon_image)
401 class ListEdit(Frame):
402 # edit a list of values (duplicates not allowed) with a supplied editor hook
403 def __init__(self, newlegend, list, editor, master, helptxt):
407 # Set up a widget to accept new elements
408 self.newval = StringVar(master)
409 newwin = LabeledEntry(master, newlegend, self.newval, '12')
410 newwin.bind('<Double-1>', self.handleNew)
411 newwin.bind('<Return>', self.handleNew)
412 newwin.pack(side=TOP, fill=X, anchor=E)
414 # Edit the existing list
415 listframe = Frame(master)
416 scroll = Scrollbar(listframe)
417 self.listwidget = Listbox(listframe, height=0, selectmode='browse')
420 self.listwidget.insert(END, x)
421 listframe.pack(side=TOP, expand=YES, fill=BOTH)
422 self.listwidget.config(yscrollcommand=scroll.set, relief=SUNKEN)
423 self.listwidget.pack(side=LEFT, expand=YES, fill=BOTH)
424 scroll.config(command=self.listwidget.yview, relief=SUNKEN)
425 scroll.pack(side=RIGHT, fill=BOTH)
426 self.listwidget.config(selectmode=SINGLE, setgrid=TRUE)
427 self.listwidget.bind('<Double-1>', self.handleList);
428 self.listwidget.bind('<Return>', self.handleList);
432 Button(bf, text='Edit', command=self.editItem).pack(side=LEFT)
433 Button(bf, text='Delete', command=self.deleteItem).pack(side=LEFT)
435 self.helptxt = helptxt
436 Button(bf, text='Help', fg='blue',
437 command=self.help).pack(side=RIGHT)
441 helpwin(self.helptxt)
443 def handleList(self, event):
446 def handleNew(self, event):
447 item = self.newval.get()
448 entire = self.listwidget.get(0, self.listwidget.index('end'));
449 if item and (not entire) or (not item in self.listwidget.get(0, self.listwidget.index('end'))):
450 self.listwidget.insert('end', item)
451 if self.list != None: self.list.append(item)
455 select = self.listwidget.curselection()
460 if index and self.editor:
461 label = self.listwidget.get(index);
462 apply(self.editor, (label,))
464 def deleteItem(self):
465 select = self.listwidget.curselection()
469 index = string.atoi(select[0])
470 self.listwidget.delete(index)
471 if self.list != None: del self.list[index]
473 def ConfirmQuit(frame, context):
476 text = 'Really quit ' + context + ' without saving?',
478 strings = ('Yes', 'No'),
482 def dispose_window(master, legend, help):
483 dispose = Frame(master, relief=RAISED, bd=5)
484 Label(dispose, text=legend).pack(side=TOP,pady=10)
485 Button(dispose, text='Save', fg='blue',
486 command=master.save).pack(side=LEFT)
487 Button(dispose, text='Quit', fg='blue',
488 command=master.nosave).pack(side=LEFT)
489 Button(dispose, text='Help', fg='blue',
490 command=lambda x=help: helpwin(x)).pack(side=RIGHT)
495 # Common methods for Tkinter widgets -- deals with Tkinter declaration
496 def post(self, widgetclass, field):
497 for x in widgetclass.typemap:
498 if x[1] == 'Boolean':
499 setattr(self, x[0], BooleanVar(self))
500 elif x[1] == 'String':
501 setattr(self, x[0], StringVar(self))
503 setattr(self, x[0], IntVar(self))
504 source = getattr(getattr(self, field), x[0])
506 getattr(self, x[0]).set(source)
508 def fetch(self, widgetclass, field):
509 for x in widgetclass.typemap:
510 setattr(getattr(self, field), x[0], getattr(self, x[0]).get())
513 # First, code to set the global fetchmail run controls.
516 configure_novice_help = {
517 'title' : 'Fetchmail novice configurator help',
518 'banner': 'Novice configurator help',
520 In the `Novice Configurator Controls' panel, you can:
522 Press `Save' to save the new fetchmail configuration you have created.
523 Press `Quit' to exit without saving.
524 Press `Help' to bring up this help message.
526 In the `Novice Configuration' panels, you will set up the basic data
527 needed to create a simple fetchmail setup. These include:
529 1. The name of the remote site you want to query.
531 2. Your login name on that site.
533 3. Your password on that site.
535 4. A protocol to use (POP, IMAP, ETRN, etc.)
537 5. A polling interval.
539 6. Options to fetch old messages as well as new, uor to suppress
540 deletion of fetched message.
542 The novice-configuration code will assume that you want to forward mail
543 to a local sendmail listener with no special options.
546 configure_expert_help = {
547 'title' : 'Fetchmail expert configurator help',
548 'banner': 'Expert configurator help',
550 In the `Expert Configurator Controls' panel, you can:
552 Press `Save' to save the new fetchmail configuration you have edited.
553 Press `Quit' to exit without saving.
554 Press `Help' to bring up this help message.
556 In the `Run Controls' panel, you can set the following options that
557 control how fetchmail runs:
560 Number of seconds to wait between polls in the background.
561 If zero, fetchmail will run in foreground.
564 If empty, emit progress and error messages to stderr.
565 Otherwise this gives the name of the files to write to.
566 This field is ignored if the "Log to syslog?" option is on.
569 If empty, store seen-message IDs in .fetchids under user's home
570 directory. If nonempty, use given file name.
573 Who to send multidrop mail to as a last resort if no address can
574 be matched. Normally empty; in this case, fetchmail treats the
575 invoking user as the address of last resort unless that user is
576 root. If that user is root, fetchmail sends to `postmaster'.
579 If false (the default) fetchmail generates a Received line into
580 each message and generates a HELO from the machine it is running on.
581 If true, fetchmail generates no Received line and HELOs as if it were
584 In the `Remote Mail Configurations' panel, you can:
586 1. Enter the name of a new remote mail server you want fetchmail to query.
588 To do this, simply enter a label for the poll configuration in the
589 `New Server:' box. The label should be a DNS name of the server (unless
590 you are using ssh or some other tunneling method and will fill in the `via'
591 option on the site configuration screen).
593 2. Change the configuration of an existing site.
595 To do this, find the site's label in the listbox and double-click it.
596 This will take you to a site configuration dialogue.
600 class ConfigurationEdit(Frame, MyWidget):
601 def __init__(self, configuration, outfile, master=None):
602 self.configuration = configuration
603 self.outfile = outfile
604 self.container = master
605 ConfigurationEdit.mode_to_help = {
606 'novice':configure_novice_help, 'expert':configure_expert_help
609 def edit(self, mode):
611 Frame.__init__(self, self.container)
612 self.master.title('fetchmail ' + self.mode + ' configurator');
613 self.master.iconname('fetchmail ' + self.mode + ' configurator');
614 self.keepalive = [] # Use this to anchor the PhotoImage object
615 make_icon_window(self, fetchmail_gif)
617 self.post(Configuration, 'configuration')
620 'Configurator ' + self.mode + ' Controls',
621 ConfigurationEdit.mode_to_help[self.mode])
623 gf = Frame(self, relief=RAISED, bd = 5)
625 text='Fetchmail Run Controls',
626 bd=2).pack(side=TOP, pady=10)
631 if self.mode != 'novice':
633 log = LabeledEntry(ff, ' Postmaster:', self.postmaster, '14')
634 log.pack(side=RIGHT, anchor=E)
635 # Set the poll interval
636 de = LabeledEntry(ff, ' Poll interval:', self.poll_interval, '14')
637 de.pack(side=RIGHT, anchor=E)
642 if self.mode != 'novice':
645 {'text':'Log to syslog?',
646 'variable':self.syslog,
647 'relief':GROOVE}).pack(side=LEFT, anchor=W)
648 log = LabeledEntry(sf, ' Logfile:', self.logfile, '14')
649 log.pack(side=RIGHT, anchor=E)
653 {'text':'Invisible mode?',
654 'variable':self.invisible,
655 'relief':GROOVE}).pack(side=LEFT, anchor=W)
657 log = LabeledEntry(gf, ' Idfile:', self.idfile, '14')
658 log.pack(side=RIGHT, anchor=E)
662 # Expert mode allows us to edit multiple sites
663 lf = Frame(self, relief=RAISED, bd=5)
665 text='Remote Mail Server Configurations',
666 bd=2).pack(side=TOP, pady=10)
667 ListEdit('New Server:',
668 map(lambda x: x.pollname, self.configuration.servers),
669 lambda site, m=self.mode, s=self.configuration.servers:
670 ServerEdit(site, s).edit(m, Toplevel()),
675 if ConfirmQuit(self, self.mode + " configuration editor"):
679 self.fetch(Configuration, 'configuration')
683 elif not os.path.isfile(self.outfile) or Dialog(self,
684 title = 'Overwrite existing run control file?',
685 text = 'Really overwrite existing run control file?',
687 strings = ('Yes', 'No'),
688 default = 1).num == 0:
689 fm = open(self.outfile, 'w')
691 fm.write("# Configuration created %s by fetchmailconf\n" % time.ctime(time.time()))
692 fm.write(`self.configuration`)
693 os.chmod(self.outfile, 0600)
697 # Server editing stuff.
700 'title' : 'Remote site help',
701 'banner': 'Remote sites',
703 When you add a site name to the list here,
704 you initialize an entry telling fetchmail
705 how to poll a new site.
707 When you select a sitename (by double-
708 clicking it, or by single-clicking to
709 select and then clicking the Edit button),
710 you will open a window to configure that
715 'title' : 'Server options help',
716 'banner': 'Server Options',
718 The server options screen controls fetchmail
719 options that apply to one of your mailservers.
721 Once you have a mailserver configuration set
722 up as you like it, you can select `Save' to
723 store it in the server list maintained in
724 the main configuration window.
726 If you wish to discard changes to a server
727 configuration, select `Quit'.
731 'title' : 'Run Control help',
732 'banner': 'Run Controls',
734 If the `Poll normally' checkbox is on, the host is polled as part of
735 the normal operation of fetchmail when it is run with no arguments.
736 If it is off, fetchmail will only query this host when it is given as
737 a command-line argument.
739 The `True name of server' box should specify the actual DNS name
740 to query. By default this is the same as the poll name.
742 Normally each host described in the file is queried once each
743 poll cycle. If `Cycles to skip between polls' is greater than 0,
744 that's the number of poll cycles that are skipped between the
745 times this post is actually polled.
747 The `Server timeout' is the number of seconds fetchmail will wait
748 for a reply from the mailserver before concluding it is hung and
753 'title' : 'Protocol and Port help',
754 'banner': 'Protocol and Port',
756 These options control the remote-mail protocol
757 and TCP/IP service port used to query this
760 If you click the `Probe for supported protocols'
761 button, fetchmail will try to find you the most
762 capable server on the selected host (this will
763 only work if you're conncted to the Internet).
764 The probe only checks for ordinary IMAP and POP
765 protocols; fortunately these are the most
766 frequently supported.
768 The `Protocol' button bar offers you a choice of
769 all the different protocols available. The `auto'
770 protocol is the default mode; it probes the host
771 ports for POP3 and IMAP to see if either is
774 Normally the TCP/IP service port to use is
775 dictated by the protocol choice. The `Port'
776 field (only present in expert mode) lets you
777 set a non-standard port.
781 'title' : 'Security option help',
782 'banner': 'Security',
784 The `interface' option allows you to specify a range
785 of IP addresses to monitor for activity. If these
786 addresses are not active, fetchmail will not poll.
787 Specifying this may protect you from a spoofing attack
788 if your client machine has more than one IP gateway
789 address and some of the gateways are to insecure nets.
791 The `monitor' option, if given, specifies the only
792 device through which fetchmail is permitted to connect
793 to servers. This option may be used to prevent
794 fetchmail from triggering an expensive dial-out if the
795 interface is not already active.
797 The `interface' and `monitor' options are available
798 only for Linux systems. See the fetchmail manual page
799 for details on these.
801 The `netsec' option will be configurable only if fetchmail
802 was compiled with IPV6 support. If you need to use it,
803 you probably know what to do.
807 'title' : 'Multidrop option help',
808 'banner': 'Multidrop',
810 These options are only useful with multidrop mode.
811 See the manual page for extended discussion.
815 'title' : 'User list help',
816 'banner': 'User list',
818 When you add a user name to the list here,
819 you initialize an entry telling fetchmail
820 to poll the site on behalf of the new user.
822 When you select a username (by double-
823 clicking it, or by single-clicking to
824 select and then clicking the Edit button),
825 you will open a window to configure the
826 user's options on that site.
829 class ServerEdit(Frame, MyWidget):
830 def __init__(self, host, servers):
833 if site.pollname == host:
835 if (self.server == None):
836 self.server = Server()
837 self.server.pollname = host
838 self.server.via = None
839 servers.append(self.server)
841 def edit(self, mode, master=None):
842 Frame.__init__(self, master)
844 self.master.title('Fetchmail host ' + self.server.pollname);
845 self.master.iconname('Fetchmail host ' + self.server.pollname);
846 self.post(Server, 'server')
847 self.makeWidgets(self.server.pollname, mode)
848 self.keepalive = [] # Use this to anchor the PhotoImage object
849 make_icon_window(self, fetchmail_gif)
855 if ConfirmQuit(self, 'server option editing'):
856 Widget.destroy(self.master)
859 self.fetch(Server, 'server')
860 Widget.destroy(self.master)
862 def refreshPort(self):
863 proto = self.protocol.get()
864 self.port.set(defaultports[proto])
865 if not proto in ("POP3", "APOP", "KPOP"): self.uidl.state = DISABLED
867 def makeWidgets(self, host, mode):
868 topwin = dispose_window(self, "Server options for querying " + host, serverhelp)
870 leftwin = Frame(self);
874 ctlwin = Frame(leftwin, relief=RAISED, bd=5)
875 Label(ctlwin, text="Run Controls").pack(side=TOP)
876 Checkbutton(ctlwin, text='Poll ' + host + ' normally?', variable=self.active).pack(side=TOP)
877 LabeledEntry(ctlwin, 'True name of ' + host + ':',
878 self.via, leftwidth).pack(side=TOP, fill=X)
879 LabeledEntry(ctlwin, 'Cycles to skip between polls:',
880 self.interval, leftwidth).pack(side=TOP, fill=X)
881 LabeledEntry(ctlwin, 'Server timeout (seconds):',
882 self.timeout, leftwidth).pack(side=TOP, fill=X)
883 Button(ctlwin, text='Help', fg='blue',
884 command=lambda: helpwin(controlhelp)).pack(side=RIGHT)
887 # Compute the available protocols from the compile-time options
889 if 'pop2' in feature_options:
890 protolist.append("POP2")
891 if 'pop3' in feature_options:
892 protolist = protolist + ["POP3", "APOP", "KPOP"]
893 if 'sdps' in feature_options:
894 protolist.append("SDPS")
895 if 'imap' in feature_options:
896 protolist.append("IMAP")
897 if 'imap-gss' in feature_options:
898 protolist.append("IMAP-GSS")
899 if 'imap-k4' in feature_options:
900 protolist.append("IMAP-K4")
901 if 'etrn' in feature_options:
902 protolist.append("ETRN")
904 protwin = Frame(leftwin, relief=RAISED, bd=5)
905 Label(protwin, text="Protocol").pack(side=TOP)
906 ButtonBar(protwin, '',
907 self.protocol, protolist, 2,
910 LabeledEntry(protwin, 'On server TCP/IP port:',
911 self.port, leftwidth).pack(side=TOP, fill=X)
914 text="POP3: track `seen' with client-side UIDLs?",
915 variable=self.uidl).pack(side=TOP)
916 Button(protwin, text='Probe for supported protocols', fg='blue',
917 command=self.autoprobe).pack(side=LEFT)
918 Button(protwin, text='Help', fg='blue',
919 command=lambda: helpwin(protohelp)).pack(side=RIGHT)
922 userwin = Frame(leftwin, relief=RAISED, bd=5)
923 Label(userwin, text="User entries for " + host).pack(side=TOP)
924 ListEdit("New user: ",
925 map(lambda x: x.remote, self.server.users),
926 lambda u, m=mode, s=self.server: UserEdit(u,s).edit(m, Toplevel()),
930 leftwin.pack(side=LEFT, anchor=N, fill=X);
933 rightwin = Frame(self);
935 mdropwin = Frame(rightwin, relief=RAISED, bd=5)
936 Label(mdropwin, text="Multidrop options").pack(side=TOP)
937 LabeledEntry(mdropwin, 'Envelope address header:',
938 self.envelope, '22').pack(side=TOP, fill=X)
939 LabeledEntry(mdropwin, 'Envelope headers to skip:',
940 self.envskip, '22').pack(side=TOP, fill=X)
941 LabeledEntry(mdropwin, 'Name prefix to strip:',
942 self.qvirtual, '22').pack(side=TOP, fill=X)
943 Checkbutton(mdropwin, text="Enable multidrop DNS lookup?",
944 variable=self.dns).pack(side=TOP)
945 Label(mdropwin, text="DNS aliases").pack(side=TOP)
946 ListEdit("New alias: ", self.server.aka, None, mdropwin, None)
947 Label(mdropwin, text="Domains to be considered local").pack(side=TOP)
948 ListEdit("New domain: ",
949 self.server.localdomains, None, mdropwin, multihelp)
950 mdropwin.pack(fill=X)
952 if os_type == 'linux' or 'netsec' in feature_options:
953 secwin = Frame(rightwin, relief=RAISED, bd=5)
954 Label(secwin, text="Security").pack(side=TOP)
955 # Don't actually let users set this. KPOP sets it implicitly
956 # ButtonBar(secwin, 'Authorization mode:',
957 # self.auth, authlist, 1, None).pack(side=TOP)
958 if os_type == 'linux' or 'interface' in dictmembers:
959 LabeledEntry(secwin, 'IP range to check before poll:',
960 self.interface, leftwidth).pack(side=TOP, fill=X)
961 if os_type == 'linux' or 'monitor' in dictmembers:
962 LabeledEntry(secwin, 'Interface to monitor:',
963 self.monitor, leftwidth).pack(side=TOP, fill=X)
964 if 'netsec' in feature_options or 'netsec' in dictmembers:
965 LabeledEntry(secwin, 'IPV6 security options:',
966 self.netsec, leftwidth).pack(side=TOP, fill=X)
967 Button(secwin, text='Help', fg='blue',
968 command=lambda: helpwin(sechelp)).pack(side=RIGHT)
971 rightwin.pack(side=LEFT, anchor=N);
974 # Note: this only handles case (1) near fetchmail.c:892
975 # We're assuming people smart enough to set up ssh tunneling
976 # won't need autoprobing.
977 if self.server.via != None:
978 realhost = self.server.via
980 realhost = self.server.pollname
982 for (protocol, port) in (("IMAP",143), ("POP3",110), ("POP2",109)):
983 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
985 sock.connect(realhost, port)
986 greetline = sock.recv(1024)
993 if greetline == None:
994 title = "Autoprobe of " + realhost + " failed"
996 Fetchmailconf didn't find any mailservers active.
997 This could mean the host doesn't support any,
998 or that your Internet connection is down, or
999 that the host is so slow that the probe timed
1000 out before getting a response.
1004 # OK, now try to recognize potential problems
1006 if protocol == "POP2":
1007 warnings = warnings + """
1008 It appears you have somehow found a mailserver running only POP2.
1009 Congratulations. Have you considered a career in archaeology?
1011 Unfortunately, stock fetchmail binaries don't include POP2 support anymore.
1012 Unless the first line of your fetchmail -V output includes the string "POP2",
1013 you'll have to build it from sources yourself with the configure
1014 switch --enable-POP2.
1017 if string.find(greetline, "1.003") > 0 or string.find(greetline, "1.004") > 0:
1018 warnings = warnings + """
1019 This appears to be an old version of the UC Davis POP server. These are
1020 dangerously unreliable (among other problems, they may drop your mailbox
1021 on the floor if your connection is interrupted during the session).
1023 It is strongly recommended that you find a better POP3 server. The fetchmail
1024 FAQ includes pointers to good ones.
1027 if string.find(greetline, "usa.net") > 0:
1028 warnings = warnings + """
1029 You appear to be using USA.NET's free mail service. Their POP3 servers
1030 (at least as of the 2.2 version in use mid-1998) are quite flaky, but
1031 fetchmail can compensate. They seem to require that fetchall be switched on
1032 (otherwise you won't necessarily see all your mail, not even new mail).
1033 They also botch the TOP command the fetchmail normally uses for retrieval
1034 (it only retrieves about 10 lines rather than the number specified).
1035 Turning on fetchall will disable the use of TOP.
1037 Therefore, it is strongly recommended that you turn on `fetchall' on all
1038 user entries associated with this server.
1041 if string.find(greetline, "QPOP") > 0:
1042 warnings = warnings + """
1043 This appears to be a version of Eudora qpopper. That's good. Fetchmail
1044 knows all about qpopper.
1047 closebrak = string.find(greetline, ">")
1048 if closebrak > 0 and greetline[closebrak+1] == "\r":
1049 warnings = warnings + """
1050 It looks like you could use APOP on this server and avoid sending it your
1051 password in clear. You should talk to the mailserver administrator about
1055 if string.find(greetline, "csi.com") > 0:
1056 warnings = warnings + """
1057 It appears you're talking to CompuServe. You can use their special RPA
1058 service for authentication, but only if your fetchmail -V output's first
1059 line contains the string "RPA". This is not included in stock fetchmail
1060 binaries; to compile it in, rebuild from sources with the configure
1061 option --enable-RPA.
1063 if string.find(greetline, "IMAP2bis") > 0:
1064 warnings = warnings + """
1065 IMAP2bis servers have a minor problem; they can't peek at messages without
1066 marking them seen. If you take a line hit during the retrieval, the
1067 interrupted message may get left on the server, marked seen.
1069 To work around this, it is recommended that you set the `fetchall'
1070 option on all user entries associated with this server, so any stuck
1071 mail will be retrieved next time around.
1074 if string.find(greetline, "IMAP4rev1") > 0:
1075 warnings = warnings + """
1076 I see an IMAP4rev1 server. Excellent. This is (a) the best kind of
1077 remote-mail server, and (b) the one the fetchmail author uses. Fetchmail
1078 has therefore been extremely well tested with this class of server.
1081 warnings = warnings + """
1082 Fetchmail doesn't know anything special about this server type.
1085 # Display success window with warnings
1086 title = "Autoprobe of " + realhost + " succeeded"
1087 confirm = "The " + protocol + " server said:\n\n" + greetline + warnings
1088 self.protocol.set(protocol)
1089 confwin.title(title)
1090 confwin.iconname(title)
1091 Label(confwin, text=title).pack()
1092 Message(confwin, text=confirm, width=600).pack()
1093 Button(confwin, text='Done',
1094 command=lambda x=confwin: Widget.destroy(x),
1095 relief=SUNKEN, bd=2).pack()
1098 # User editing stuff
1102 'title' : 'User option help',
1103 'banner': 'User options',
1105 You may use this panel to set options
1106 that may differ between individual
1109 Once you have a user configuration set
1110 up as you like it, you can select `Save' to
1111 store it in the server list maintained in
1112 the main configuration window.
1114 If you wish to discard the changes you have
1115 made to user options, select `Quit'.
1119 'title' : 'Local name help',
1120 'banner': 'Local names',
1122 The local name(s) in a user entry are the
1123 people on the client machine who should
1124 receive mail from the poll described.
1126 Note: if a user entry has more than one
1127 local name, messages will be retrieved
1128 in multidrop mode. This complicates
1129 the configuration issues; see the manual
1130 page section on multidrop mode.
1133 class UserEdit(Frame, MyWidget):
1134 def __init__(self, username, server):
1135 self.server = server
1137 for user in server.users:
1138 if user.remote == username:
1140 if self.user == None:
1142 self.user.remote = username
1143 self.user.localnames = [username]
1144 server.users.append(self.user)
1146 def edit(self, mode, master=None):
1147 Frame.__init__(self, master)
1149 self.master.title('Fetchmail user ' + self.user.remote
1150 + ' querying ' + self.server.pollname);
1151 self.master.iconname('Fetchmail user ' + self.user.remote);
1152 self.post(User, 'user')
1153 self.makeWidgets(mode, self.server.pollname)
1154 self.keepalive = [] # Use this to anchor the PhotoImage object
1155 make_icon_window(self, fetchmail_gif)
1158 # self.wait_window()
1161 if ConfirmQuit(self, 'user option editing'):
1162 Widget.destroy(self.master)
1165 self.fetch(User, 'user')
1166 Widget.destroy(self.master)
1168 def makeWidgets(self, mode, servername):
1169 dispose_window(self,
1170 "User options for " + self.user.remote + " querying " + servername,
1173 if mode != 'novice':
1174 leftwin = Frame(self);
1178 secwin = Frame(leftwin, relief=RAISED, bd=5)
1179 Label(secwin, text="Authentication").pack(side=TOP)
1180 LabeledEntry(secwin, 'Password:',
1181 self.password, '12').pack(side=TOP, fill=X)
1182 secwin.pack(fill=X, anchor=N)
1184 names = Frame(leftwin, relief=RAISED, bd=5)
1185 Label(names, text="Local names").pack(side=TOP)
1186 ListEdit("New name: ",
1187 self.user.localnames, None, names, localhelp)
1188 names.pack(fill=X, anchor=N)
1190 if mode != 'novice':
1191 targwin = Frame(leftwin, relief=RAISED, bd=5)
1192 Label(targwin, text="Forwarding Options").pack(side=TOP)
1193 Label(targwin, text="Listeners to forward to").pack(side=TOP)
1194 ListEdit("New listener:", self.user.smtphunt, None, targwin, None)
1195 LabeledEntry(targwin, 'Append to MAIL FROM line:',
1196 self.smtpaddress, '26').pack(side=TOP, fill=X)
1197 LabeledEntry(targwin, 'Connection setup command:',
1198 self.preconnect, '26').pack(side=TOP, fill=X)
1199 LabeledEntry(targwin, 'Connection wrapup command:',
1200 self.postconnect, '26').pack(side=TOP, fill=X)
1201 LabeledEntry(targwin, 'Local delivery agent:',
1202 self.mda, '26').pack(side=TOP, fill=X)
1203 LabeledEntry(targwin, 'BSMTP output file:',
1204 self.bsmtp, '26').pack(side=TOP, fill=X)
1205 LabeledEntry(targwin, 'Listener spam-block codes:',
1206 self.antispam, '26').pack(side=TOP, fill=X)
1207 LabeledEntry(targwin, 'Pass-through properties:',
1208 self.properties, '26').pack(side=TOP, fill=X)
1209 Checkbutton(targwin, text="Use LMTP?",
1210 variable=self.lmtp).pack(side=TOP, fill=X)
1211 targwin.pack(fill=X, anchor=N)
1213 if mode != 'novice':
1214 leftwin.pack(side=LEFT, fill=X, anchor=N)
1215 rightwin = Frame(self)
1219 optwin = Frame(rightwin, relief=RAISED, bd=5)
1220 Label(optwin, text="Processing Options").pack(side=TOP)
1221 Checkbutton(optwin, text="Suppress deletion of messages after reading",
1222 variable=self.keep).pack(side=TOP, anchor=W)
1223 Checkbutton(optwin, text="Fetch old messages as well as new",
1224 variable=self.fetchall).pack(side=TOP, anchor=W)
1225 if mode != 'novice':
1226 Checkbutton(optwin, text="Flush seen messages before retrieval",
1227 variable=self.flush).pack(side=TOP, anchor=W)
1228 Checkbutton(optwin, text="Rewrite To/Cc/Bcc messages to enable reply",
1229 variable=self.rewrite).pack(side=TOP, anchor=W)
1230 Checkbutton(optwin, text="Force CR/LF at end of each line",
1231 variable=self.forcecr).pack(side=TOP, anchor=W)
1232 Checkbutton(optwin, text="Strip CR from end of each line",
1233 variable=self.stripcr).pack(side=TOP, anchor=W)
1234 Checkbutton(optwin, text="Pass 8 bits even though SMTP says 7BIT",
1235 variable=self.pass8bits).pack(side=TOP, anchor=W)
1236 Checkbutton(optwin, text="Undo MIME armoring on header and body",
1237 variable=self.mimedecode).pack(side=TOP, anchor=W)
1238 Checkbutton(optwin, text="Drop Status lines from forwarded messages",
1239 variable=self.dropstatus).pack(side=TOP, anchor=W)
1242 if mode != 'novice':
1243 limwin = Frame(rightwin, relief=RAISED, bd=5)
1244 Label(limwin, text="Resource Limits").pack(side=TOP)
1245 LabeledEntry(limwin, 'Message size limit:',
1246 self.limit, '30').pack(side=TOP, fill=X)
1247 LabeledEntry(limwin, 'Size warning interval:',
1248 self.warnings, '30').pack(side=TOP, fill=X)
1249 LabeledEntry(limwin, 'Max messages to fetch per poll:',
1250 self.fetchlimit, '30').pack(side=TOP, fill=X)
1251 LabeledEntry(limwin, 'Max messages to forward per poll:',
1252 self.batchlimit, '30').pack(side=TOP, fill=X)
1253 if self.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1254 LabeledEntry(limwin, 'Interval between expunges (IMAP):',
1255 self.expunge, '30').pack(side=TOP, fill=X)
1258 if self.server.protocol in ('IMAP', 'IMAP-K4', 'IMAP-GSS'):
1259 foldwin = Frame(rightwin, relief=RAISED, bd=5)
1260 Label(foldwin, text="Remote folders (IMAP only)").pack(side=TOP)
1261 ListEdit("New folder:", self.user.mailboxes, None, foldwin, None)
1262 foldwin.pack(fill=X, anchor=N)
1264 if mode != 'novice':
1265 rightwin.pack(side=LEFT)
1271 # Top-level window that offers either novice or expert mode
1272 # (but not both at once; it disappears when one is selected).
1275 class MainWindow(Frame):
1276 def __init__(self, outfile, master=None):
1277 Frame.__init__(self, master)
1278 self.outfile = outfile
1279 self.master.title('fetchmail configurator main');
1280 self.master.iconname('fetchmail configurator main');
1283 text='Fetchmailconf ' + version,
1284 bd=2).pack(side=TOP, pady=10)
1285 self.keepalive = [] # Use this to anchor the PhotoImage object
1286 make_icon_window(self, fetchmail_gif)
1288 Message(self, text="""
1289 Use `Novice Configuration' for basic fetchmail setup;
1290 with this, you can easily set up a single-drop connection
1291 to one remote mail server.
1292 """, width=600).pack(side=TOP)
1293 Button(self, text='Novice Configuration',
1294 fg='blue', command=self.novice).pack()
1296 Message(self, text="""
1297 Use `Expert Configuration' for advanced fetchmail setup,
1298 including multiple-site or multidrop connections.
1299 """, width=600).pack(side=TOP)
1300 Button(self, text='Expert Configuration',
1301 fg='blue', command=self.expert).pack()
1303 Message(self, text="""
1304 Or you can just select `Quit' to leave the configurator now.
1305 """, width=600).pack(side=TOP)
1306 Button(self, text='Quit', fg='blue', command=self.leave).pack()
1310 ConfigurationEdit(Fetchmailrc, self.outfile).edit('novice')
1314 ConfigurationEdit(Fetchmailrc, self.outfile).edit('expert')
1319 # Functions for turning a dictionary into an instantiated object tree.
1321 def intersect(list1, list2):
1322 # Compute set intersection of lists
1329 def setdiff(list1, list2):
1330 # Compute set difference of lists
1337 def copy_instance(toclass, fromdict):
1338 # Initialize a class object of given type from a conformant dictionary.
1339 for fld in fromdict.keys():
1340 if not fld in dictmembers:
1341 dictmembers.append(fld)
1342 # The `optional' fields are the ones we can ignore for purposes of
1343 # conformability checking; they'll still get copied if they are
1344 # present in the dictionary.
1345 optional = ('interface', 'monitor', 'netsec');
1346 class_sig = setdiff(toclass.__dict__.keys(), optional)
1348 dict_keys = setdiff(fromdict.keys(), optional)
1350 common = intersect(class_sig, dict_keys)
1351 if 'typemap' in class_sig:
1352 class_sig.remove('typemap')
1353 if tuple(class_sig) != tuple(dict_keys):
1354 print "Fields don't match what fetchmailconf expected:"
1355 # print "Class signature: " + `class_sig`
1356 # print "Dictionary keys: " + `dict_keys`
1357 diff = setdiff(class_sig, common)
1359 print "Not matched in class `" + toclass.__class__.__name__ + "' signature: " + `diff`
1360 diff = setdiff(dict_keys, common)
1362 print "Not matched in dictionary keys: " + `diff`
1366 setattr(toclass, x, fromdict[x])
1369 # And this is the main sequence. How it works:
1371 # First, call `fetchmail --configdump' and trap the output in a tempfile.
1372 # This should fill it with a Python initializer for a variable `fetchmailrc'.
1373 # Run execfile on the file to pull fetchmailrc into Python global space.
1374 # You don't want static data, though; you want, instead, a tree of objects
1375 # with the same data members and added appropriate methods.
1377 # This is what the copy_instance function() is for. It tries to copy a
1378 # dictionary field by field into a class, aborting if the class and dictionary
1379 # have different data members (except for any typemap member in the class;
1380 # that one is strictly for use by the MyWidget supperclass).
1382 # Once the object tree is set up, require user to choose novice or expert
1383 # mode and instantiate an edit object for the configuration. Class methods
1384 # will take it all from there.
1386 # Options (not documented because they're for fetchmailconf debuggers only):
1387 # -d: Read the configuration and dump it to stdout before editing. Dump
1388 # the edited result to stdout as well.
1389 # -f: specify the run control file to read.
1391 if __name__ == '__main__':
1394 R0lGODdhPAAoAPcAAP///wgICBAQEISEhIyMjJSUlKWlpa2trbW1tcbGxs7Ozufn5+/v7//39yEY
1395 GNa9tUoxKZyEe1o5KTEQAN7OxpyMhIRjUvfn3pxSKYQ5EO/Wxv/WvWtSQrVzSmtCKWspAMatnP/e
1396 xu+1jIxSKaV7Wt6ca5xSGK2EY8aUa72MY86UY617UsaMWrV7SpRjOaVrOZRaKYxSIXNCGGs5EIRC
1397 CJR7Y/+UMdbOxnNrY97Ove/Wvd7GrZyEa961jL2Ua9alc86ca7WEUntSKcaMSqVjGNZ7GGM5CNa1
1398 jPfOnN6tc3taMffeve/WtWtaQv/OjGtSMYRzWv/erda1hM6te7WUY62MWs61jP/vzv/ntda9jL2l
1399 czEhAO/n1oyEc//elDEpGEo5EOfexpyUe+/epefevffvxnNrQpyUStbWzsbGvZyclN7ezmNjWv//
1400 5/f33qWllNbWve/vzv//1ufnve/vvf//xvf3vefnrf//taWlc0pKMf//pbW1Y///jKWlWq2tWsbG
1401 Y///c97eUvf3Ut7nc+/3a87We8bOjOfv1u/37/f//621tb3Gxtbn52Nra87n53uUlJTv/6W9xuf3
1402 /8bW3iExOXu11tbv/5TW/4TO/63e/zmt/1KUxlK1/2u9/wCM/73GzrXG1gBKjACE/87e72NzhCkx
1403 OaXO92OMtUql/xCE/wApUtbe57W9xnN7hHut52Ot/xBSnABKnABavQB7/2ul7zF71gBr77XO73Oc
1404 1lqc9yFSlBApSimE/wAYOQApY0J7zlKM5wAxhABS1gBj/6W95wAhWgA5nAAYSgBS7wBS/wBK9wAp
1405 jABC5wBK/wApnABC/wApxgAhtYSMtQAQYwAp/3OE74SMxgAYxlpjvWNr70pS/wgQ3sbGzs7O1qWl
1406 3qWl70pKe0JC/yEhlCkp/wgI/wAAEAAAIQAAKQAAOQAASgAAUgAAYwAAawAAlAAAnAAApQAArQAA
1407 zgAA1gAA5wAA9wAA/0pC/xgQ52Na9ykhe4R7zikhYxgQSjEpQgAAACwAAAAAPAAoAAAI/wABCBxI
1408 sKDBgwgTKiRIYKHDhxARIvgXsaLFhGgEUBSYoKPHjyBDihxJkuS/kwNLqlzJcuTJjQBaypxpEiVH
1409 mjhxvkyZs2fLnTd9ehxAtKjRo0ZrwhTasUsENhYHKOUpk1E3j11mxCBiQVLEBlJd2owp9iVRjwUs
1410 zMCQ5IcLD4saPVxjIKxIoGTvvqSoyFEFGTBeqEhyxAoSFR/USGKVcEGBAwDshsSr1OYTEyhQpJiS
1411 ZcoUKWOQtJDRJFSaggzUGBgoGSTlsjahlPCRIkWVKT16THHRIoqIISBIEUgAYIGBhgRbf3ytFygU
1412 FZp9UDmxQkkMCRwyZKDBQy4aApABhP8XqNwj88l7BVpQYZtF5iArWgwAgGZBq24HU7OeGhQ90PVA
1413 aKZZCiiUMJ9ArSTEwGqR8ZeXfzbV0MIIMQTBwoUdxDDfAm8sZFyDZVEF4UYSKBEBD0+k6IEFPMxH
1414 3FzldXSea+kBgANJSOWIlIMhXZXAXv+c1WM3PuJEpH8iuhbAkv+MdENPRHaTRkdF/jiWSKCAwlKW
1415 VbbkY5Q0LgUSKExgoYBKCjCxARpdltQNKHaUoYAddnR53lVRnJLKBWh4RIEGCZx5FSOv1OLNDUVe
1416 deZHaWiZAB35fIOGNtbEUeV5oGAByzPOrBPFGt3kwEgxITACSg5oLGGLMg60oQAjaNz/oAAcN4Ai
1417 a0c3kHFDK3jYsw4g9sRzBgPLXdkRrBrQ8gsWQUxCCRZX9IJNBQ1s8IgCdeBCzBYN6IBIN2TUsQYd
1418 dXhDBxdzlAHOHHKEcocZdWwDjx8MTCmjsR2FMAstw1RyiSzHqPLALaOwk8QmzCzDCSi0xJKMMk4E
1419 Yw8389iTDT32GAKOPf7YY0Aa9tATyD3w/EGsefgmgEYUtPiChLKWQDMBJtEUgYkzH2RiTgGfTMCI
1420 Mlu0Yc85hNiDziH2tMqOGL72QY47gshLb7Fi4roELcjoQIsxWpDwQyfS2OCJMkLI4YUmyhgxSTVg
1421 CP2FHPZ80UDcieBjStNPD5LPOyZT/y0iHGiMwswexDSzRiRq6KIMJBc4M8skwKAyChia2KPH3P24
1422 YU8/lFhOTj152OPOHuXMU4g48vCRiN/9rZGLMdS4csUu1JzDgxuipOMDHMKsAwEnq/ByzTrrZMNO
1423 OtO0k84+7KjzBjzplMJOOOOoo8846/ATxqJWinkkGUyEkMAaIezABQM3bMAEK1xEsUMDGjARRxhY
1424 xEGGHfPjEcccca6BRxhyuEMY7FCHMNDhf9140r2qRiVvdENQ3liUArzREW/0qRsRVIAGFfBADnLw
1425 gUSiYASJpMEHhilJTEnhAlGoQqYAZQ1AiqEMZ0jDGtqQImhwwA13yMMevoQAGvGhEAWHGMOAAAA7
1427 # Note on making icons: the above was generated by the following procedure:
1430 # data = open("fetchmail.gif", "rb").read()
1431 # print "fetchmail_gif =\\"
1432 # print repr(base64.encodestring(data))
1436 (options, arguments) = getopt.getopt(sys.argv[1:], "df:")
1437 dump = rcfile = None;
1438 for (switch, val) in options:
1439 if (switch == '-d'):
1441 elif (switch == '-f'):
1444 # Get client host's FQDN
1445 hostname = socket.gethostbyaddr(socket.gethostname())[0]
1448 ConfigurationDefaults = Configuration()
1449 ServerDefaults = Server()
1450 UserDefaults = User()
1452 # Read the existing configuration
1453 tmpfile = "/tmp/fetchmailconf." + `os.getpid()`
1455 cmd = "fetchmail -f " + rcfile + " --configdump >" + tmpfile
1457 cmd = "fetchmail --configdump >" + tmpfile
1462 print "`" + cmd + "' run failure, status " + `s`
1465 print "Unknown error while running fetchmail --configdump"
1472 print "Can't read configuration output of fetchmail --configdump."
1478 # The tricky part -- initializing objects from the configuration global
1479 # `Configuration' is the top level of the object tree we're going to mung.
1480 # The dictmembers list is used to track the set of fields the dictionary
1481 # contains; in particular, we can use it to tell whether things like the
1482 # monitor, interface, and netsec fields are present.
1484 Fetchmailrc = Configuration()
1485 copy_instance(Fetchmailrc, fetchmailrc)
1486 Fetchmailrc.servers = [];
1487 for server in fetchmailrc['servers']:
1489 copy_instance(Newsite, server)
1490 Fetchmailrc.servers.append(Newsite)
1492 for user in server['users']:
1494 copy_instance(Newuser, user)
1495 Newsite.users.append(Newuser)
1497 # We may want to display the configuration and quit
1499 print "This is a dump of the configuration we read:\n"+`Fetchmailrc`
1501 # The theory here is that -f alone sets the rcfile location,
1502 # but -d and -f together mean the new configuration should go to stdout.
1503 if not rcfile and not dump:
1504 rcfile = os.environ["HOME"] + "/.fetchmailrc"
1506 # OK, now run the configuration edit
1507 root = MainWindow(rcfile)
1510 # The following sets edit modes for GNU EMACS