aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman/MTA
diff options
context:
space:
mode:
author <>2003-01-02 05:25:50 +0000
committer <>2003-01-02 05:25:50 +0000
commitb132a73f15e432eaf43310fce9196ca0c0651465 (patch)
treec15f816ba7c4de99fef510e3bd75af0890d47441 /Mailman/MTA
downloadmailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.gz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.xz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.zip
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.
Diffstat (limited to 'Mailman/MTA')
-rw-r--r--Mailman/MTA/.cvsignore1
-rw-r--r--Mailman/MTA/Makefile.in69
-rw-r--r--Mailman/MTA/Manual.py135
-rw-r--r--Mailman/MTA/Postfix.py344
-rw-r--r--Mailman/MTA/Utils.py79
-rw-r--r--Mailman/MTA/__init__.py15
6 files changed, 643 insertions, 0 deletions
diff --git a/Mailman/MTA/.cvsignore b/Mailman/MTA/.cvsignore
new file mode 100644
index 00000000..f3c7a7c5
--- /dev/null
+++ b/Mailman/MTA/.cvsignore
@@ -0,0 +1 @@
+Makefile
diff --git a/Mailman/MTA/Makefile.in b/Mailman/MTA/Makefile.in
new file mode 100644
index 00000000..42a6fcc5
--- /dev/null
+++ b/Mailman/MTA/Makefile.in
@@ -0,0 +1,69 @@
+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# NOTE: Makefile.in is converted into Makefile by the configure script
+# in the parent directory. Once configure has run, you can recreate
+# the Makefile by running just config.status.
+
+# Variables set by configure
+
+VPATH= @srcdir@
+srcdir= @srcdir@
+bindir= @bindir@
+prefix= @prefix@
+exec_prefix= @exec_prefix@
+
+CC= @CC@
+CHMOD= @CHMOD@
+INSTALL= @INSTALL@
+
+DEFS= @DEFS@
+
+# Customizable but not set by configure
+
+OPT= @OPT@
+CFLAGS= $(OPT) $(DEFS)
+PACKAGEDIR= $(prefix)/Mailman/MTA
+SHELL= /bin/sh
+
+MODULES= *.py
+
+# Modes for directories and executables created by the install
+# process. Default to group-writable directories but
+# user-only-writable for executables.
+DIRMODE= 775
+EXEMODE= 755
+FILEMODE= 644
+INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
+
+
+# Rules
+
+all:
+
+install:
+ for f in $(MODULES); \
+ do \
+ $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(PACKAGEDIR); \
+ done
+
+finish:
+
+clean:
+
+distclean:
+ -rm *.pyc
+ -rm Makefile
diff --git a/Mailman/MTA/Manual.py b/Mailman/MTA/Manual.py
new file mode 100644
index 00000000..dd9127cc
--- /dev/null
+++ b/Mailman/MTA/Manual.py
@@ -0,0 +1,135 @@
+# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Creation/deletion hooks for manual /etc/aliases files."""
+
+import sys
+from cStringIO import StringIO
+
+from Mailman import mm_cfg
+from Mailman import Message
+from Mailman import Utils
+from Mailman.Queue.sbcache import get_switchboard
+from Mailman.i18n import _
+from Mailman.MTA.Utils import makealiases
+
+
+
+# no-ops for interface compliance
+def makelock():
+ class Dummy:
+ def lock(self):
+ pass
+ def unlock(self, unconditionally=0):
+ pass
+ return Dummy()
+
+
+def clear():
+ pass
+
+
+
+# nolock argument is ignored, but exists for interface compliance
+def create(mlist, cgi=0, nolock=0):
+ if mlist is None:
+ return
+ listname = mlist.internal_name()
+ fieldsz = len(listname) + len('-unsubscribe')
+ if cgi:
+ # If a list is being created via the CGI, the best we can do is send
+ # an email message to mailman-owner requesting that the proper aliases
+ # be installed.
+ sfp = StringIO()
+ print >> sfp, _("""\
+The mailing list `%(listname)s' has been created via the through-the-web
+interface. In order to complete the activation of this mailing list, the
+proper /etc/aliases (or equivalent) file must be updated. The program
+`newaliases' may also have to be run.
+
+Here are the entries for the /etc/aliases file:
+""")
+ outfp = sfp
+ else:
+ print _("""
+To finish creating your mailing list, you must edit your /etc/aliases (or
+equivalent) file by adding the following lines, and possibly running the
+`newaliases' program:
+
+## %(listname)s mailing list""")
+ outfp = sys.stdout
+ # Common path
+ for k, v in makealiases(listname):
+ print >> outfp, k + ':', ((fieldsz - len(k)) * ' '), v
+ # If we're using the command line interface, we're done. For ttw, we need
+ # to actually send the message to mailman-owner now.
+ if not cgi:
+ print >> outfp
+ return
+ # Send the message to the site -owner so someone can do something about
+ # this request.
+ siteowner = Utils.get_site_email(extra='owner')
+ # Should this be sent in the site list's preferred language?
+ msg = Message.UserNotification(
+ siteowner, siteowner,
+ _('Mailing list creation request for list %(listname)s'),
+ sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
+ outq.enqueue(msg, recips=[siteowner])
+
+
+
+def remove(mlist, cgi=0):
+ listname = mlist.internal_name()
+ fieldsz = len(listname) + len('-unsubscribe')
+ if cgi:
+ # If a list is being removed via the CGI, the best we can do is send
+ # an email message to mailman-owner requesting that the appropriate
+ # aliases be deleted.
+ sfp = StringIO()
+ print >> sfp, _("""\
+The mailing list `%(listname)s' has been removed via the through-the-web
+interface. In order to complete the de-activation of this mailing list, the
+appropriate /etc/aliases (or equivalent) file must be updated. The program
+`newaliases' may also have to be run.
+
+Here are the entries in the /etc/aliases file that should be removed:
+""")
+ outfp = sfp
+ else:
+ print _("""
+To finish removing your mailing list, you must edit your /etc/aliases (or
+equivalent) file by removing the following lines, and possibly running the
+`newaliases' program:
+
+## %(listname)s mailing list""")
+ outfp = sys.stdout
+ # Common path
+ for k, v in makealiases(listname):
+ print >> outfp, k + ':', ((fieldsz - len(k)) * ' '), v
+ # If we're using the command line interface, we're done. For ttw, we need
+ # to actually send the message to mailman-owner now.
+ if not cgi:
+ print >> outfp
+ return
+ siteowner = Utils.get_site_email(extra='owner')
+ # Should this be sent in the site list's preferred language?
+ msg = Message.UserNotification(
+ siteowner, siteowner,
+ _('Mailing list removal request for list %(listname)s'),
+ sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
+ outq.enqueue(msg, recips=[siteowner])
diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py
new file mode 100644
index 00000000..24c1c7e5
--- /dev/null
+++ b/Mailman/MTA/Postfix.py
@@ -0,0 +1,344 @@
+# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Creation/deletion hooks for the Postfix MTA.
+"""
+
+import os
+import time
+import errno
+import pwd
+import grp
+from stat import *
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import LockFile
+from Mailman.i18n import _
+from Mailman.MTA.Utils import makealiases
+from Mailman.Logging.Syslog import syslog
+
+LOCKFILE = os.path.join(mm_cfg.LOCK_DIR, 'creator')
+ALIASFILE = os.path.join(mm_cfg.DATA_DIR, 'aliases')
+VIRTFILE = os.path.join(mm_cfg.DATA_DIR, 'virtual-mailman')
+
+
+
+def _update_maps():
+ msg = 'command failed: %s (status: %s, %s)'
+ acmd = mm_cfg.POSTFIX_ALIAS_CMD + ' ' + ALIASFILE
+ status = (os.system(acmd) >> 8) & 0xff
+ if status:
+ errstr = os.strerror(status)
+ syslog('error', msg, acmd, status, errstr)
+ raise RuntimeError, msg % (acmd, status, errstr)
+ if os.path.exists(VIRTFILE):
+ vcmd = mm_cfg.POSTFIX_MAP_CMD + ' ' + VIRTFILE
+ status = (os.system(vcmd) >> 8) & 0xff
+ if status:
+ errstr = os.strerror(status)
+ syslog('error', msg, vcmd, status, errstr)
+ raise RuntimeError, msg % (vcmd, status, errstr)
+
+
+
+def makelock():
+ return LockFile.LockFile(LOCKFILE)
+
+
+def _zapfile(filename):
+ # Truncate the file w/o messing with the file permissions, but only if it
+ # already exists.
+ if os.path.exists(filename):
+ fp = open(filename, 'w')
+ fp.close()
+
+
+def clear():
+ _zapfile(ALIASFILE)
+ _zapfile(VIRTFILE)
+
+
+
+def _addlist(mlist, fp):
+ # Set up the mailman-loop address
+ loopaddr = Utils.ParseEmail(Utils.get_site_email(extra='loop'))[0]
+ loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox')
+ # Seek to the end of the text file, but if it's empty write the standard
+ # disclaimer, and the loop catch address.
+ fp.seek(0, 2)
+ if not fp.tell():
+ print >> fp, """\
+# This file is generated by Mailman, and is kept in sync with the
+# binary hash file aliases.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE
+# unless you know what you're doing, and can keep the two files properly
+# in sync. If you screw it up, you're on your own.
+"""
+ print >> fp, '# The ultimate loop stopper address'
+ print >> fp, '%s: %s' % (loopaddr, loopmbox)
+ print >> fp
+ # Bootstrapping. bin/genaliases must be run before any lists are created,
+ # but if no lists exist yet then mlist is None. The whole point of the
+ # exercise is to get the minimal aliases.db file into existance.
+ if mlist is None:
+ return
+ listname = mlist.internal_name()
+ fieldsz = len(listname) + len('-unsubscribe')
+ # The text file entries get a little extra info
+ print >> fp, '# STANZA START:', listname
+ print >> fp, '# CREATED:', time.ctime(time.time())
+ # Now add all the standard alias entries
+ for k, v in makealiases(listname):
+ # Format the text file nicely
+ print >> fp, k + ':', ((fieldsz - len(k)) * ' ') + v
+ # Finish the text file stanza
+ print >> fp, '# STANZA END:', listname
+ print >> fp
+
+
+
+def _addvirtual(mlist, fp):
+ listname = mlist.internal_name()
+ fieldsz = len(listname) + len('-unsubscribe')
+ hostname = mlist.host_name
+ # Set up the mailman-loop address
+ loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
+ loopdest = Utils.ParseEmail(loopaddr)[0]
+ # Seek to the end of the text file, but if it's empty write the standard
+ # disclaimer, and the loop catch address.
+ fp.seek(0, 2)
+ if not fp.tell():
+ print >> fp, """\
+# This file is generated by Mailman, and is kept in sync with the binary hash
+# file virtual-mailman.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you
+# know what you're doing, and can keep the two files properly in sync. If you
+# screw it up, you're on your own.
+#
+# Note that you should already have this virtual domain set up properly in
+# your Postfix installation. See README.POSTFIX for details.
+
+# LOOP ADDRESSES START
+%s\t%s
+# LOOP ADDRESSES END
+""" % (loopaddr, loopdest)
+ # The text file entries get a little extra info
+ print >> fp, '# STANZA START:', listname
+ print >> fp, '# CREATED:', time.ctime(time.time())
+ # Now add all the standard alias entries
+ for k, v in makealiases(listname):
+ fqdnaddr = '%s@%s' % (k, hostname)
+ # Format the text file nicely
+ print >> fp, fqdnaddr, ((fieldsz - len(k)) * ' '), k
+ # Finish the text file stanza
+ print >> fp, '# STANZA END:', listname
+ print >> fp
+
+
+
+# Blech.
+def _check_for_virtual_loopaddr(mlist, filename):
+ loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
+ loopdest = Utils.ParseEmail(loopaddr)[0]
+ infp = open(filename)
+ omask = os.umask(007)
+ try:
+ outfp = open(filename + '.tmp', 'w')
+ finally:
+ os.umask(omask)
+ try:
+ # Find the start of the loop address block
+ while 1:
+ line = infp.readline()
+ if not line:
+ break
+ outfp.write(line)
+ if line.startswith('# LOOP ADDRESSES START'):
+ break
+ # Now see if our domain has already been written
+ while 1:
+ line = infp.readline()
+ if not line:
+ break
+ if line.startswith('# LOOP ADDRESSES END'):
+ # It hasn't
+ print >> outfp, '%s\t%s' % (loopaddr, loopdest)
+ outfp.write(line)
+ break
+ elif line.startswith(loopaddr):
+ # We just found it
+ outfp.write(line)
+ break
+ else:
+ # This isn't our loop address, so spit it out and continue
+ outfp.write(line)
+ outfp.writelines(infp.readlines())
+ finally:
+ infp.close()
+ outfp.close()
+ os.rename(filename + '.tmp', filename)
+
+
+
+def _do_create(mlist, textfile, func):
+ # Crack open the plain text file
+ try:
+ fp = open(textfile, 'r+')
+ except IOError, e:
+ if e.errno <> errno.ENOENT: raise
+ omask = os.umask(007)
+ try:
+ fp = open(textfile, 'w+')
+ finally:
+ os.umask(omask)
+ try:
+ func(mlist, fp)
+ finally:
+ fp.close()
+ # Now double check the virtual plain text file
+ if func is _addvirtual:
+ _check_for_virtual_loopaddr(mlist, textfile)
+
+
+def create(mlist, cgi=0, nolock=0):
+ # Acquire the global list database lock
+ lock = None
+ if not nolock:
+ lock = makelock()
+ lock.lock()
+ # Do the aliases file, which need to be done in any case
+ try:
+ _do_create(mlist, ALIASFILE, _addlist)
+ if mlist and mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS:
+ _do_create(mlist, VIRTFILE, _addvirtual)
+ _update_maps()
+ finally:
+ if lock:
+ lock.unlock(unconditionally=1)
+
+
+
+def _do_remove(mlist, textfile, virtualp):
+ listname = mlist.internal_name()
+ # Now do our best to filter out the proper stanza from the text file.
+ # The text file better exist!
+ outfp = None
+ try:
+ infp = open(textfile)
+ except IOError, e:
+ if e.errno <> errno.ENOENT: raise
+ # Otherwise, there's no text file to filter so we're done.
+ return
+ try:
+ omask = os.umask(007)
+ try:
+ outfp = open(textfile + '.tmp', 'w')
+ finally:
+ os.umask(omask)
+ filteroutp = 0
+ start = '# STANZA START: ' + listname
+ end = '# STANZA END: ' + listname
+ while 1:
+ line = infp.readline()
+ if not line:
+ break
+ # If we're filtering out a stanza, just look for the end marker and
+ # filter out everything in between. If we're not in the middle of
+ # filtering out a stanza, we're just looking for the proper begin
+ # marker.
+ if filteroutp:
+ if line.startswith(end):
+ filteroutp = 0
+ # Discard the trailing blank line, but don't worry if
+ # we're at the end of the file.
+ infp.readline()
+ # Otherwise, ignore the line
+ else:
+ if line.startswith(start):
+ # Filter out this stanza
+ filteroutp = 1
+ else:
+ outfp.write(line)
+ # Close up shop, and rotate the files
+ finally:
+ infp.close()
+ outfp.close()
+ os.rename(textfile+'.tmp', textfile)
+
+
+def remove(mlist, cgi=0):
+ # Acquire the global list database lock
+ lock = makelock()
+ lock.lock()
+ try:
+ _do_remove(mlist, ALIASFILE, 0)
+ if mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS:
+ _do_remove(mlist, VIRTFILE, 1)
+ # Regenerate the alias and map files
+ _update_maps()
+ finally:
+ lock.unlock(unconditionally=1)
+
+
+
+def checkperms(state):
+ targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
+ for file in ALIASFILE, VIRTFILE:
+ if state.VERBOSE:
+ print _('checking permissions on %(file)s')
+ stat = None
+ try:
+ stat = os.stat(file)
+ except OSError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ if stat and (stat[ST_MODE] & targetmode) <> targetmode:
+ state.ERRORS += 1
+ octmode = oct(stat[ST_MODE])
+ print _('%(file)s permissions must be 066x (got %(octmode)s)'),
+ if state.FIX:
+ print _('(fixing)')
+ os.chmod(file, stat[ST_MODE] | targetmode)
+ else:
+ print
+ # Make sure the corresponding .db files are owned by the Mailman user.
+ # We don't need to check the group ownership of the file, since
+ # check_perms checks this itself.
+ dbfile = file + '.db'
+ stat = None
+ try:
+ stat = os.stat(dbfile)
+ except OSError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ continue
+ if state.VERBOSE:
+ print _('checking ownership of %(dbfile)s')
+ user = mm_cfg.MAILMAN_USER
+ ownerok = stat[ST_UID] == pwd.getpwnam(user)[2]
+ if not ownerok:
+ try:
+ owner = pwd.getpwuid(stat[ST_UID])[0]
+ except KeyError:
+ owner = 'uid %d' % stat[ST_UID]
+ print _('%(dbfile)s owned by %(owner)s (must be owned by %(user)s')
+ state.ERRORS += 1
+ if state.FIX:
+ print _('(fixing)')
+ uid = pwd.getpwnam(user)[2]
+ gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2]
+ os.chown(dbfile, uid, gid)
+ else:
+ print
diff --git a/Mailman/MTA/Utils.py b/Mailman/MTA/Utils.py
new file mode 100644
index 00000000..f55a1ed3
--- /dev/null
+++ b/Mailman/MTA/Utils.py
@@ -0,0 +1,79 @@
+# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Utilities for list creation/deletion hooks."""
+
+import os
+import pwd
+
+from Mailman import mm_cfg
+
+
+
+def getusername():
+ username = os.environ.get('USER') or os.environ.get('LOGNAME')
+ if not username:
+ import pwd
+ username = pwd.getpwuid(os.getuid())[0]
+ if not username:
+ username = '<unknown>'
+ return username
+
+
+
+def _makealiases_mailprog(listname):
+ wrapper = os.path.join(mm_cfg.WRAPPER_DIR, 'mailman')
+ # Most of the list alias extensions are quite regular. I.e. if the
+ # message is delivered to listname-foobar, it will be filtered to a
+ # program called foobar. There are two exceptions:
+ #
+ # 1) Messages to listname (no extension) go to the post script.
+ # 2) Messages to listname-admin go to the bounces script. This is for
+ # backwards compatibility and may eventually go away (we really have no
+ # need for the -admin address anymore).
+ #
+ # Seed this with the special cases.
+ aliases = [(listname, '"|%s post %s"' % (wrapper, listname)),
+ ]
+ for ext in ('admin', 'bounces', 'confirm', 'join', 'leave', 'owner',
+ 'request', 'subscribe', 'unsubscribe'):
+ aliases.append(('%s-%s' % (listname, ext),
+ '"|%s %s %s"' % (wrapper, ext, listname)))
+ return aliases
+
+
+
+def _makealiases_maildir(listname):
+ maildir = mm_cfg.MAILDIR_DIR
+ if not maildir.endswith('/'):
+ maildir += '/'
+ # Deliver everything using maildir style. This way there's no mail
+ # program, no forking and no wrapper necessary!
+ #
+ # Note, don't use this unless your MTA leaves the envelope recipient in
+ # Delivered-To:, Envelope-To:, or Apparently-To:
+ aliases = [(listname, maildir)]
+ for ext in ('admin', 'bounces', 'confirm', 'join', 'leave', 'owner',
+ 'request', 'subscribe', 'unsubscribe'):
+ aliases.append(('%s-%s' % (listname, ext), maildir))
+ return aliases
+
+
+
+if mm_cfg.USE_MAILDIR:
+ makealiases = _makealiases_maildir
+else:
+ makealiases = _makealiases_mailprog
diff --git a/Mailman/MTA/__init__.py b/Mailman/MTA/__init__.py
new file mode 100644
index 00000000..55cd5826
--- /dev/null
+++ b/Mailman/MTA/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.