diff -u mailman-2.1.9/debian/patches/series mailman-ssls-2.1.9/debian/patches/series --- mailman-2.1.9/debian/patches/series +++ mailman-ssls-2.1.9/debian/patches/series @@ -1,3 +1,4 @@ +00_mailman_ssls.dpatch -p1 00_stolen_from_HEAD.patch -p0 01_defaults.debian.patch -p0 02_HyperDatabase_mapkeys.patch -p0 diff -u mailman-2.1.9/debian/control mailman-ssls-2.1.9/debian/control --- mailman-2.1.9/debian/control +++ mailman-ssls-2.1.9/debian/control @@ -1,4 +1,4 @@ -Source: mailman +Source: mailman-ssls Section: mail Priority: optional Maintainer: Mailman for Debian @@ -9,11 +9,11 @@ XS-Vcs-Browse: http://svn.debian.org/wsvn/pkg-mailman XS-Python-Version: current -Package: mailman +Package: mailman-ssls Architecture: any Pre-Depends: debconf (>= 1.4.16) | debconf-2.0 -Depends: ${shlibs:Depends}, ${python:Depends}, logrotate, cron (>= 3.0pl1-42), exim4 | mail-transport-agent, apache2 | httpd, ucf (>= 0.28), pwgen, adduser, lsb-base (>= 3.0-6) -Conflicts: suidmanager (<< 0.50), sendmail (<< 8.12.6) +Depends: ${shlibs:Depends}, ${python:Depends}, logrotate, cron (>= 3.0pl1-42), exim4 | mail-transport-agent, apache2 | httpd, ucf (>= 0.28), pwgen, adduser, lsb-base (>= 3.0-6), python-gnupginterface +Conflicts: suidmanager (<< 0.50), sendmail (<< 8.12.6), mailman Suggests: spamassassin, lynx, python-korean-codecs, python-japanese-codecs, listadmin XB-Python-Version: ${python:Versions} Description: Powerful, web-based mailing list manager diff -u mailman-2.1.9/debian/rules mailman-ssls-2.1.9/debian/rules --- mailman-2.1.9/debian/rules +++ mailman-ssls-2.1.9/debian/rules @@ -47,7 +47,8 @@ -$(MAKE) distclean rm -rf build-stamp Makefile debian/ucffiles debian/mailman.postinst.ucf rm -f debian/mailman.postrm.ucf - dh_clean + rm -rf debian/mailman-ssls/ # hackhack + dh_clean -p$(package) chmod +x debian/{prerm,postinst} binary-indep: checkroot build @@ -61,9 +62,9 @@ dh_testdir chmod +x debian/unicodify_archives.py dh_install - dh_installdirs - dh_installdocs ACKNOWLEDGMENTS README* TODO FAQ - dh_installchangelogs NEWS + dh_installdirs -p$(package) + dh_installdocs -p$(package) ACKNOWLEDGMENTS README* TODO FAQ + dh_installchangelogs -p$(package) NEWS $(MAKE) doinstall prefix=$$(pwd)/debian/mailman/var/lib/$(package) \ var_prefix=$$(pwd)/debian/mailman/var/lib/$(package) \ icondir=$$(pwd)/debian/mailman/usr/share/images/mailman \ @@ -83,27 +84,27 @@ # link them back to /var/lib/mailman for i in debian/mailman/usr/lib/$(package)/*; do \ - dh_link usr/lib/$(package)/`basename $$i` var/lib/$(package)/`basename $$i`; \ + dh_link -p$(package) usr/lib/$(package)/`basename $$i` var/lib/$(package)/`basename $$i`; \ done # Link cgi-bin as well - dh_link usr/lib/cgi-bin/$(package) var/lib/$(package)/cgi-bin + dh_link -p$(package) usr/lib/cgi-bin/$(package) var/lib/$(package)/cgi-bin # Backwards compatibility link - dh_link usr/lib/mailman/mail/mailman usr/lib/mailman/mail/wrapper - dh_link usr/share/images/mailman usr/share/doc/mailman/images + dh_link -p$(package) usr/lib/mailman/mail/mailman usr/lib/mailman/mail/wrapper + dh_link -p$(package) usr/share/images/mailman usr/share/doc/mailman/images rmdir debian/$(package)/var/lib/mailman/icons - dh_link usr/share/images/mailman var/lib/mailman/icons + dh_link -p$(package) usr/share/images/mailman var/lib/mailman/icons # move the templates to /etc/mailman mv debian/mailman/var/lib/$(package)/templates debian/mailman/etc/mailman # link it back to /var/lib/mailman/templates - dh_link etc/mailman var/lib/$(package)/templates + dh_link -p$(package) etc/mailman var/lib/$(package)/templates # remove the log directory and link it to /var/log/mailman rmdir debian/mailman/var/lib/$(package)/logs - dh_link var/log/mailman var/lib/$(package)/logs + dh_link -p$(package) var/log/mailman var/lib/$(package)/logs # move the pending subscriptions database so it doesnt overwrite the # old one when installing @@ -112,27 +113,27 @@ mv debian/mailman/var/lib/$(package)/locks debian/mailman/var/lock/mailman # link it back to /var/lib/mailman/locks - dh_link var/lock/mailman var/lib/$(package)/locks + dh_link -p$(package) var/lock/mailman var/lib/$(package)/locks - dh_installlogrotate + dh_installlogrotate -p$(package) install -m 0644 debian/mm_cfg.py debian/mailman/usr/lib/mailman/Mailman/mm_cfg.py.dist rm debian/$(package)/usr/lib/$(package)/Mailman/mm_cfg.py - dh_link etc/$(package)/mm_cfg.py \ + dh_link -p$(package) etc/$(package)/mm_cfg.py \ usr/lib/$(package)/Mailman/mm_cfg.py cp -a admin/www debian/mailman/usr/share/doc/$(package)/html for bin in $(binaries); \ - do dh_link usr/lib/$(package)/bin/$$bin usr/sbin/$$bin; done + do dh_link -p$(package) usr/lib/$(package)/bin/$$bin usr/sbin/$$bin; done mv debian/mailman/usr/sbin/arch debian/mailman/usr/sbin/mmarch - dh_installman - dh_installinit - dh_installdebconf - dh_installexamples templates/* debian/mm_cfg.py \ + dh_installman -p$(package) + dh_installinit -p$(package) + dh_installdebconf -p$(package) --mainpackage=$(package) + dh_installexamples -p$(package) templates/* debian/mm_cfg.py \ debian/mailman/usr/lib/$(package)/Mailman/Defaults.py - dh_strip - dh_compress - dh_fixperms + dh_strip -p$(package) + dh_compress -p$(package) + dh_fixperms -p$(package) chown -R root:root debian/mailman chown -R root:list \ debian/mailman/{etc/$(package),var/lib/$(package),usr/lib/{$(package),cgi-bin/$(package)}} @@ -150,7 +151,7 @@ # postfix-to-mailman.py install -m 0755 debian/contrib/postfix-to-mailman.py debian/mailman/usr/share/mailman - dh_link etc/mailman/postfix-to-mailman.py usr/lib/mailman/bin/postfix-to-mailman.py + dh_link -p$(package) etc/mailman/postfix-to-mailman.py usr/lib/mailman/bin/postfix-to-mailman.py # apache default config install -m 0644 debian/contrib/apache.conf debian/mailman/etc/mailman @@ -161,7 +162,7 @@ find debian/mailman/usr/share/mailman -type f -printf '/etc/mailman/%P\n' > debian/ucffiles - dh_link etc/mailman/qmail-to-mailman.py usr/lib/mailman/bin/qmail-to-mailman.py + dh_link -p$(package) etc/mailman/qmail-to-mailman.py usr/lib/mailman/bin/qmail-to-mailman.py # Fix permissions @@ -182,7 +183,9 @@ chmod g+w debian/mailman/var/lock/mailman # Python Policy - dh_pysupport -a `find debian/mailman/usr/lib/mailman/Mailman/ debian/mailman/usr/lib/mailman/pythonlib/ -type d` + mkdir debian/mailman-ssls # hackhack + dh_pysupport -p$(package) -a `find debian/mailman/usr/lib/mailman/Mailman/ debian/mailman/usr/lib/mailman/pythonlib/ -type d` + rmdir debian/mailman-ssls # hackhack install -d debian/mailman/usr/share/python/runtime.d install debian/mailman.rtupdate debian/mailman/usr/share/python/runtime.d @@ -192,7 +195,7 @@ find debian/mailman/usr/share/doc/mailman -name "*.html" -exec chmod -x {} \; find debian/mailman/usr/share/doc/mailman -name "*.txt" -exec chmod -x {} \; - dh_installdeb + dh_installdeb -p$(package) --mainpackage=$(package) # echo 'if [ "$$1" = purge ]; then' >> debian/mailman.postrm.ucf # for f in `cat debian/ucffiles`; do \ @@ -215,10 +218,11 @@ # perl -pi -e '/#UCF#/ and do { open F, "debian/mailman.postinst.ucf"; local $$/ ; $$_ = };' debian/mailman/DEBIAN/postinst # perl -pi -e '/#UCF#/ and do { open F, "debian/mailman.postrm.ucf"; local $$/ ; $$_ = };' debian/mailman/DEBIAN/postrm - dh_shlibdeps - dh_gencontrol + dh_shlibdeps -p$(package) + dh_gencontrol -p$(package) dh_md5sums - dh_builddeb + #dh_builddeb + dpkg --build debian/mailman .. # Below here is fairly generic really diff -u mailman-2.1.9/debian/postinst mailman-ssls-2.1.9/debian/postinst --- mailman-2.1.9/debian/postinst +++ mailman-ssls-2.1.9/debian/postinst @@ -224,4 +224,6 @@ fi +# hackhack +cd /usr/lib/mailman/Mailman/ && ln -sf /usr/share/python-support/python-gnupginterface/GnuPGInterface.py . #DEBHELPER# diff -u mailman-2.1.9/debian/changelog mailman-ssls-2.1.9/debian/changelog --- mailman-2.1.9/debian/changelog +++ mailman-ssls-2.1.9/debian/changelog @@ -1,3 +1,14 @@ +mailman-ssls (1:2.1.9-7+ssls) unstable; urgency=low + + * Apply mailman-2.1.9-ssls_2008-01-10.patch.gz + (as debian/patches/00_mailman_ssls.dpatch) + * debian/control: add: Conflicts: mailman. + * debian/{control,rules}: Rename to mailman-ssls, but stay with mailman + for pathnames + * debian/control: Depend on python-gnupginterface + + -- Mike Gerber Fri, 11 Jan 2008 00:52:12 +0100 + mailman (1:2.1.9-7) unstable; urgency=low * Upgrade subject and author indexes of _all_ archiving volumes to only in patch2: unchanged: --- mailman-ssls-2.1.9.orig/debian/patches/00_mailman_ssls.dpatch +++ mailman-ssls-2.1.9/debian/patches/00_mailman_ssls.dpatch @@ -0,0 +1,2726 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## ../mailman-2.1.9-ssls_2008-01-10.patch.dpatch by Mike Gerber +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: The SURFnet Secure List Server: an OpenPGP and S/MIME aware Mailman + +@DPATCH@ + +diff -uNr mailman-2.1.9.virgin/bin/update mailman-2.1.9/bin/update +--- mailman-2.1.9.virgin/bin/update 2008-01-10 20:49:03.000000000 +0100 ++++ mailman-2.1.9/bin/update 2008-01-10 20:49:49.000000000 +0100 +@@ -197,6 +197,32 @@ + '%(listname)s') + return 1 + ++ # Check for new GPG options ++ def add_only_if_missing(attr, initval, l=mlist): ++ if not hasattr(l, attr): ++ print _("""Adding attribute %(attr)s to list.""") ++ setattr(l, attr, initval) ++ ++ print _("""Checking and adding PGP and S/MIME properties.""") ++ # 1.2.5-gpg ++ add_only_if_missing('gpg_postings_allowed', mm_cfg.DEFAULT_GPG_POSTINGS_ALLOWED) ++ add_only_if_missing('gpg_msg_distribution', mm_cfg.DEFAULT_GPG_MSG_DISTRIBUTION) ++ add_only_if_missing('gpg_public_key', "") ++ add_only_if_missing('gpg_secret_key', "") ++ add_only_if_missing('gpg_passphrase', "") ++ add_only_if_missing('gpgkeys', {}) ++ add_only_if_missing('gpgkeyids', {}) ++ add_only_if_missing('gpg_msg_sign', mm_cfg.DEFAULT_GPG_MSG_SIGN) ++ add_only_if_missing('gpg_post_sign', mm_cfg.DEFAULT_GPG_POST_SIGN) ++ # S/MIME stuff ++ add_only_if_missing('smime_post_encrypt', mm_cfg.DEFAULT_SMIME_POST_ENCRYPT) ++ add_only_if_missing('smime_post_sign', mm_cfg.DEFAULT_SMIME_POST_SIGN) ++ add_only_if_missing('smime_distrib_encrypt', mm_cfg.DEFAULT_SMIME_DISTRIB_ENCRYPT) ++ add_only_if_missing('smime_distrib_sign', mm_cfg.DEFAULT_SMIME_DISTRIB_SIGN) ++ ++ mlist.Save() ++ ++ + # Sanity check the invariant that every BYBOUNCE disabled member must have + # bounce information. Some earlier betas broke this. BAW: we're + # submerging below the MemberAdaptor interface, so skip this if we're not +diff -uNr mailman-2.1.9.virgin/Mailman/Cgi/options.py mailman-2.1.9/Mailman/Cgi/options.py +--- mailman-2.1.9.virgin/Mailman/Cgi/options.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/Cgi/options.py 2008-01-10 20:49:49.000000000 +0100 +@@ -32,6 +32,8 @@ + from Mailman import i18n + from Mailman.htmlformat import * + from Mailman.Logging.Syslog import syslog ++from Mailman import GPGUtils ++from Mailman import SMIMEUtils + + SLASH = '/' + SETLANGUAGE = -1 +@@ -457,6 +459,24 @@ + print doc.Format() + return + ++ if cgidata.has_key('submitgpgkey'): ++ gpgkey = cgidata.getvalue('gpgkey') ++ # See if the user wants to change their gpg keys globally ++ mlists = [mlist] ++ if cgidata.getvalue('gpgkey-globally'): ++ mlists.extend(lists_of_member(mlist, user)) ++ for gmlist in mlists: ++ set_gpgkey(gmlist, user, gpgkey) ++ ++ if cgidata.has_key('submitsmimekey'): ++ smimekey = cgidata.getvalue('smimekey') ++ # See if the user wants to change their gpg keys globally ++ mlists = [mlist] ++ if cgidata.getvalue('smimekey-globally'): ++ mlists.extend(lists_of_member(mlist, user)) ++ for gmlist in mlists: ++ set_smimekey(gmlist, user, smimekey) ++ + if cgidata.has_key('unsub'): + # Was the confirming check box turned on? + if not cgidata.getvalue('unsubconfirm'): +@@ -791,6 +811,26 @@ + replacements[''] = mlist.FormatBox( + 'fullname', value=fullname) + ++ gpgkey = mlist.getGPGKey(user) ++ if gpgkey==None: ++ gpgkey="" ++ replacements[''] = ( ++ '' % gpgkey) ++ replacements[''] = ( ++ CheckBox('gpgkey-globally', 1, checked=0).Format()) ++ replacements[''] = ( ++ mlist.FormatButton('submitgpgkey', _('Submit GPG key'))) ++ ++ smimekey = mlist.getSMIMEKey(user) ++ if not smimekey: ++ smimekey="" ++ replacements[''] = ( ++ '' % smimekey) ++ replacements[''] = ( ++ CheckBox('smimekey-globally', 1, checked=0).Format()) ++ replacements[''] = ( ++ mlist.FormatButton('submitsmimekey', _('Submit S/MIME key'))) ++ + # Create the topics radios. BAW: what if the list admin deletes a topic, + # but the user still wants to get that topic message? + usertopics = mlist.getMemberTopics(user) +@@ -955,6 +995,98 @@ + + + ++def set_gpgkey(mlist, user, key): ++ # This operation requires the list lock, so let's set up the signal ++ # handling so the list lock will get released when the user hits the ++ # browser stop button. ++ def sigterm_handler(signum, frame, mlist=mlist): ++ # Make sure the list gets unlocked... ++ mlist.Unlock() ++ # ...and ensure we exit, otherwise race conditions could cause us to ++ # enter MailList.Save() while we're in the unlocked state, and that ++ # could be bad! ++ sys.exit(0) ++ ++ # Must own the list lock! ++ mlist.Lock() ++ try: ++ # Install the emergency shutdown signal handler ++ signal.signal(signal.SIGTERM, sigterm_handler) ++ # change the user's password. The password must already have been ++ # compared to the confirmpw and otherwise been vetted for ++ # acceptability. ++ gh = GPGUtils.GPGHelper(mlist) ++ if len(key)==0: ++ key=None ++ if key!=None: ++ # adjust the keyring on the filesystem ++ keyids=gh.importKey(key) ++ if keyids: ++ syslog('gpg','Key %s for user %s imported.', ++ ",".join(keyids),user) ++ else: ++ syslog('gpg','Import of key for user %s failed',user) ++ else: ++ keyids=['nonexistent'] ++ syslog('gpg','Removing keys from user %s',user) ++ if keyids: ++ oldkeyids = mlist.getGPGKeyIDs(user) ++ # adjust the in-memory copy of the list ++ mlist.setGPGKey(user, key, keyids) ++ # Remove old keys; check if oldkeyids and keyids overlap ++ if oldkeyids: ++ for i in keyids: ++ try: ++ oldkeyids.remove(i) ++ except ValueError: ++ pass ++ if len(oldkeyids)>0: ++ syslog('gpg','Removing keys %s',",".join(oldkeyids)) ++ gh.removeKeys(oldkeyids) ++ mlist.Save() ++ finally: ++ mlist.Unlock() ++ ++def set_smimekey(mlist, user, key): ++ # Like set_gpgkey, this operation requires the list lock, so let's set up the signal ++ # handling so the list lock will get released when the user hits the ++ # browser stop button. ++ def sigterm_handler(signum, frame, mlist=mlist): ++ # Make sure the list gets unlocked... ++ mlist.Unlock() ++ # ...and ensure we exit, otherwise race conditions could cause us to ++ # enter MailList.Save() while we're in the unlocked state, and that ++ # could be bad! ++ sys.exit(0) ++ ++ # Must own the list lock! ++ mlist.Lock() ++ try: ++ # Install the emergency shutdown signal handler ++ signal.signal(signal.SIGTERM, sigterm_handler) ++ # change the user's password. The password must already have been ++ # compared to the confirmpw and otherwise been vetted for ++ # acceptability. ++ sm = SMIMEUtils.SMIMEHelper(mlist) ++ if len(key)==0: ++ key=None ++ if key!=None: ++ if sm.importKey(user, key): ++ syslog('gpg','Key %s for user %s imported', key, user) ++ else: ++ syslog('gpg','Import of key for user %s failed',user) ++ else: ++ # FIXME : should support removing a key here ++ pass ++ ++ # Unlike the GPG case, we support just one S/MIME key per ++ # subscriber. We don't have ++ # an in-memory view of the keys per list. So we're done now. ++ mlist.Save() ++ finally: ++ mlist.Unlock() ++ ++ + def global_options(mlist, user, globalopts): + # Is there anything to do? + for attr in dir(globalopts): +diff -uNr mailman-2.1.9.virgin/Mailman/Defaults.py.in mailman-2.1.9/Mailman/Defaults.py.in +--- mailman-2.1.9.virgin/Mailman/Defaults.py.in 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/Defaults.py.in 2008-01-10 20:49:49.000000000 +0100 +@@ -208,11 +208,11 @@ + PUBLIC_ARCHIVE_URL = 'http://%(hostname)s/pipermail/%(listname)s' + + # Are archives on or off by default? +-DEFAULT_ARCHIVE = On ++DEFAULT_ARCHIVE = Off + + # Are archives public or private by default? + # 0=public, 1=private +-DEFAULT_ARCHIVE_PRIVATE = 0 ++DEFAULT_ARCHIVE_PRIVATE = 1 + + # ARCHIVE_TO_MBOX + #-1 - do not do any archiving +@@ -222,7 +222,7 @@ + # use this to make both external archiving mechanism work and + # mailman's builtin html archiving. the flat mail file can be + # useful for searching, external archivers, etc. +-ARCHIVE_TO_MBOX = 2 ++ARCHIVE_TO_MBOX = 0 + + # 0 - yearly + # 1 - monthly +@@ -352,6 +352,26 @@ + + + ##### ++# GPG defaults ++##### ++ ++DEFAULT_GPG_POSTINGS_ALLOWED = No ++DEFAULT_GPG_MSG_DISTRIBUTION = No ++DEFAULT_GPG_MSG_SIGN = No ++DEFAULT_GPG_POST_SIGN = No ++ ++ ++##### ++# S/MIME defaults ++##### ++ ++DEFAULT_SMIME_POST_ENCRYPT = No ++DEFAULT_SMIME_POST_SIGN = No ++DEFAULT_SMIME_DISTRIB_ENCRYPT = No ++DEFAULT_SMIME_DISTRIB_SIGN = No ++ ++ ++##### + # Delivery defaults + ##### + +@@ -829,7 +849,7 @@ + # These format strings will be expanded w.r.t. the dictionary for the + # mailing list instance. + DEFAULT_SUBJECT_PREFIX = "[%(real_name)s] " +-# DEFAULT_SUBJECT_PREFIX = "[%(real_name)s %%d]" # for numbering ++# DEFAULT_SUBJECT_PREFIX = "[%(real_name)s %%d] " # for numbering + DEFAULT_MSG_HEADER = "" + DEFAULT_MSG_FOOTER = """_______________________________________________ + %(real_name)s mailing list +@@ -949,7 +969,7 @@ + DEFAULT_UNSUBSCRIBE_POLICY = 0 + + # Private_roster == 0: anyone can see, 1: members only, 2: admin only. +-DEFAULT_PRIVATE_ROSTER = 1 ++DEFAULT_PRIVATE_ROSTER = 2 + + # When exposing members, make them unrecognizable as email addrs, so + # web-spiders can't pick up addrs for spam purposes. +diff -uNr mailman-2.1.9.virgin/Mailman/GPGUtils.py mailman-2.1.9/Mailman/GPGUtils.py +--- mailman-2.1.9.virgin/Mailman/GPGUtils.py 1970-01-01 01:00:00.000000000 +0100 ++++ mailman-2.1.9/Mailman/GPGUtils.py 2008-01-10 20:49:49.000000000 +0100 +@@ -0,0 +1,352 @@ ++# Copyright (C) 2005 by Tilburg University, http://www.uvt.nl/. ++# Copyright (C) 2005 by Stefan Schlott ++# ++# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ ++"""This is a interface to the GnuPGInterface library. It eases the ++creation of instances of the interface and handles deadlock problems ++using threads. Furthermore, in this way it should be possible to ++replace GnuPGInterface with a different one (if ever needed).""" ++ ++ ++import re ++import os ++import tempfile ++import threading ++ ++from Mailman import Errors ++from Mailman.Logging.Syslog import syslog ++from Mailman import mm_cfg ++import GnuPGInterface ++ ++ ++class AsyncRead(threading.Thread): ++ def __init__(self,infile): ++ threading.Thread.__init__(self) ++ self.infile=infile ++ self.data=None ++ def run(self): ++ self.data = self.infile.read() ++ self.infile.close() ++ ++class AsyncWrite(threading.Thread): ++ def __init__(self,outfile,data): ++ threading.Thread.__init__(self) ++ self.outfile=outfile ++ self.data=data ++ def run(self): ++ self.outfile.write(self.data) ++ self.outfile.close() ++ ++ ++class GPGHelper: ++ def __init__(self, mlist): ++ self.mlist = mlist ++ self.gpgdir="%s/%s/gpg" % (mm_cfg.LIST_DATA_DIR,mlist.internal_name()) ++ self.pubkeyfile="%s/pubring.gpg" % self.gpgdir ++ self.seckeyfile="%s/secring.gpg" % self.gpgdir ++ self.trustdbfile="%s/trustdb.gpg" % self.gpgdir ++ ++ def getGPGObject(self): ++ gpg = GnuPGInterface.GnuPG() ++ gpg.options.armor = 1 ++ gpg.options.meta_interactive = 0 ++ gpg.options.extra_args.append('--no-secmem-warning') ++ gpg.options.homedir = self.gpgdir ++ gpg.options.quiet = 0 ++ return gpg ++ ++ def cleanListKeyring(self): ++ success = True ++ if not os.path.isdir(self.gpgdir): ++ try: ++ os.mkdir(self.gpgdir) ++ os.chmod(self.gpgdir,((7*8)+7)*8) ++ except IOError, (errno, strerror): ++ syslog('error','Could not create gpg dir: %s',strerror) ++ success = False ++ for fname in (self.pubkeyfile,self.seckeyfile,self.trustdbfile): ++ if os.path.exists(fname): ++ try: ++ os.unlink(fname) ++ except: ++ syslog('error','Unable to remove %s',fname) ++ success = False ++ return success ++ ++ ++ def checkPerms(self): ++ success = True ++ if os.path.exists(self.gpgdir): ++ try: ++ os.chmod(self.gpgdir,((7*8)+7)*8) ++ except: ++ syslog('error','Unable to set mode on %s',self.gpgdir) ++ success = False ++ for fname in (self.pubkeyfile,self.seckeyfile,self.trustdbfile): ++ if os.path.exists(fname): ++ try: ++ os.chmod(fname,((6*8)+6)*8) ++ except: ++ syslog('error','Unable to set mode on %s',fname) ++ success = False ++ return success ++ ++ ++ def importKey(self,key): ++ gpg = self.getGPGObject() ++ p = gpg.run(['--import'],create_fhs=['stdin','stdout','stderr']) ++ t_out = AsyncRead(p.handles['stdout']) ++ t_out.start() ++ t_err = AsyncRead(p.handles['stderr']) ++ t_err.start() ++ p.handles['stdin'].write(key) ++ p.handles['stdin'].close() ++ t_out.join() ++ t_err.join() ++ # Ignore date from t_out ++ result = t_err.data ++ try: ++ p.wait() ++ except IOError: ++ syslog('gpg','Error importing keys: %s' % result) ++ return None ++ self.checkPerms() ++ key_ids= [] ++ for line in result.lower().splitlines(): ++ g = re.search('key ([0-9a-f]+):',line) ++ if g!=None: ++ key_ids.append('0x%s' % g.groups()[0]) ++ return key_ids ++ ++ ++ def importAllSubscriberKeys(self): ++ gpg = self.getGPGObject() ++ p = gpg.run(['--import'],create_fhs=['stdin','stdout','stderr']) ++ t_out = AsyncRead(p.handles['stdout']) ++ t_out.start() ++ t_err = AsyncRead(p.handles['stderr']) ++ t_err.start() ++ for user in self.mlist.getMembers(): ++ key = self.mlist.getGPGKey(user) ++ if key: ++ p.handles['stdin'].write(key) ++ p.handles['stdin'].close() ++ t_out.join() ++ t_err.join() ++ # Ignore date from t_out ++ result = t_err.data ++ try: ++ p.wait() ++ except IOError: ++ syslog('gpg','Error importing keys: %s' % result) ++ return None ++ self.checkPerms() ++ key_ids= [] ++ for line in result.lower().splitlines(): ++ g = re.search('key ([0-9a-f]+):',line) ++ if g!=None: ++ key_ids.append('0x%s' % g.groups()[0]) ++ return key_ids ++ ++ ++ def removeKeys(self,keyids): ++ gpg = self.getGPGObject() ++ params = ['--batch','--yes','--delete-keys'] ++ for i in keyids: ++ params.append(i) ++ p = gpg.run(params,create_fhs=['stdin','stdout','stderr']) ++ result = p.handles['stderr'].read() ++ p.handles['stderr'].close() ++ try: ++ p.wait() ++ except IOError: ++ syslog('gpg','Error removing keys: %s' % result) ++ return False ++ self.checkPerms() ++ return True ++ ++ ++ def decryptMessage(self,msg): ++ gpg = self.getGPGObject() ++ plaintext = None ++ p = gpg.run(['--decrypt','--no-permission-warning'], ++ create_fhs=['stdin','stdout','stderr','status','passphrase']) ++ t_out = AsyncRead(p.handles['stdout']) ++ t_out.start() ++ t_err = AsyncRead(p.handles['stderr']) ++ t_err.start() ++ t_status = AsyncRead(p.handles['status']) ++ t_status.start() ++ p.handles['passphrase'].write(self.mlist.gpg_passphrase) ++ p.handles['passphrase'].close() ++ p.handles['stdin'].write(msg) ++ p.handles['stdin'].close() ++ t_out.join() ++ t_err.join() ++ t_status.join() ++ plaintext = t_out.data ++ status = t_status.data ++ result = t_err.data ++ try: ++ p.wait() ++ except IOError: ++ if (plaintext==None) or (len(plaintext)==0): ++ syslog('gpg',"Error decrypting message: %s",result) ++ return (None,None) ++ else: ++ syslog('gpg',"Return code non-zero, but plaintext received: %s",result) ++ ++ # Check signature ++ key_ids = [] ++ for line in status.splitlines(): ++ # example status output: ++ # ++ #[GNUPG:] NEED_PASSPHRASE D044CC7F450B4EE8 5F76E17A88C6EDF6 16 0 ++ #[GNUPG:] GOOD_PASSPHRASE ++ #[GNUPG:] BEGIN_DECRYPTION ++ #[GNUPG:] PLAINTEXT 62 1113571634 issue ++ #[GNUPG:] PLAINTEXT_LENGTH 1914 ++ #[GNUPG:] SIG_ID H2clD0wU6w1QYPF38D7wAYzyy9s 2005-03-14 1110797362 ++ #[GNUPG:] GOODSIG 5F76E17A88C6EDF6 Joost van Baal ++ #[GNUPG:] VALIDSIG 7177F40B051B57938A0BE2195F76E17A88C6EDF6 2005-03-14 1110797362 0 3 0 17 2 00 7177F40B051B57938A0BE2195F76E17A88C6EDF6 ++ #[GNUPG:] TRUST_ULTIMATE ++ # ++ # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id ++ g = re.search('^\[GNUPG:\] GOODSIG [0-9A-F]{8}([0-9A-F]{8}) ',line) ++ if g!=None: ++ key_ids.append('0x%s' % g.groups()[0].lower()) ++ ++ return (plaintext,key_ids) ++ ++ ++ def encryptMessage(self,msg,recipients): ++ gpg = self.getGPGObject() ++ params = ['--encrypt','--always-trust','--batch','--no-permission-warning'] ++ for i in recipients: ++ params.append('-r') ++ params.append(i) ++ p = gpg.run(params, create_fhs=['stdin','stdout','stderr']) ++ t_out = AsyncRead(p.handles['stdout']) ++ t_out.start() ++ t_err = AsyncRead(p.handles['stderr']) ++ t_err.start() ++ p.handles['stdin'].write(msg) ++ p.handles['stdin'].close() ++ t_out.join() ++ t_err.join() ++ ciphertext = t_out.data ++ result = t_err.data ++ try: ++ p.wait() ++ except IOError: ++ syslog('gpg',"Error encrypting message: %s\n%s",result,ciphertext) ++ return None ++ return ciphertext ++ ++ ++ def encryptSignMessage(self,msg,recipients): ++ gpg = self.getGPGObject() ++ params = ['--encrypt','--sign','--always-trust','--batch','--no-permission-warning'] ++ for i in recipients: ++ params.append('-r') ++ params.append(i) ++ p = gpg.run(params, create_fhs=['stdin','stdout','stderr','passphrase']) ++ t_out = AsyncRead(p.handles['stdout']) ++ t_out.start() ++ t_err = AsyncRead(p.handles['stderr']) ++ t_err.start() ++ p.handles['passphrase'].write(self.mlist.gpg_passphrase) ++ p.handles['passphrase'].close() ++ p.handles['stdin'].write(msg) ++ p.handles['stdin'].close() ++ t_out.join() ++ t_err.join() ++ ciphertext = t_out.data ++ result = t_err.data ++ try: ++ p.wait() ++ except IOError: ++ syslog('gpg',"Error encrypting message: %s\n%s",result,ciphertext) ++ return None ++ return ciphertext ++ ++ ++ def verifyMessage(self,msg,signature): ++ gpg = self.getGPGObject() ++ ++ if signature: ++ # signature is not None but a non-empty string: we are dealing with ++ # a detached signature ++ ++ # our gpg call will look something like ++ # gpg --verify sigfile - < msg ++ # we'll need a tmpfile for signature ++ ++ # mkstemp is available in python >= 2.3 ++ # FIXME check errors ++ # ++ # fd is the file descriptor returned by os.open (NOT a python ++ # file object!) (python-Bugs-922922) ++ (fd, sigfilename) = tempfile.mkstemp('.GPGUtils') ++ ++ os.write(fd, signature) ++ os.close(fd) ++ args = [sigfilename, '-'] ++ else: ++ # signature == None in case complete signature ++ # no args to gpg call, read from stdin ++ args = [] ++ ++ params = ['--verify','--always-trust','--batch','--no-permission-warning'] ++ # specify stdout too: we don't want to clutter this proces's stdout ++ p = gpg.run(params, args=args, create_fhs=['stdin', 'stdout','stderr','status']) ++ # see gnupg/DETAILS in the gnupg package for info on status fd ++ t_out = AsyncRead(p.handles['stdout']) ++ t_out.start() ++ t_err = AsyncRead(p.handles['stderr']) ++ t_err.start() ++ t_status = AsyncRead(p.handles['status']) ++ t_status.start() ++ p.handles['stdin'].write(msg) ++ p.handles['stdin'].close() ++ t_out.join() ++ t_err.join() ++ t_status.join() ++ result = t_err.data ++ status = t_status.data ++ try: ++ p.wait() ++ except IOError: ++ syslog('gpg',"Error verifying message: %s",result) ++ return [] ++ ++ # clean up tmpfile ++ if sigfilename: ++ os.remove(sigfilename) # FIXME check errors ++ ++ key_ids = [] ++ for line in status.splitlines(): ++ # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id ++ g = re.search('^\[GNUPG:\] GOODSIG [0-9A-F]{8}([0-9A-F]{8}) ',line) ++ if g!=None: ++ key_ids.append('0x%s' % g.groups()[0].lower()) ++ ++ if not key_ids: ++ syslog('gpg',"No good signature found on message: %s (%s)",status,result) ++ else: ++ syslog('gpg',"Valid signature from key(s) %s found on message",key_ids) ++ return key_ids ++ +diff -uNr mailman-2.1.9.virgin/Mailman/Gui/Privacy.py mailman-2.1.9/Mailman/Gui/Privacy.py +--- mailman-2.1.9.virgin/Mailman/Gui/Privacy.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/Gui/Privacy.py 2008-01-10 20:49:49.000000000 +0100 +@@ -18,11 +18,15 @@ + """MailList mixin class managing the privacy options.""" + + import re ++import os + + from Mailman import mm_cfg + from Mailman import Utils + from Mailman.i18n import _ + from Mailman.Gui.GUIBase import GUIBase ++from Mailman.Logging.Syslog import syslog ++from Mailman import GPGUtils ++from Mailman import SMIMEUtils + + try: + True, False +@@ -42,6 +46,8 @@ + ('sender', _('Sender filters')), + ('recipient', _('Recipient filters')), + ('spam', _('Spam filters')), ++ ('gpg', _('GPG options')), ++ ('smime', _('S/MIME options')), + ] + return None + +@@ -417,12 +423,134 @@ + bracketing it.""")), + ] + ++ gpg_rtn = [ ++ _("""This section allows you to configure the GPG policy for ++ this list"""), ++ ++ ('gpg_postings_allowed', mm_cfg.Radio, ++ (_('No'), _('Yes'), _('Force')), 0, ++ _("""Is it allowed (or even mandatory) to send to this list ++ postings which are encrypted with the GPG list key?"""), ++ ++ _("""In order to allow encrypted list communication, you can ++ enable GPG-encrypted postings to this list. Every poster may ++ send postings to this list using the GPG list key. The server ++ will decrypt these postings in order to receive the original ++ plain text.

Options are: ++

  • No: GPG is disabled (e.g. due to performance reasons)
  • ++
  • Yes: GPG is enabled, subscribers may send encrypted ++ postings. Unencrypted postings will be accepted as well.
  • ++
  • Force: Subscribers are only allowed to send encrypted mails ++ to this list. Unencrypted messages will be rejected.
++ """)), ++ ++ ('gpg_public_key', mm_cfg.Text, ++ (10, WIDTH), 0, ++ _("""Public key for mailing list"""), ++ ++ _("""Please export your list public key in ASCII-armored ++ format and paste it here. This can be done using the following ++ command:
++ gpg --homedir your_tmpdir -a --export
++ It is recomended that you publish the list public key in the ++ list info, too.""")), ++ ++ ('gpg_secret_key', mm_cfg.Text, ++ (10, WIDTH), 0, ++ _("""Secret key for mailing list"""), ++ ++ _("""Please export your list secret key in ASCII-armored ++ format and paste it here. This can be done using the following ++ command:
++ gpg --homedir your_tmpdir ++ -a --export-secret-key""")), ++ ++ ('gpg_passphrase', mm_cfg.String, ++ WIDTH, 0, ++ _("""Enter the passphrase for the secret key"""), ++ ++ _("""Please enter the passphrase for the secret key. ++ Note that the passphrase will be stored in plain in the ++ server configuration, so don't use a passphrase which you ++ use for other, important keys.""")), ++ ++ ('gpg_msg_distribution', mm_cfg.Radio, ++ (_('No'), _('Yes'), _('Force')), 0, ++ _("""Are subscribers allowed (or even forced) to upload their ++ GPG public key in order to receive all messages encrypted?"""), ++ ++ _("""In order to allow encrypted list communication, you can ++ enable GPG-encrypted distributions of postings to this list. ++ Every subscriber may upload his GPG public key (which will be ++ trusted implicitly) in order to receive encrypted mail from this ++ list. The server will encrypt all postings with this key before ++ sending them to the subscriber. ++

Options are: ++

  • No: GPG is disabled (e.g. due to performance reasons)
  • ++
  • Yes: GPG is enabled, subscribers may receive ++ encrypted postings if they upload a key. Unencrypted postings ++ will be distributed as well.
  • ++
  • Force: Subscribers have to upload a key, otherwise they ++ will receive no list messages.
++ """)), ++ ++ ('gpg_post_sign', mm_cfg.Radio, ++ (_('No'), _('Yes'), _('Force')), 0, ++ _("""Should posts be GPG signed with an acknowledged subscriber key ++ before being distributed? (Yes means: hold for approval.)"""), ++ ++ _("""In order to allow authenticated list communication, you can ++ enforce GPG-signed posts to this list.""")), ++ ++ ('gpg_msg_sign', mm_cfg.Radio, ++ (_('No'), _('Yes')), 0, ++ _("""Should the server sign encrypted messages?"""), ++ ++ _("""When sending an encrypted message, the server can optionally ++ sign the outgoing mail. ++ """)), ++ ++ ] ++ ++ smime_rtn = [ ++ _("""This section allows you to configure the S/MIME policy for ++ this list"""), ++ ++ ('smime_post_encrypt', mm_cfg.Radio, ++ (_('No'), _('Yes'), _('Force')), 0, ++ _("""Is it allowed (or even mandatory) to send to this list ++ postings which are encrypted with the S/MIME list key?"""), ++ _("""FIXME""")), ++ ++ ('smime_post_sign', mm_cfg.Radio, ++ (_('No'), _('Yes'), _('Force')), 0, ++ _("""Should posts be S/MIME signed with a signed subscriber key ++ before being distributed? (Yes means: hold for approval.)"""), ++ _("""FIXME""")), ++ ++ ('smime_distrib_encrypt', mm_cfg.Radio, ++ (_('No'), _('Yes'), _('Force')), 0, ++ _("""Should the server encrypt messages before distributing?"""), ++ _("""FIXME""")), ++ ++ ('smime_distrib_sign', mm_cfg.Radio, ++ (_('No'), _('Yes'), _('Force')), 0, ++ _("""Should the server sign messages before distributing?"""), ++ _("""FIXME""")), ++ ++ ] ++ ++ + if subcat == 'sender': + return sender_rtn + elif subcat == 'recipient': + return recip_rtn + elif subcat == 'spam': + return spam_rtn ++ elif subcat == 'gpg': ++ return gpg_rtn ++ elif subcat == 'smime': ++ return smime_rtn + else: + return subscribing_rtn + +@@ -532,3 +660,28 @@ + self._handleForm(mlist, category, subcat, cgidata, doc) + # Everything else is dealt with by the base handler + GUIBase.handleForm(self, mlist, category, subcat, cgidata, doc) ++ # GPG keys.. ++ if subcat == 'gpg': ++ syslog('gpg','New list keys uploaded') ++ gh = GPGUtils.GPGHelper(mlist) ++ gh.cleanListKeyring() ++ if mlist.gpg_secret_key!=None and len(mlist.gpg_secret_key)==0: ++ mlist.gpg_secret_key = None ++ if mlist.gpg_secret_key: ++ syslog('gpg',' Importing secret key...') ++ keyids = gh.importKey(mlist.gpg_secret_key) ++ if not keyids: ++ mlist.gpg_secret_key = None ++ if mlist.gpg_public_key!=None and len(mlist.gpg_public_key)==0: ++ mlist.gpg_public_key = None ++ if mlist.gpg_public_key: ++ syslog('gpg',' Importing public key...') ++ keyids = gh.importKey(mlist.gpg_public_key) ++ if not keyids: ++ mlist.gpg_public_key = None ++ syslog('gpg',' Importing subscriber public keys...') ++ keyids=gh.importAllSubscriberKeys() ++ elif subcat == 'smime': ++ syslog('gpg','Not yet implemented') # FIXME ++ ++ +diff -uNr mailman-2.1.9.virgin/Mailman/Handlers/Hold.py mailman-2.1.9/Mailman/Handlers/Hold.py +--- mailman-2.1.9.virgin/Mailman/Handlers/Hold.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/Handlers/Hold.py 2008-01-10 20:49:49.000000000 +0100 +@@ -61,6 +61,22 @@ + reason = _('Post by non-member to a members-only list') + rejection = _('Non-members are not allowed to post messages to this list.') + ++class NonGPGSignedPost(Errors.HoldMessage): ++ reason = _('Unsigned post to Secure list') ++ rejection = _('Only messages which are PGP signed with an approved key are allowed on this list.') ++ ++class WrongGPGSignedPost(Errors.HoldMessage): ++ reason = _('Post to Secure list signed by unapproved key' ) ++ rejection = _('Only messages which are PGP signed with an approved key are allowed on this list. Upload your PGP public key.') ++ ++class NonSMIMESignedPost(Errors.HoldMessage): ++ reason = _('Unsigned post to S/MIME Secure list') ++ rejection = _('Only messages which are S/MIME signed with a key, signed with the listkey are allowed on this list.') ++ ++class WrongSMIMESignedPost(Errors.HoldMessage): ++ reason = _('Post to S/MIME Secure list signed by unapproved key' ) ++ rejection = _('Only messages which are S/MIME signed with an approved key are allowed on this list. Get your key signed by the listkey.') ++ + class NotExplicitlyAllowed(Errors.HoldMessage): + reason = _('Posting to a restricted list by sender requires approval') + rejection = _('This list is restricted; your message was not approved.') +diff -uNr mailman-2.1.9.virgin/Mailman/Handlers/Moderate.py mailman-2.1.9/Mailman/Handlers/Moderate.py +--- mailman-2.1.9.virgin/Mailman/Handlers/Moderate.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/Handlers/Moderate.py 2008-01-10 20:49:49.000000000 +0100 +@@ -1,3 +1,5 @@ ++# Copyright (C) 2005 by Stefan Schlott ++# Copyright (C) 2005 by Tilburg University, http://www.uvt.nl/. + # Copyright (C) 2001-2003 by the Free Software Foundation, Inc. + # + # This program is free software; you can redistribute it and/or +@@ -14,14 +16,17 @@ + # along with this program; if not, write to the Free Software + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +-"""Posting moderation filter. ++"""Posting moderation filter, if appropriate takes care of decrypting using list key + """ + + import re ++from email.Parser import Parser + from email.MIMEMessage import MIMEMessage + from email.MIMEText import MIMEText + + from Mailman import mm_cfg ++from Mailman import GPGUtils ++from Mailman import SMIMEUtils + from Mailman import Utils + from Mailman import Message + from Mailman import Errors +@@ -31,6 +36,151 @@ + + + ++def enforceEncryptPolicy(mlist, msg, msgdata): ++ result = True ++ if msgdata.get('toowner') or msgdata.get('toleave') \ ++ or msgdata.get('tojoin') or msgdata.get('toconfirm'): ++ result = False ++ if msgdata.get('torequest'): ++ # This could be more sophisticated: ++ # Parse message, enforce if commands containing passwords are used ++ # These would be: password, subscribe, unsubscribe, who ++ result = False ++ return result ++ ++ ++ ++def decryptGpg(mlist, msg, msgdata): ++ ++ """Returns (encrypted (bool), signed (bool), key_ids), msg is replaced with ++ decrypted msg""" ++ ++ encrypted = False ++ signed = False ++ key_ids = () ++ plaintext = None ++ ciphertext = None ++ is_pgpmime = False ++ ++ # Check: Is inline pgp? ++ if msg.get_content_type()=='application/pgp' or msg.get_param('x-action')=='pgp-encrypted': ++ ciphertext = msg.get_payload() ++ is_pgpmime = False ++ # Check: Is pgp/mime? ++ if msg.get_content_type()=='multipart/encrypted' and msg.get_param('protocol')=='application/pgp-encrypted': ++ if msg.is_multipart(): ++ for submsg in msg.get_payload(): ++ if submsg.get_content_type()=='application/octet-stream': ++ is_pgpmime = True ++ ciphertext = submsg.get_payload() ++ else: ++ ciphertext = msg.get_payload() ++ # Some clients send text/plain messages containing PGP-encrypted data :-( ++ if not msg.is_multipart() and (ciphertext==None) and \ ++ (len(msg.get_payload())>10): ++ firstline = msg.get_payload().splitlines()[0] ++ if firstline=='-----BEGIN PGP MESSAGE-----': ++ syslog('gpg','Encrypted message detected, although MIME type is %s',msg.get_content_type()) ++ is_pgpmime = False ++ ciphertext = msg.get_payload() ++ # Ciphertext present? Decode ++ if ciphertext: ++ gh = GPGUtils.GPGHelper(mlist) ++ (plaintext,key_ids) = gh.decryptMessage(ciphertext) ++ if plaintext is None: ++ syslog('gpg','Unable to decrypt GPG data') ++ raise Errors.RejectMessage, "Unable to decrypt mail!" ++ else: ++ encrypted = True ++ ++ if key_ids: ++ signed = True ++ ++ if not encrypted: ++ return (encrypted, signed, key_ids) ++ ++ # Check decryption result ++ ++ # Check transfer type ++ parser = Parser() ++ tmpmsg = parser.parsestr(plaintext) ++ if msg.get_content_type()=='application/pgp': ++ msg.set_type("text/plain") ++ msg.del_param("x-action") ++ for i in ('Content-Type','Content-Disposition','Content-Transfer-Encoding'): ++ if tmpmsg.has_key(i): ++ if msg.has_key(i): ++ msg.replace_header(i,tmpmsg.get(i)) ++ else: ++ msg.add_header(i,tmpmsg.get(i)) ++ if tmpmsg.is_multipart(): ++ msg.set_payload(None) ++ for i in tmpmsg.get_payload(): ++ msg.attach(i) ++ else: ++ tmppayload = tmpmsg.get_payload() ++ msg.set_payload(tmppayload) ++ ++ if not is_pgpmime: ++ mailclient = '' ++ if msg.has_key('User-Agent'): ++ mailclient = msg.get('User-Agent').lower() ++ # Content-Transfer-Encoding and charset are not standardized... ++ if mailclient.startswith('mutt'): ++ msg.set_param('charset','utf-8') ++ if msg.has_key('Content-Transfer-Encoding'): ++ msg.replace_header('Content-Transfer-Encoding','utf-8') ++ else: ++ msg.add_header('Content-Transfer-Encoding','utf-8') ++ else: ++ # Just a wild guess... ++ msg.set_param('charset','iso-8859-1') ++ if msg.has_key('Content-Transfer-Encoding'): ++ msg.replace_header('Content-Transfer-Encoding','8bit') ++ else: ++ msg.add_header('Content-Transfer-Encoding','8bit') ++ ++ return (encrypted, signed, key_ids) ++ ++ ++def decryptSmime(mlist, msg, msgdata): ++ """Returns (encrypted (bool), signed (bool)), msg is replaced with ++ decrypted msg""" ++ ++ # FIXME this implementation is _very_ crude. ++ # merge some stuff with decryptGpg ++ ++ encrypted = False ++ signed = False ++ plaintext = None ++ ciphertext = None ++ ++ if msg.get_content_type()=="application/x-pkcs7-mime": ++ sm = SMIMEUtils.SMIMEHelper(mlist) ++ ciphertext = msg.as_string() ++ (plaintext, signed) = sm.decryptMessage(ciphertext) ++ else: ++ # don't touch the message if it's no S/MIME ++ return (encrypted, signed) ++ ++ parser = Parser() ++ tmpmsg = parser.parsestr(plaintext) ++ ++ msg.del_param("x-action") ++ ++ for i in ('Content-Type','Content-Disposition','Content-Transfer-Encoding'): ++ if tmpmsg.has_key(i): ++ if msg.has_key(i): ++ msg.replace_header(i,tmpmsg.get(i)) ++ else: ++ msg.add_header(i,tmpmsg.get(i)) ++ ++ tmppayload = tmpmsg.get_payload() ++ msg.set_payload(tmppayload) ++ ++ return (encrypted, signed) ++ ++ + class ModeratedMemberPost(Hold.ModeratedPost): + # BAW: I wanted to use the reason below to differentiate between this + # situation and normal ModeratedPost reasons. Greg Ward and Stonewall +@@ -47,13 +197,181 @@ + def process(mlist, msg, msgdata): + if msgdata.get('approved') or msgdata.get('fromusenet'): + return +- # First of all, is the poster a member or not? ++ ++ # Deal with encrypted messages ++ ++ encrypted = False ++ signed = False ++ key_ids = '' ++ ++ # Are we dealing with a secure list, and is the message properly signed? ++ signedByMember = False ++ ++ # no, yes, force (0,1,2) ++ if mlist.gpg_postings_allowed!=0: ++ # if msg is encrypted, we should decryp ++ (encrypted, signed, key_ids) = decryptGpg(mlist, msg, msgdata) ++ if mlist.gpg_postings_allowed==2 and not encrypted: ++ syslog('gpg','Throwing RejectMessage exception: Message has to be GPG encrypted') ++ raise Errors.RejectMessage, "Message has to be encrypted!" ++ ++ if mlist.smime_post_encrypt!=0: ++ (encrypted, signedByMember) = decryptSmime(mlist, msg, msgdata) ++ if mlist.smime_post_encrypt==2 and not encrypted: ++ syslog('gpg','Throwing RejectMessage exception: Message has to be S/MIME encrypted') ++ raise Errors.RejectMessage, "Message has to be S/MIME encrypted!" ++ ++ # legal values are: ++ # 0 = "No" ++ # 1 = "Yes" ++ # 2 = "Force" ++ if mlist.gpg_post_sign!=0 and not signed: ++ # PGP signature matters, we have not checked while decrypting ++ gh = GPGUtils.GPGHelper(mlist) ++ payload = '' ++ signature = '' ++ if msg.get_content_type()=='multipart/signed' and msg.get_param('protocol')=='application/pgp-signature' and msg.is_multipart(): ++ # handle detached signatures, these look like: ++ # ++ # Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="x0ZPnva+gsdVsg/k" ++ # Content-Disposition: inline ++ # ++ # ++ # --x0ZPnva+gsdVsg/k ++ # Content-Type: text/plain; charset=us-ascii ++ # Content-Disposition: inline ++ # ++ # hello ++ # ++ # --x0ZPnva+gsdVsg/k ++ # Content-Type: application/pgp-signature; name="signature.asc" ++ # Content-Description: Digital signature ++ # Content-Disposition: inline ++ # ++ # -----BEGIN PGP SIGNATURE----- ++ # Version: GnuPG v1.2.5 (GNU/Linux) ++ # ++ # iD8DBQFCQDTGPSnqOAwU/4wRAsoZAKDtN6Pn1dXjC/DAQhqOLHNI6VfNigCfaDPs ++ # FRJlhlGvyhkpx4soGR+CLxE= ++ # =AmS5 ++ # -----END PGP SIGNATURE----- ++ # ++ # --x0ZPnva+gsdVsg/k-- ++ # ++ # for verification, use payload INCLUDING MIME header: ++ # ++ # 'Content-Type: text/plain; charset=us-ascii ++ # Content-Disposition: inline ++ # ++ # hello ++ # ' ++ # Thanks Wessel Dankers for hint. ++ ++ for submsg in msg.get_payload(): ++ if submsg.get_content_type()=='application/pgp-signature': ++ signature = submsg.get_payload() ++ else: ++ # we assume exactly 2 parts: one payload, one signature ++ # ++ # yes, including headers ++ payload = submsg.as_string() ++ elif msg.get_content_type()=='text/plain' and not msg.is_multipart(): ++ # handle inline signature; message looks like e.g. ++ # ++ # Content-Type: text/plain; charset=iso-8859-1 ++ # Content-Disposition: inline ++ # Content-Transfer-Encoding: 8bit ++ # MIME-Version: 1.0 ++ # ++ # -----BEGIN PGP SIGNED MESSAGE----- ++ # Hash: SHA1 ++ # ++ # blah blah ++ # ++ # -----BEGIN PGP SIGNATURE----- ++ # Version: GnuPG v1.4.0 (GNU/Linux) ++ # ++ # iD8DBQFCPtWXW5ql+IAeqTIRAirPAK.... ++ # -----END PGP SIGNATURE----- ++ signature = None ++ payload = msg.get_payload() ++ ++ syslog('gpg', "gonna verify payload '%s' with signature '%s'", payload, signature) ++ key_ids = gh.verifyMessage(payload, signature) ++ ++ if mlist.smime_post_sign!=0 and not signedByMember: ++ # S/MIME signature matters, we have not checked while decrypting ++ sm = SMIMEUtils.SMIMEHelper(mlist) ++ payload = '' ++ signature = '' ++ ++ syslog('gpg', "gonna verify SMIME message") ++ signedByMember = sm.verifyMessage(msg) ++ # raise Errors.NotYetImplemented, "SMIMEUtils doesn't yet do verifyMessage" ++ ++ if mlist.gpg_post_sign!=0: ++ if not key_ids: ++ syslog('gpg','No valid signatures on message') ++ if mlist.gpg_post_sign==1: ++ # unsigned means: hold ++ syslog('gpg','gpg_post_sign is Yes, gonna Hold') ++ Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonGPGSignedPost) ++ return ++ else: ++ # mlist.gpg_post_sign==2 # 'Force' ++ do_discard(mlist, msg) ++ ++ for user in mlist.getMembers(): ++ syslog('gpg','Checking signature: listmember %s',user) ++ for key_id in key_ids: ++ syslog('gpg','Checking signature: key_id %s',key_id) ++ try: ++ ks=mlist.getGPGKeyIDs(user) ++ except: ++ ks=None ++ if ks: ++ for k in mlist.getGPGKeyIDs(user): ++ syslog('gpg','Checking signature: keyid of listmember is %s',k) ++ if k==key_id: ++ signedByMember = True ++ break ++ ++ if mlist.smime_post_sign!=0: ++ if not signedByMember: ++ syslog('gpg','No valid S/MIME signatures on message') ++ if mlist.smime_post_sign==1: ++ # unsigned means: hold ++ syslog('gpg','smime_post_sign is Yes, gonna Hold') ++ Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonSMIMESignedPost) ++ return ++ else: ++ syslog('gpg','smime_post_sign is Force, gonna Discard') ++ do_discard(mlist, msg) ++ ++ # done dealing with most of gpg stuff ++ ++ # Is the poster a member or not? + for sender in msg.get_senders(): + if mlist.isMember(sender): + break + else: + sender = None + if sender: ++ # If posts need to be PGP signed, process signature. ++ if mlist.gpg_post_sign!=0: ++ if mlist.gpg_post_sign==1: ++ if signedByMember==True: ++ syslog('gpg','Message properly signed: distribute') ++ return ++ else: ++ Hold.hold_for_approval(mlist, msg, msgdata, Hold.WrongGPGSignedPost) ++ elif mlist.gpg_post_sign==2: ++ if signedByMember==True: ++ syslog('gpg','Message properly signed: distribute') ++ return ++ else: ++ do_discard(mlist, msg) ++ + # If the member's moderation flag is on, then perform the moderation + # action. + if mlist.getMemberOption(sender, mm_cfg.Moderate): +diff -uNr mailman-2.1.9.virgin/Mailman/Handlers/SMTPDirect.py mailman-2.1.9/Mailman/Handlers/SMTPDirect.py +--- mailman-2.1.9.virgin/Mailman/Handlers/SMTPDirect.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/Handlers/SMTPDirect.py 2008-01-10 20:49:49.000000000 +0100 +@@ -1,5 +1,10 @@ ++# This file is a moderately patched version of SMTPDirect.py which has: ++# + # Copyright (C) 1998-2005 by the Free Software Foundation, Inc. + # ++# GPG modifications: ++# Copyright (C) 2005 by Stefan Schlott ++# + # 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 +@@ -15,7 +20,7 @@ + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + # USA. + +-"""Local SMTP direct drop-off. ++"""Local SMTP direct drop-off - after GPG encryption. + + This module delivers messages via SMTP to a locally specified daemon. This + should be compatible with any modern SMTP server. It is expected that the MTA +@@ -38,10 +43,14 @@ + from Mailman.Handlers import Decorate + from Mailman.Logging.Syslog import syslog + from Mailman.SafeDict import MsgSafeDict ++from Mailman import GPGUtils ++from Mailman import SMIMEUtils + + import email + from email.Utils import formataddr + from email.Header import Header ++from email.Parser import HeaderParser ++from email.Message import Message + from email.Charset import Charset + + DOT = '.' +@@ -95,6 +104,7 @@ + + + def process(mlist, msg, msgdata): ++ syslog('gpg','GPG SMTP module called') + recips = msgdata.get('recips') + if not recips: + # Nobody to deliver to! +@@ -106,22 +116,10 @@ + envsender = mlist.GetBouncesEmail() + else: + envsender = Utils.get_site_email(extra='bounces') +- # Time to split up the recipient list. If we're personalizing or VERPing +- # then each chunk will have exactly one recipient. We'll then hand craft +- # an envelope sender and stitch a message together in memory for each one +- # separately. If we're not VERPing, then we'll chunkify based on +- # SMTP_MAX_RCPTS. Note that most MTAs have a limit on the number of +- # recipients they'll swallow in a single transaction. +- deliveryfunc = None +- if (not msgdata.has_key('personalize') or msgdata['personalize']) and ( +- msgdata.get('verp') or mlist.personalize): +- chunks = [[recip] for recip in recips] +- msgdata['personalize'] = 1 +- deliveryfunc = verpdeliver +- elif mm_cfg.SMTP_MAX_RCPTS <= 0: +- chunks = [recips] +- else: +- chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS) ++ # Encryption has to be done on per-mail basis. Chunking is not possible. ++ chunks = [[recip] for recip in recips] ++ msgdata['personalize'] = 1 ++ deliveryfunc = verpdeliver + # See if this is an unshunted message for which some were undelivered + if msgdata.has_key('undelivered'): + chunks = msgdata['undelivered'] +@@ -276,6 +274,13 @@ + + + ++def enforceEncryptPolicy(mlist, msg, msgdata): ++ if msgdata.get('tolist'): ++ return True ++ return False ++ ++ ++ + def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): + for recip in msgdata['recips']: + # We now need to stitch together the message with its header and +@@ -340,6 +345,144 @@ + del msgcopy['x-mailman-copy'] + if msgdata.get('add-dup-header', {}).has_key(recip): + msgcopy['X-Mailman-Copy'] = 'yes' ++ # GPG encryption ++ if mlist.gpg_msg_distribution!=0 and mlist.gpg_msg_distribution!='No': ++ # Encryption is not forbidden in config ++ try: ++ keyids=mlist.getGPGKeyIDs(recip) ++ except: ++ keyids=None ++ if enforceEncryptPolicy(mlist,msg,msgdata) and keyids==None \ ++ and (mlist.gpg_msg_distribution==2 \ ++ or mlist.gpg_msg_distribution=='Force'): ++ syslog('gpg','Encryption forced, but no keys found for %s: '\ ++ 'Discarding message',recip) ++ failures[recip]=(550,'Encryption forced, but no keys found') ++ return ++ gh = GPGUtils.GPGHelper(mlist) ++ # Extract / generate plaintext ++ gpg_use_inlineformat = False # TODO: Create config setting ++ if not msgcopy.is_multipart() and gpg_use_inlineformat: ++ plaintext=msgcopy.get_payload() ++ else: ++ if not msgcopy.is_multipart(): ++ plaintext = 'Content-Type: %s\n' \ ++ 'Content-Disposition: inline\n' \ ++ % msgcopy.get('Content-Type') ++ if not msgcopy.get('Content-Transfer-Encoding') is None: ++ plaintext += 'Content-Transfer-Encoding: %s\n' \ ++ % msgcopy.get('Content-Transfer-Encoding') ++ plaintext += '\n%s' % msgcopy.get_payload() ++ else: ++ hp = HeaderParser() ++ tmp = msgcopy.as_string() ++ tmpmsg = hp.parsestr(tmp) ++ plaintext = 'Content-Type: %s\n' \ ++ 'Content-Disposition: inline\n\n%s' \ ++ % (msgcopy.get('Content-Type'),tmpmsg.get_payload()) ++ # Do encryption, report errors ++ ciphertext = None ++ if not keyids is None: ++ if mlist.gpg_msg_sign == 0 or mlist.gpg_msg_sign=='No': ++ ciphertext = gh.encryptMessage(plaintext,keyids) ++ else: ++ ciphertext = gh.encryptSignMessage(plaintext,keyids) ++ if ciphertext==None: ++ if mlist.gpg_msg_sign=='2' or mlist.gpg_msg_sign=='Force': ++ syslog('gpg',"Can't encrypt message to %s: " \ ++ "Discarding message",keyids) ++ failures[recip]=(550,'Unable to encrypt message') ++ return ++ else: ++ syslog('gpg',"Can't encrypt message to %s, " \ ++ "sending unencrypted",keyids) ++ # Compile encrypted message ++ if not ciphertext is None: ++ if msgcopy.has_key('Content-Transfer-Encoding'): ++ msgcopy.replace_header('Content-Transfer-Encoding','7bit') ++ else: ++ msgcopy.add_header('Content-Transfer-Encoding','7bit') ++ if not msgcopy.is_multipart() and gpg_use_inlineformat: ++ msgcopy.set_payload(ciphertext) ++ msgcopy.set_param('x-action','pgp-encrypted') ++ else: ++ msgcopy.replace_header('Content-Type','multipart/encrypted') ++ msgcopy.set_param('protocol','application/pgp-encrypted') ++ msgcopy.set_payload(None) ++ submsg = Message() ++ submsg.add_header('Content-Type','application/pgp-encrypted') ++ submsg.set_payload('Version: 1\n') ++ msgcopy.attach(submsg) ++ submsg = Message() ++ submsg.add_header('Content-Type','application/octet-stream') ++ submsg.set_payload(ciphertext) ++ msgcopy.attach(submsg) ++ syslog('gpg','Sending encrypted message to %s',recip) ++ else: ++ syslog('gpg','Sending unencrypted message to %s',recip) ++ ++ # S/MIME encryption ++ if mlist.smime_distrib_encrypt != 0: ++ # FIXME: this is as crude as can be ++ sm = SMIMEUtils.SMIMEHelper(mlist) ++ recipfile = sm.getSMIMEMemberCertFile(recip) ++ ++ if not recipfile: ++ if mlist.smime_distrib_sign == 2: ++ failures[recip]=(550,'No S/MIME key found') ++ return ++ else: ++ syslog('gpg', "Can't S/MIME encrypt message to %s, " \ ++ "sending unencrypted",recip) ++ else: ++ plaintext=msgcopy.get_payload() ++ if not msgcopy.is_multipart(): ++ plaintext = msgcopy.get_payload() ++ syslog('gpg', "About to S/MIME encrypt plaintext from singlepart %s", plaintext) ++ else: ++ # message contains e.g. signature? ++ # FIXME we fetch only the first attachment. We search for ++ # attachments only 2 levels deep. That's suboptimal... ++ # perhaps the PGP way (invoking ++ # hp = HeaderParser() ++ # ) is better. ++ submsgs = msgcopy.get_payload() ++ submsg = submsgs[0] ++ if not submsg.is_multipart(): ++ plaintext = submsg.get_payload() ++ else: ++ subsubmsgs = submsg.get_payload() ++ subsubmsg = subsubmsgs[0] ++ plaintext = subsubmsg.get_payload() ++ ++ syslog('gpg', "About to S/MIME encrypt plaintext from multipart %s", plaintext) ++ ++ if mlist.smime_distrib_sign == 0: ++ ciphertext = sm.encryptMessage(plaintext,recipfile) ++ else: ++ ciphertext = sm.encryptSignMessage(plaintext,recipfile) ++ ++ # deal with both header and body-part of ciphertext ++ (header, body) = ciphertext.split("\n\n", 1) ++ for l in header.split("\n"): ++ (k, v) = l.split(": ", 1) ++ ++ # behave sane with borken openssl like 0.9.7e (e.g. Debian's 0.9.7e-3sarge1) ++ # openssl 0.9.8a-4a0.sarge.1 is known to work OK. ++ # A borken openssl (and therefore sm.encryptMessage) returns ++ # Content-Type: application/x-pkcs7-mime; name="smime.p7m" ++ # while we need a ++ # Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m" ++ if v == 'application/x-pkcs7-mime; name="smime.p7m"': ++ v = 'application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"' ++ ++ try: ++ msgcopy.replace_header(k, v) ++ except KeyError: ++ msgcopy.add_header(k, v) ++ ++ msgcopy.set_payload(body) ++ + # For the final delivery stage, we can just bulk deliver to a party of + # one. ;) + bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) +diff -uNr mailman-2.1.9.virgin/Mailman/MailList.py mailman-2.1.9/Mailman/MailList.py +--- mailman-2.1.9.virgin/Mailman/MailList.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/MailList.py 2008-01-10 20:49:49.000000000 +0100 +@@ -310,6 +310,8 @@ + self.language = {} + self.usernames = {} + self.passwords = {} ++ self.gpgkeys = {} ++ self.gpgkeyids = {} + self.new_member_options = mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS + + # This stuff is configurable +@@ -344,6 +346,20 @@ + mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS + self.header_filter_rules = [] + self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST ++ ++ self.gpg_postings_allowed = mm_cfg.DEFAULT_GPG_POSTINGS_ALLOWED ++ self.gpg_msg_distribution = mm_cfg.DEFAULT_GPG_MSG_DISTRIBUTION ++ self.gpg_msg_sign = mm_cfg.DEFAULT_GPG_MSG_SIGN ++ self.gpg_post_sign = mm_cfg.DEFAULT_GPG_POST_SIGN ++ self.gpg_public_key = '' ++ self.gpg_secret_key = '' ++ self.gpg_passphrase = '' ++ ++ self.smime_post_encrypt = mm_cfg.DEFAULT_SMIME_POST_ENCRYPT ++ self.smime_post_sign = mm_cfg.DEFAULT_SMIME_POST_SIGN ++ self.smime_distrib_encrypt = mm_cfg.DEFAULT_SMIME_DISTRIB_ENCRYPT ++ self.smime_distrib_sign = mm_cfg.DEFAULT_SMIME_DISTRIB_SIGN ++ + internalname = self.internal_name() + self.real_name = internalname[0].upper() + internalname[1:] + self.description = '' +diff -uNr mailman-2.1.9.virgin/Mailman/MemberAdaptor.py mailman-2.1.9/Mailman/MemberAdaptor.py +--- mailman-2.1.9.virgin/Mailman/MemberAdaptor.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/MemberAdaptor.py 2008-01-10 20:49:49.000000000 +0100 +@@ -219,6 +219,25 @@ + """ + raise NotImplementedError + ++ def getGPGKey(self, member): ++ """Return the member's GPG key. ++ ++ The key will be ASCII-armored (as it was uploaded). ++ ++ If no key was uploaded, None is returned. ++ If a member is not a member of the list, raise NotAMemberError. ++ """ ++ raise NotImplementedError ++ ++ def getGPGKeyIDs(self, member): ++ """Return the member's GPG key ID(s). ++ ++ The return value will contain an array of Strings. ++ If no key was uploaded, None is returned. ++ If a member is not a member of the list, raise NotAMemberError. ++ """ ++ raise NotImplementedError ++ + + # + # The writeable interface +@@ -347,3 +366,14 @@ + and returned by getBounceInfo() without modification. + """ + raise NotImplementedError ++ ++ def setGPGKey(self, member, key, keyids): ++ """Set the member's GPG key. ++ ++ The key should be ASCII-armored (as it was uploaded). ++ ++ To erase a key, set key to None. ++ If a member is not a member of the list, raise NotAMemberError. ++ """ ++ raise NotImplementedError ++ +diff -uNr mailman-2.1.9.virgin/Mailman/OldStyleMemberships.py mailman-2.1.9/Mailman/OldStyleMemberships.py +--- mailman-2.1.9.virgin/Mailman/OldStyleMemberships.py 2008-01-10 20:49:04.000000000 +0100 ++++ mailman-2.1.9/Mailman/OldStyleMemberships.py 2008-01-10 20:49:49.000000000 +0100 +@@ -30,6 +30,7 @@ + from Mailman import Utils + from Mailman import Errors + from Mailman import MemberAdaptor ++from Mailman import SMIMEUtils + + ISREGULAR = 1 + ISDIGEST = 2 +@@ -165,6 +166,24 @@ + self.__assertIsMember(member) + return self.__mlist.bounce_info.get(member.lower()) + ++ def getGPGKey(self, member): ++ self.__assertIsMember(member) ++ return self.__mlist.gpgkeys.get(member.lower()) ++ ++ def getGPGKeyIDs(self, member): ++ self.__assertIsMember(member) ++ return self.__mlist.gpgkeyids.get(member.lower()) ++ ++ def getSMIMEKey(self, member): ++ self.__assertIsMember(member) ++ sm = SMIMEUtils.SMIMEHelper(self.__mlist) ++ recipfile = sm.getSMIMEMemberCertFile(member) ++ if recipfile: ++ f = file(recipfile) ++ return f.read() ++ else: ++ return None ++ + # + # Write interface + # +@@ -354,3 +373,19 @@ + del self.__mlist.delivery_status[member] + else: + self.__mlist.bounce_info[member] = info ++ ++ def setGPGKey(self, member, key, keyids): ++ assert self.__mlist.Locked() ++ self.__assertIsMember(member) ++ member = member.lower() ++ if key!=None and len(key)==0: ++ key = None ++ if key is None: ++ if self.__mlist.gpgkeys.has_key(member): ++ del self.__mlist.gpgkeys[member] ++ if self.__mlist.gpgkeyids.has_key(member): ++ del self.__mlist.gpgkeyids[member] ++ else: ++ self.__mlist.gpgkeys[member] = key ++ self.__mlist.gpgkeyids[member] = keyids ++ +diff -uNr mailman-2.1.9.virgin/Mailman/SMIMEUtils.py mailman-2.1.9/Mailman/SMIMEUtils.py +--- mailman-2.1.9.virgin/Mailman/SMIMEUtils.py 1970-01-01 01:00:00.000000000 +0100 ++++ mailman-2.1.9/Mailman/SMIMEUtils.py 2008-01-10 20:49:49.000000000 +0100 +@@ -0,0 +1,320 @@ ++# Copyright (C) 2005 Tilburg University, http://www.uvt.nl/. ++# Author: Joost van Baal ++# Inspired by Stefan Schlott's GPGUtils.py ++# ++# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ ++"""This is a interface to the openssl command line tool, dealing with ++SMIME email messages.""" ++ ++# It should handle deadlock problems using threads. ++# It should be merged with GPGUtils.py and use the pyme GPGME interface. ++ ++# It should implement ++# key_ids = sm.verifyMessage(payload, signature) ++ ++# import re ++import os ++# import tempfile ++# import threading ++import errno ++ ++import tempfile ++ ++from Mailman import Errors ++from Mailman.Logging.Syslog import syslog ++from Mailman import mm_cfg ++ ++ ++class SMIMEHelper: ++ def __init__(self, mlist): ++ self.mlist = mlist ++ ++ # /var/lib/mailman/lists/test-secure/gpg is ~/.gnupg/ for list ++ # test-secure ++ # use /var/lib/mailman/lists//smime/{key,cert}.pem ++ ++ # FIXME die when these files are not present. As of 2005-11-28, we behave ++ # very bad when these are missing... ++ # ++ # self.smimedir = "/home/joostvb/smime" ++ self.smimedir = "%s/%s/smime" % (mm_cfg.LIST_DATA_DIR,mlist.internal_name()) ++ self.certfile = "%s/cert.pem" % self.smimedir ++ self.keyfile = "%s/key.pem" % self.smimedir ++ self.cafile = "%s/ca.pem" % self.smimedir ++ ++ def _getSMIMEMemberCertFile(self, member): ++ return "%s/%s.cert.pem" % (self.smimedir, member.lower()) ++ ++ def getSMIMEMemberCertFile(self, member): ++ recipfile = self._getSMIMEMemberCertFile(member) ++ ++ if not os.access(recipfile,os.F_OK): ++ syslog('gpg', "No Member SMIME Certfile %s found", recipfile) ++ return None ++ ++ syslog('gpg', "Using Member SMIME Certfile %s", recipfile) ++ return recipfile ++ ++ def importKey(self, member, key): ++ """beware! this routine does _not_ check wether member is a member of the list""" ++ recipfile = self._getSMIMEMemberCertFile(member) ++ try: ++ f = open(recipfile, 'w') ++ f.write(key) ++ f.close() ++ return True ++ except IOError (errno, strerror): ++ syslog('gpg', "Troubles writing S/MIME key for %s: %s, %s", member, errno, strerror) ++ return False ++ ++ def decryptMessage(self,msg): ++ """Typical invokation: (plaintext,signed) = ++ sm.decryptMessage(ciphertext) ++ signed is a Bool""" ++ ++ # cmd may be a sequence, in which case arguments will be passed ++ # directly to the program without shell intervention (as with ++ # os.spawnv()). If cmd is a string it will be passed to the shell (as ++ # with os.system()). ++ ++ # we don't give a password ++ # decrypt doesn't need -certfile, doesn't use /etc/ssl/certs/ ++ cmd = ("openssl", "smime" , "-decrypt", "-recip", self.certfile, "-inkey", self.keyfile) ++ # ++ # if we _want_ to fork an extra shell, run something like: ++ # cmd = "openssl smime -decrypt -recip %s -inkey %s" % (self.certfile, self.keyfile) ++ c_in, c_out, c_err = os.popen3(cmd) ++ ++ # hrm, we might need to do threading stuff here, like in ++ # Mailman/GPGUtils.py ++ # for now, the order in which we read and close different file handles ++ # _does_ matter! (does it?) ++ ++ c_in.write(msg) ++ c_in.close() ++ ++ out = c_out.read() ++ c_out.close() ++ ++ err = c_err.read() ++ c_err.close() ++ ++ # don't drag along children in zombie status ++ # FIXME check return status: actually do something with pid and status. ++ # see also Mailman/Utils.py ++ pid, status = os.waitpid(-1, os.WNOHANG) ++ ++ syslog('gpg',"openssl decrypt stderr: %s",err) ++ # syslog('gpg',"openssl decrypt stdout: %s",out) ++ ++ if out.startswith('Content-Type: multipart/signed; protocol="application/x-pkcs7-signature";'): ++ ++ cmd = ("openssl", "smime" , "-verify", "-CAfile", self.cafile) ++ c_in, c_out, c_err = os.popen3(cmd) ++ c_in.write(out) ++ c_in.flush() # FIXME is this needed? ++ c_in.close() ++ err = c_err.read() ++ plaintext = c_out.read() ++ c_out.close() ++ c_err.close() ++ ++ pid, status = os.waitpid(-1, os.WNOHANG) ++ syslog('gpg',"openssl verify stderr: %s",err) ++ ++ if err.startswith('Verification successful'): ++ syslog('gpg',"Valid smime signature found on message") ++ return (plaintext,True) ++ # return (plaintext,key_ids) FIXME: bool in key_ids? ++ else: ++ syslog('gpg',"No good smime signature found on message") ++ return (plaintext,False) ++ else: ++ syslog('gpg',"No good smime signature found on message: no x-pkcs7-signature MIME part in message") ++ return (out,False) ++ ++ def encryptMessage(self,msg,recipfile): ++ """msg: string holding plaintext. recipfile: .pem file holding ++ recipient certificate. returns ciphertext with leading MIME ++ headers""" ++ ++ # openssl smime -encrypt %a -outform DER -in %f %c ++ # %c One or more certificate IDs. ++ ++ # openssl smime -encrypt -in in.txt -from steve@openssl.org \ ++ # -to someone@somewhere -subject "Encrypted message" \ ++ # -des3 user.pem -out mail.msg ++ ++ ++# beware : openssl smime -encrypt from 0.9.7e (like Debian's 0.9.7e-3sarge1) gives us ++# Content-Type: application/x-pkcs7-mime; name="smime.p7m" ++# while we need a ++# Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m" ++# . ++# openssl_0.9.8a-4a0.sarge.1 gives us this. ++ ++# this works: ++# ++# % openssl smime -encrypt ~/.smime/certificates/joostvb-test-banach.crt < /etc/motd.old > ~/tmp/c ++# joostvb@banach:~% openssl smime -decrypt -recip ~/.smime/certificates/joostvb-test-banach.crt -inkey ~/.smime/keys/joostvb-test-banach.key < ~/tmp/c ++ ++ syslog('gpg',"running encryptMessage on %s", recipfile) ++ ++ # cmd = ("openssl", "smime" , "-encrypt", recipfile) ++ ++ # poor man's bfr(1) ++ (tmpfd, intmpfilename) = tempfile.mkstemp('.mailman') ++ os.write(tmpfd, msg) ++ os.close(tmpfd) ++ ++ (tmpfd, outtmpfilename) = tempfile.mkstemp('.mailman') ++ os.close(tmpfd) ++ ++ # "openssl -encrypt" reads and writes at same time. ++ # bfr(1) in Debian bfr package would help. ++ cmd = "openssl smime -encrypt -in %s %s > %s" % (intmpfilename, recipfile, outtmpfilename) ++ ++ c_in, c_out, c_err = os.popen3(cmd) ++ ++ # import popen2 ++ # c_out, c_in, c_err = popen2.popen3(cmd) # although this is what's suggested in ++ # Python Library Reference - 6.9.2 Flow Control Issues, it doesn't do the trick ++ ++ c_in.close() ++ err = c_err.read() ++ out = c_out.read() ++ c_out.close() ++ c_err.close() ++ ++ # FIXME would (0, os.WNOHANG) be better? ++ pid, status = os.waitpid(-1, os.WNOHANG) ++ ++ os.remove(intmpfilename) ++ ++ tmp = file(outtmpfilename) ++ ciphertext = tmp.read() ++ tmp.close() ++ os.remove(outtmpfilename) ++ ++ syslog('gpg',"openssl encrypt stderr: %s",err) ++ # syslog('gpg',"openssl encrypt stdout: %s",ciphertext) ++ ++ return ciphertext ++ ++ ++ def encryptSignMessage(self,msg,recipfile): ++ """signs as current list""" ++ ++ # Sign and encrypt mail: ++ # openssl smime -sign -in ml.txt -signer my.pem -text \ ++ # | openssl smime -encrypt -out mail.msg \ ++ # -from steve@openssl.org -to someone@somewhere \ ++ # -subject "Signed and Encrypted message" -des3 user.pem ++ ++ # does something like ++ # openssl smime -sign -signer ~/.smime/certificates/joostvb+20051121.crt -inkey ~/.smime/keys/joostvb+20051121.key -text < /etc/motd.old | openssl smime -encrypt ~/.smime/certificates/joostvb+20051121.crt > ~/tmp/mail.signed+encrypt ++ # uses encryptMessage ++ ++ syslog('gpg',"running encryptSignMessage on %s", recipfile) ++ ++ (tmpfd, intmpfilename) = tempfile.mkstemp('.mailman') ++ os.write(tmpfd, msg) ++ os.close(tmpfd) ++ ++ (tmpfd, outtmpfilename) = tempfile.mkstemp('.mailman') ++ os.close(tmpfd) ++ ++ (tmpfd, errtmpfilename) = tempfile.mkstemp('.mailman') ++ os.close(tmpfd) ++ ++ # cmd = ("openssl", "smime", "-sign", "-signer", crtfile, "-inkey", keyfile, "text", "-in", intmpfilename, "-out", outtmpfilename) ++ cmd = "openssl smime -sign -signer %s -inkey %s -text < %s > %s 2> %s" % \ ++ (self.certfile, self.keyfile, intmpfilename, outtmpfilename, errtmpfilename) ++ # -sign NEEDS to read from stdin. "-in" won't work. ++ ++ syslog('gpg',"encryptSignMessage: invoking openssl as '%s'", cmd) ++ ++ c_in, c_out, c_err = os.popen3(cmd) ++ ++ c_in.close() ++ err = c_err.read() # empty ++ out = c_out.read() # empty ++ c_out.close() ++ c_err.close() ++ ++ pid, status = os.waitpid(-1, os.WNOHANG) ++ ++ os.remove(intmpfilename) ++ ++ o = open(outtmpfilename) ++ signeddata = o.read() ++ o.close() ++ os.remove(outtmpfilename) ++ ++ e = open(errtmpfilename) ++ err = e.read() ++ e.close() ++ syslog('gpg',"openssl smime -sign returned '%s'",err) ++ os.remove(errtmpfilename) ++ ++ # syslog('gpg',"openssl smime -sign returned signed data '%s'", signeddata) ++ ++ ciphertext = self.encryptMessage(signeddata, recipfile) ++ return ciphertext ++ ++ # def verifyMessage(self,msg,signature): ++ def verifyMessage(self,msg): ++ if msg.is_multipart(): ++ for submsg in msg.get_payload(): ++ if submsg.get_content_type()=="application/x-pkcs7-signature": ++ ++ (tmpfd, intmpfilename) = tempfile.mkstemp('.mailman') ++ os.write(tmpfd, msg.as_string()) ++ os.close(tmpfd) ++ ++ (tmpfd, outtmpfilename) = tempfile.mkstemp('.mailman') ++ os.close(tmpfd) ++ ++ # specify cmd as a sequence: no shell needed ++ # cmd = ("openssl", "smime", "-verify", "-CAfile", self.cafile) ++ # cmd = "openssl smime -verify -CAfile self.cafile -out %s" % tmpfilename ++ cmd = ("openssl", "smime", "-verify", "-CAfile", self.cafile, "-in", intmpfilename, "-out", outtmpfilename) ++ ++ c_in, c_out, c_err = os.popen3(cmd) ++ ++ c_in.close() ++ err = c_err.read() ++ out = c_out.read() # empty ++ c_out.close() ++ c_err.close() ++ ++ pid, status = os.waitpid(-1, os.WNOHANG) ++ syslog('gpg',"openssl returned '%s'",err) ++ ++ os.remove(intmpfilename) ++ ++ # holds a copy of payload ++ os.remove(outtmpfilename) ++ ++ if err.startswith('Verification successful'): ++ syslog('gpg',"Valid smime signature found on message") ++ return True ++ else: ++ syslog('gpg',"Invalid smime signature found on message") ++ ++ return False ++ # return key_ids ++ +diff -uNr mailman-2.1.9.virgin/NEWS.SSLS mailman-2.1.9/NEWS.SSLS +--- mailman-2.1.9.virgin/NEWS.SSLS 1970-01-01 01:00:00.000000000 +0100 ++++ mailman-2.1.9/NEWS.SSLS 2008-01-10 20:49:49.000000000 +0100 +@@ -0,0 +1,165 @@ ++ChangeLog for the Mailman SURFnet Secure List Server Patch ++---------------------------------------------------------- ++ ++2006-01-30 Joost van Baal ++ ++ * Mailman/Cgi/options.py, Mailman/{Defaults.py.in,MailList.py}, ++ Mailman/Gui/Privacy.py, Mailman/Handlers/{Hold.py,SMTPDirect.py}, ++ bin/update: Updated to apply to upstream 2.1.7: merged 2.1.6 -> 2.1.7 ++ changes. ++ * Mailman/Gui/Privacy.py: more hints on how to import PGP key using webgui. ++ * Mailman/Handlers/SMTPDirect.py: fix Content-Transfer-Encodings: be nice to ++ those who don't use us-ascii. Thanks to Michael Feiri for this patch. ++ * TODO.SSLSL: bugs #0067, #0068, #0069 added. ++ ++2006-01-09 Joost van Baal ++ ++ * Split TODO.SSLS and NEWS.SSLS off README.GPG; convert README.GPG to ++ README.SSLS.html. ++ * README.SSLS.html: This project has a new homepage; added more notes on how ++ to contribute patches. ++ * TODO.SSLS: Roadmap and long-term wishes added. ++ * Mailman/Handlers/GpgSMTPDirect.py: removed. This stuff is now maintained ++ as a patch on SMTPDirect.py. ++ * Mailman/Defaults.py.in: no longer calls GpgSMTPDirect.py as ++ DELIVERY_MODULE, but uses patched SMTPDirect.py. ++ * Mailman/Handlers/GpgSMTPDirect.py: bugfixes; sanitize encrypted message's ++ MIME structure before distributing. Don't sent out S/MIME mails with ++ bogus MIME structure. ++ * Mailman/Cgi/options.py: fix bug in uploading S/MIME key via webinterface. ++ * Mailman/SMIMEUtils: make verifyMessage more robust: no more broken pipe; ++ implemented encryptSignMessage ++ * Mailman/SMIMEUtils, Mailman/Handlers/GpgSMTPDirect.py: document issue with ++ openssl 0.9.7e (we have implemented a workaround for this issue). ++ * Mailman/SMIMEUtils: encryptSignMessage no longer strips off first bodyline. ++ ++2005-11-21 Joost van Baal ++ ++ * Another extremely unstable bleeding edge known-broken release. ++ * Mailman/Gui/Privacy.py: add notes on new list properties, so that ++ config_list gets aware of these. ++ * Mailman/SMIMEUtils.py: now implements verifyMessage; honors per-list ++ ca.pem. Work around I/O deadlocks while encrypting by using tempfile ++ module. Thanks to Wessel Dankers for hint. Of course, this should ++ get reimplemented using threads. ++ * Mailman/Handlers/Hold.py: added classes NonSMIMESignedPost and ++ WrongSMIMESignedPost. ++ * Mailman/Handlers/Moderate.py: deal with unsigned S/MIME posts which ++ should be have been signed, deal with signed+encrypted posts. ++ * Mailman/Cgi/options.py, Mailman/Gui/Privacy.py, ++ Mailman/{OldStyleMemberships.py,SMIMEUtils.py}, ++ templates/en/options.html: added webgui for uploading subscriber ++ S/MIME keys; new routines SMIMEUtils.importKey() and ++ mlist.getSMIMEKey() added. ++ * Mailman/Handlers/GpgSMTPDirect.py: now creates sane S/MIME-encrypted ++ messages (no longer produces corrupt MIME) ++ * Added bunch of S/MIME-related things left to do to TODO-list in this ++ file. ++ ++2005-10-28 Joost van Baal ++ ++ * Extremely unstable bleeding edge known-broken release. ++ * S/MIME stuff added: ++ - Mailman/MailList.py, Mailman/Defaults.py.in, bin/update: new list ++ properties: ++ self.smime_post_encrypt = mm_cfg.DEFAULT_SMIME_POST_ENCRYPT ++ self.smime_post_sign = mm_cfg.DEFAULT_SMIME_POST_SIGN ++ self.smime_distrib_encrypt = mm_cfg.DEFAULT_SMIME_DISTRIB_ENCRYPT ++ self.smime_distrib_sign = mm_cft.DEFAULT_SMIME_DISTRIB_SIGN ++ - Mailman/SMIMEUtils.py: added ++ * Fixed FSF snail mail address. ++ * Updated TODO-list, added note on copyright in this file. ++ * Numbered outstanding bugs in TODO-list. ++ * Advertised ssls-devel list in this file. ++ * Advertise version control access in this file. Thanks Laurent Fousse and ++ Wessel Dankers for help in setting this up. ++ ++2005-07-01 Joost van Baal ++ ++ * Mailman/Defaults.py.in, Mailman/Gui/Privacy.py, Mailman/Handlers/Hold.py, ++ Mailman/Handlers/Moderate.py, Mailman/MailList.py, ++ templates/en/options.html: ++ Updated to apply to upstream 2.1.6: merged 2.1.5 -> 2.1.6 changes. ++ * REAME.GPG: lots of (wishlist) bugs added, assigned priorities. No longer ++ present this as a patch on Stefan Schlott's patch: adapted intro text. ++ * Mailman/GPGUtils.py: fix fatal bug: global name 'result' is not defined. ++ Triggered under some circumstances when decrypting fails. ++ * Mailman/GPGUtils.py, Mailman/Handlers/GpgSMTPDirect.py: fixed copyright ++ statements (taken from Stefan's mailman-2.1.5-gpg_2005-05-03.diff.gz) ++ ++2005-04-21 Joost van Baal ++ ++ * Mailman/Handlers/Moderate.py: Force settings of ++ gpg_postings_allowed/gpg_post_sign were mixed: bugfix. ++ * Mailman/Defaults.py.in: More strict defaults: No web archive: ++ (DEFAULT_ARCHIVE), if archive defined, not public ++ (DEFAULT_ARCHIVE_PRIVATE), don't archive in mbox format (ARCHIVE_TO_MBOX), ++ show list of subscribers to admin only (DEFAULT_PRIVATE_ROSTER). ++ * README.GPG: Stefan's todo list merged. ++ ++2005-04-18 Joost van Baal ++ ++ * Mailman/MailList.py, Mailman/versions.py, bin/update: gpg_secret_key and ++ gpg_public_key are of type string, even if unset. Otherwise, config_list ++ might choke: it tries to invoke splitlines() on these settings. ++ * Mailman/Handlers/Moderate.py: behave more sane on strange messages: code ++ robustness fix. ++ * Mailman/Handlers/GpgDecrypt.py, Mailman/Handlers/Moderate.py: GpgDecrypt ++ is merged with Moderate: we need to share data about valid signatures ++ among these things; adapting the Message type for passing this data is too ++ intrusive. ++ * Mailman/Handlers/Moderate.py: no longer adds valid-signature info to body. ++ * Mailman/mm_cfg.py.in: this file is no longer patched, all config patching ++ (i.e. DELIVERY_MODULE = 'GpgSMTPDirect') is done in Defaults.py ++ * Mailman/GPGUtils.py: decryptMessage now uses more stable status fd ++ interface from gnupg. Now returns _all_ key_ids of signers. ++ ++2005-03-24 Joost van Baal ++ ++ * Mailman/GPGUtils.py, Mailman/Handlers/Moderate.py: more fixes in copyright ++ blurbs. ++ * README.GPG: warnings on gotcha's added. ++ * Mailman/Handlers/Moderate.py: fixed bug in code (TypeError) which would ++ show up if some members didn't supply their public key. ++ ++2005-03-22 Joost van Baal ++ ++ * Updated this README.GPG file: more pointers. ++ * Mailman/GPGUtils.py: fixed verifyMessage (it was unusable.) ++ * Mailman/Handlers/Moderate.py: new verifyMessage interface: we now deal ++ with both inline signatures and detached signatures. ++ ++2005-03-21 Joost van Baal ++ ++ * Mailman/Handlers/Moderate.py: fix bug in handling of gpg_post_sign (it ++ was unusable.) ++ * bin/update: add gpg_post_sign. ++ * Mailman/Handlers/GpgDecrypt.py: Fixed copyright blurb, after consulting ++ Stefan. (Mailman/GPGUtils.py will get fixed eventually.) ++ ++2005-03-15 Joost van Baal ++ ++ * Mailman/Handlers/Moderate.py: fix syntax error and missing import. Oops. ++ * Mailman/GPGUtils.py: make sure verifyMessage returns a sequence, make ++ sure it's not waiting on stdin. Add --no-permission-warning to gpg ++ options: typically, we have a group-writable GnuPG homedirectory since ++ both the webserver and the Mailman user interface with us. ++ * Mailman/versions.py: add gpg_post_sign to list attributes, in order to fix ++ AttributeError ++ ++2005-03-14 17:01:10 +0100 Joost van Baal ++ ++ * Mailman/Defaults.py.in, Mailman/GPGUtils.py, Mailman/Gui/Privacy.py, ++ Mailman/Handlers/Hold.py, Mailman/Handlers/Moderate.py, ++ Mailman/MailList.py: first shot at adding signature-verification ++ support as a moderation criterium. ++ ++2005-02-11 Stefan Schlott (mailman-2.1.5-gpg_2005-02-22.diff.gz) ++ ++ - hide the key ID in the "good signature" info of the list server in ++ the case of "anonymous lists" ++ - change "Message had a good signature" into something more useful (if ++ detached signatures aren't possible) that's not so easy to spoof ++ (Thanks, Nicolas!) ++ - typo in the section about mailclients (Thanks, Sebastian!) ++ +diff -uNr mailman-2.1.9.virgin/README.SSLS.html mailman-2.1.9/README.SSLS.html +--- mailman-2.1.9.virgin/README.SSLS.html 1970-01-01 01:00:00.000000000 +0100 ++++ mailman-2.1.9/README.SSLS.html 2008-01-10 20:49:49.000000000 +0100 +@@ -0,0 +1,223 @@ ++ ++ ++ ++ The SURFnet Secure List Server: an OpenPGP and S/MIME aware Mailman ++ ++

The SURFnet Secure List Server: an OpenPGP and S/MIME aware Mailman

++
++ ++

This patch is an effort to include OpenPGP and S/MIME support in Mailman, as ++part of the SURFnet Secure List Server ++project.

++ ++

The Mailman SSLS project's home is at ++http://non-gnu.uvt.nl/mailman-ssls/. Sources are available from http://non-gnu.uvt.nl/pub/mailman/.

++ ++

New versions of this patch will be announced on the Mailman ++developers list

++ ++

This is Mailman patch ++#1167696.

++ ++

Beware! This code is not mature, and not yet suitable for production use. ++Inspect the code to find out if it's good enough for you.

++ ++ ++

Specs

++ ++

This patch handles both RFC 2633 (S/MIME) and RFC 2440 (OpenPGP) email ++messages.

++ ++

A post will be distributed only if the PGP (or S/MIME) signature on the post ++is from one of the list members.

++ ++

For sending encrypted email, a list member encrypts to the public key of the ++list. The post will be decrypted and re-encrypted to the public keys of all ++list members.

++ ++

In order to achieve this, each list has a public and private key. (These ++private keys can optionally be protected by passphrases.) Furthermore, new list ++settings are defined:

++ ++
    ++
  • gpg_postings_allowed: Is it allowed to send to this list postings which are ++ encrypted with the GPG list key?
  • ++
  • gpg_msg_distribution: Are subscribers allowed (or even forced) to upload ++ their GPG public key in order to receive all messages encrypted?
  • ++
  • gpg_post_sign: Should posts be GPG signed with an acknowledged subscriber ++ key before being distributed?
  • ++
  • gpg_msg_sign: Should the server sign encrypted messages?
  • ++
++ ++

Similar settings are defined for S/MIME.

++ ++

Finally, each subscriber can upload her PGP and S/MIME public key using the ++webinterface.

++ ++ ++

Installation

++ ++

Additional requirements: gpg binary in path of qrunner, GnuPGInterface ++python library from http://py-gnupg.sourceforge.net/ ++and (for now) openssl.

++ ++ ++

upgrading from earlier and/or unpatched versions

++ ++

SSLS support needs to store additional information for the mailing lists, ++thus new variables were introduced. Have a look at bin/update and search ++for "GPG" and "smime".

++ ++

The automatic upgrade procedure occuring on a change of the version number ++is implemented yet untested. You can either modify the version number in ++Version.py to trigger the automatic patch, or run bin/update --force (worked ++for me, but I won't give any guarantees).

++ ++

fresh installs

++ ++

If you're installing on a Debian(-based) hosts, you can "apt-get install" ++the patched package from

++ ++
++deb http://non-gnu.uvt.nl/debian sarge mailman
++
++ ++

If you're on another platform, you can apply the patch to a pristine official ++mailman tarball, and install using the official installation instructions.

++ ++

setting up lists

++ ++

Create a list. Go to the admin menu. Open "Privacy options", select "GPG ++options" and "S/MIME options". Make the settings appropriate for your list ++(yes, there is some help text!). Create a keypair for your list. Make sure ++the list-posting-address is in one of the keys identities. Publish the public ++key for your list, e.g. on the list info page. Ask all subscribers to upload ++their public key. A good idea is to set up the web interface to only run over ++https.

++ ++

Very likely, you'd prefer "Confirm and approve" as subscribe_policy (Privacy ++Options; Subscribtion Rules).

++ ++

Very likely, you'd prefer "No" for archive (Archiving Options): no effort ++was made to do something "sane" w.r.t. the archiving of encrypted posts. Just ++don't archive.

++ ++

Subscribers who do not upload their public keys will miss posts. Errors ++show up in /var/log/mailman/smtp-failure:

++ ++
Mar 24 15:29:28 2005 (5267) delivery to joe@example.com failed with
++  code 550: Encryption forced, but no keys found
++ ++

Tell your subscribers where to upload their keys: list Info Page, ++Subscribers section. Visit "Subscriber List", choose your address, list ++membership configuration, "Your GPG key for postings".

++ ++

Distribute the list public key to your subscribers, e.g. by posting it to ++the list after they've subscribed, or by pasting it in "[General Options], ++info" in the list admin webinterface.

++ ++

l10n

++ ++

I only updated the english html template for the user preferences; ++that means that users using a different language setting will be unable ++to upload their public key. Either allow English only, or make the ++patches to other language templates (and send the diff to me).

++ ++

lost emails

++ ++

Users won't get warnings when mails are not delivered to them due ++to the encryption send policy. Failures to send due to a missing public key ++will be treated like bounces.

++ ++ ++

Hacking

++ ++

If you're interested in helping with the work, you might like to subscribe ++to the SURFnet Secure List Server Development List ++ssls-devel /a/ securelist.surfnet.nl.

++ ++

If you'd like to contribute patches, check out the code using darcs:

++ ++
darcs get http://non-gnu.uvt.nl/repos/mailman-ssls
++ ++

A fancy webinterface to this version control system is available at http://non-gnu.uvt.nl/cgi-bin/darcs.cgi/mailman-ssls/.

++ ++

If you'd like your changes to get imported in ++http://non-gnu.uvt.nl/repos/mailman-ssls, so that your stuff will ++get incorporated in the SSLS patch, you have some options:

++ ++
    ++
  • Publish your patches using darcs: set up a repository, readable by Joost ++van Baal (the current maintainer of the repository on ++non-gnu.uvt.nl). Mail the ssls-devel list once you have some ++interesting stuff, ready for importing: Joost will run darcs ++pull;
  • ++
  • Sent patches by email, using darcs (run darcs record and ++darcs send, so that your patch is ready for ++darcs apply) or using traditional unified diff ++format;
  • ++
  • Commit to the repository on non-gnu.uvt.nl directly. You ++can get write access either after asking and getting granted access, or after ++being invited and accepting access.
  • ++
++ ++

Please split contributions and patches in small bits: one patch for each ++functional change please. This helps people who'd like to apply just a subset ++of your patches.

++ ++ ++

History, credits, copyright

++ ++

This patch is based upon prior work ++by Stefan Schlott in mailman-2.1.5-gpg_2005-02-22.diff.gz. Copyright on ++the code is held by Stefan Schlott (stuff from ++mailman-2.1.5-gpg_2005-02-22.diff.gz) and Tilburg ++University (stuff written by Joost van Baal), see the individual files for ++details. Some contributions are from Michael ++Feiri.

++ ++

Biggest lumps of changes by Joost van Baal are in Mailman/GPGUtils.py ++(function verifyMessage added) and in Mailman/Handlers/Moderate.py (code which ++deals with gpg_post_sign added). These are likely nontrivial so ++copyright-able. Check with a specialist if you'd like to know for sure. ++Likely, Mailman upstream wants a copyright assignment to FSF before patch gets ++included.

++ ++ ++

See also

++ ++

There are some alternative approaches on integrating PGP with Mailman:

++ ++ ++ ++ ++

ChangeLog, BUGS and Roadmap

++ ++

See NEWS.SSLS for user visible (and some other) ++changes. See TODO.SSLS for known bugs and plans for ++improvement.

++ ++ +diff -uNr mailman-2.1.9.virgin/templates/en/options.html mailman-2.1.9/templates/en/options.html +--- mailman-2.1.9.virgin/templates/en/options.html 2008-01-10 20:49:03.000000000 +0100 ++++ mailman-2.1.9/templates/en/options.html 2008-01-10 20:49:49.000000000 +0100 +@@ -144,6 +144,38 @@ + +

+ ++ ++ ++ ++ ++
++ Your GPG key for postings to the list ++
++ ++ ++
++ ++ Change globally. ++
++ ++

++ ++ ++ ++ ++ ++
++ Your S/MIME key for postings to the list ++
++ ++ ++
++ ++ Change globally. ++
++ ++

++ + +diff -uNr mailman-2.1.9.virgin/TODO.SSLS mailman-2.1.9/TODO.SSLS +--- mailman-2.1.9.virgin/TODO.SSLS 1970-01-01 01:00:00.000000000 +0100 ++++ mailman-2.1.9/TODO.SSLS 2008-01-10 20:49:49.000000000 +0100 +@@ -0,0 +1,380 @@ ++TODO file for the Mailman SURFnet Secure List Server Patch ++========================================================== ++ ++This file lists a roadmap, bugs and wishes. ++ ++Roadmap ++------- ++ ++This roadmap lists Joost van Baal's current plans. ++ ++1) Apply patches by Michael Feiri. ++2) Create and publish a 2.1.7 .diff.gz release. ++3) Create and publish a new Debian package release. ++ (Upgrade securelist.surfnet.nl to this release.) ++4) Announce release on mailman-developers list. ++5) Do "real" work on the code. ++ ++ ++Goals ++----- ++ ++This is a wishlist for long-term development; mainly aimed at improving chances ++patch will get accepted upstream (and/or by a *BSD or GNU/Linux distribution). ++ ++1) Security Audit ++See also bugs #0009 #0012 #0013 #0030 #0033 ++ ++2) Make patch non-intrusive and minimal ++Default Mailman behavious should be the same as unpatched Mailman. ++Use one library for all crypto-operations, e.g. GPGME. ++See also bug #0015. ++ ++3) Documentation ++Documentation is needed for: ++ - endusers (list subscribers: html helpfiles in webfrontend) ++ - list admins (html helpfiles in webfrontend) ++ - site admins (TeX documentation in tarball) ++ - developers (python docstrings in code) ++See also: #0011 #0012 #0020 #0034 ++ ++ ++Bugs and Wishes ++--------------- ++ ++This is a detailed list of known bugs and of current wishes/tasks. ++ ++- Do more testing. ++ ++work /severity work is: easy, normal, difficult. ++ severity is: wishlist, normal, critical. ++ ++#0001 normal/crit Fatal error: ++ shamir:/var/log/mailman/error ++ Apr 26 13:22:11 2005 (23365) Uncaught runner exception: [Errno 32] Broken pipe ++ Apr 26 13:22:11 2005 (23365) Traceback (most recent call last): ++ File "/usr/lib/mailman/Mailman/Queue/Runner.py", line 111, in _oneloop ++ self._onefile(msg, msgdata) ++ File "/usr/lib/mailman/Mailman/Queue/Runner.py", line 167, in _onefile ++ keepqueued = self._dispose(mlist, msg, msgdata) ++ File "/usr/lib/mailman/Mailman/Queue/OutgoingRunner.py", line 73, in _dispose ++ self._func(mlist, msg, msgdata) ++ File "/usr/lib/mailman/Mailman/Handlers/GpgSMTPDirect.py", line 146, in process ++ deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) ++ File "/usr/lib/mailman/Mailman/Handlers/GpgSMTPDirect.py", line 378, in verpdeliver ++ ciphertext = gh.encryptSignMessage(plaintext,keyids) ++ File "/var/lib/mailman/Mailman/GPGUtils.py", line 281, in encryptSignMessage ++ p.handles['stdin'].close() ++ IOError: [Errno 32] Broken pipe ++ Apr 26 13:22:11 2005 (23365) SHUNTING: 1114514530.3134+8c6726072985dc472532b1f538236a4365743440 ++ . ++ Probably occurs when private key for list is missing, and: ++ gpg_postings-allowed: Yes ++ gpg_msg_distribution: Yes ++ gpg_post_sign: Yes ++ gpg_msg_sign: Yes ++#0002 normal/crit The "change global" toggle in the subscribers ++ upload-your-public-key webgui box is broken: ++ joostvb@shamir:~% gpg --homedir /var/lib/mailman/lists/ssls-private/gpg ++ --fingerprint ++ doesn't show the key. See also #0066. ++#0003 easy /normal Prepare translations for the 'upload pgp key' web thingie. ++ Especially Dutch is needed. Users who have a non-english preferred language ++ won't see this option now. ++#0005 normal/wish Bounce messages to posters ("Message has to be encrypted!") ++ have same subject as post. That's better be: "Subject: Message rejected ++ (was: foo)". ++#0006 ? /? Using the web roster, any subscriber can view any ++ subscribers' preferences, including public key. And maybe even change. Can ++ it? ++#0007 easy /normal Under some circumstances, uploading a bogus public ++ subscriber key leads to posts being silently discarded (even the admin does ++ not get a notification) if list has gpg_msg_distribution Force. ++#0008 easy /crit Remove all debug code: currently, it sends way to much ++ stuff to syslog. ++#0009 diffic/? Harden this thing: re-encrypt immediately after decrypting. ++ This patch (re)encrypts _just_ before sending. ++#0011 normal/normal Merge more of my own docs (doc/secure-list-patch.pod, ++ doc/mutt.txt, doc/smime.pod) with this patch. ++#0012 easy /crit Fix documentation: when creating a list, make sure the ++ listadmin password is _not_ sent via plain email: Someone stealing the list ++ admin password has access to the list private key. Therefore, create the list ++ using the CLI, and transport password manually via secure channel. ++#0013 easy /normal Perhaps we should suggest an empty passphrase for list ++ keys in our interface: The passphrase is stored in clear-text anyway. ++ Perhaps even just remove the passphare textbox in the webgui. gpg warns when ++ creating such a key: "You need a Passphrase to protect your secret key. ++ [...] You don't want a passphrase - this is probably a *bad* idea! I will ++ do it anyway. You can change your passphrase at any time, using this program ++ with the option "--edit-key"." ++#0014 ? /normal Passphrase as supplied to webinterface cannot contain ++ stuff which needs html-escaping. Workaround: use [:alphanum:] only. ++#0015 diffic/normal We should refuse to create an html list archive for secure ++ lists. (Currently, the default for new lists is changed by this patch from ++ do-archive to don't-archive.) ++#0016 normal/wish Perhaps we should enable fetching public keys from ++ subscribers from OpenPGP keyservers. Pasting huge public keys can be a pita. ++#0017 ? /crit It seems we need a symlink ++ /usr/lib/mailman/Mailman/GnuPGInterface.py -> ++ /usr/lib/site-python/GnuPGInterface.py ++ We might have to hack paths.py to fix this. ++#0018 normal/? It'd be nice if commandline interfaces and email interfaces ++ could be used for configuring (some of the) gpg stuff too, e.g. for uploading ++ public keys. ++#0019 ? /crit Member public keys should be importable from a ++ database; therefore write CLI's. ++#0020 easy /crit Add ^L-thingies and other stuff from Mailman coders ++ styleguide to this patch. ++#0021 diffic/? Add extra configuration toggle: some users might want to ++ post signed, but receive unencrypted posts unencrypted. Currently, this is ++ not possible. ++#0022 easy /crit (Perhaps a problem in the Debian package only: ) ++ /var/log/mailman/gpg is not rotated. ++#0023 ? /wish If a list has: ++ gpg_postings_allowed Yes (encrypt post to listkey) ++ gpg_msg_distribution Force (distribute encypted) ++ gpg_post_sign Force (should posts be signed) ++ gpg_msg_sign Yes (distribute signed) ++ and someone is subcribed without having uploaded her public key, then ++ this person receives posts mangled: content-type header says us-ascii, ++ while body is quoted-printable. ++#0024 diffic/? If a post is properly signed, accept it, no matter wether ++ the From-adress is subscribed and no matter the sender moderation policy. ++#0025 ? /? Problems with umlauts aka inline-pgp-trouble - seems to be ++ fixed, but requires additional testing ++#0026 ? /wish Inline PGP-mail with attachments - undefined results. Some ++ mailers can produce this. :-( ++#0027 ? /? Deal with both inline (aka traditional) pgp signed/encrypted ++ posts as well as pgpg/mime; test this. ++#0028 diffic/wish If the "force" policy is in effect: reject plaintext control ++ e-mails that contain a command with a password - currently all unsigned ++ control e-mails are accepted without question because subscribe requests are ++ handled over this channel as well ++#0029 diffic/wish If the "force" policy is in effect: "fuzzy checking", that ++ is, if only part of the e-mail is encrypted (with cleartext underneath the PGP ++ block). Is there a need for this? ++#0030 diffic/crit Deal with subscribers without public keys: Notification ++ e-mail to those who haven't uploaded a key; and/or: make a (CLI) interface ++ to check for subscibers without keys, to be used by site- or list-admin. ++ Under some circumstances, these subscribers receive all mail in clear-text, ++ even the encrypted ones. ++#0031 diffic/wish When bouncing e-mail because list policy was violated (e.g., ++ someone sent unencrypted e-mail to mailman even though ++ encryption-policy="force"): only bounce the headers, not the complete e-mail ++ message. ++#0032 diffic/wish Fix the way mails are sent out over SMTP: do chunking for ++ encrypted e-mail too. see also ++ http://mail.python.org/pipermail/mailman-developers/2005-February/017910.html ++ . ++#0033 ? /crit All defaults should be strict: lists not visible on ++ listinfo page, ++ no roster, only listadmin can add members. Perhaps it's best to add ++ this to install manual: system-admin, you should hack mm_cfg.py! ++#0034 normal/crit Write proper documentation for listadmins and subscribers. ++ Ideally distribute this in a patch to the official Mailman docs. We'd possibly ++ need to fix upstream Makefile for this: how is documentation being typesetted ++ before being distributed? Should we include patches for both .tex and .ps in ++ our patch? Or hack installation instructions and recommend running "make doc" ++ manually? See thread: ++ Date: Wed, 4 Jan 2006 16:57:33 +0100 ++ From: Joost van Baal ++ To: Maiman Developers ++ Subject: preferred documentation format, sources for documentation in admin/www ++ Message-ID: <20060104155733.GA29152@banach.uvt.nl> ++#0035 diff /crit Try to make this patch clean (i.e.: default behaviour for ++ non-ssls lists should be the same; minimize the amount of ssls code executed ++ for such lists), in order to get it into upstream Mailman (or the Mailman ++ Debian package). ++#0036 diff /wish When re-encrypting a signed message, the original signature ++ gets lost: this makes it possible for one list member to pose as another list ++ member. In theory, it should be possible to keep the original signature after ++ decryption. ++#0037 normal/wish Merge stuff from Stefan's 2005-05-03 patch in this patch. ++#0038 normal/normal Create a commit list for the darcs repository and announce ++ it. See the thread following ++ Date: Fri, 12 Aug 2005 11:26:30 +0200 ++ From: Joost van Baal ++ To: SURFnet Secure List Server Development List ++ Message-ID: <20050812092630.GA16126@banach.uvt.nl> ++ Subject: [Ssls-devel] current status and plans of Mailman SSLS: S/MIME and ++ other stuff ++ . Possibly we can use the RSS feed from the darcs webinterface for this. ++#0039 easy/normal Bounce message: "Unsigned post to Secure list" is ++ misleading, and should be rephrased to "Post to Secure List not signed ++ with registered subscriber PGP key". ++ ++#0040 Tag work and severity of all bugs listed below. ++ ++#0041 (smime) Check if trouble with "broken pipe" problem is really fixed now. ++ use 2 tmpfiles for each popen3-call in SMIMEUtils.py, to be sure no ++ deadlocking will occur. ideas: use os.system, not popen3. Clean up code. ++ ++#0042 (smime) Check signing and signing-and-encrypting. ++ + sending signed as j.e.vanbaal+20051121@uvt.nl to ++ test-smime@securelist.surfnet.nl: OK (recheck!) ++ sending bare as j.e.vanbaal+20051121@uvt.nl to ++ test-smime@securelist.surfnet.nl: OK (recheck!) ++ ++#0065 (smime) In GpgSMTPDirect.py we fetch only the first attachment when ++ dealing with S/MIME. We search for attachments only 2 levels deep. That's ++ suboptimal... ++ ++#0043 (smime) In the webgui, add an interface to upload a list-key (or one to ++ create one). ++ ++#0044 (smime) Clean up comments in Mailman/SMIMEUtils.py ++ ++#0045 Check all FIXME's and TODO's in all files. ++ ++#0046 Value tests are crap, for both gpg and smime. Very often " =='1' " is ++ written where " == 1 " should have been written. Very often tests for 'Force' ++ ' and 'No' are done. Just test for int.s. ++ ++#0047 Use os.path.join , not "/". ++ ++#0048 (smime) Clean up and make more robust: decryptSmime(mlist, msg, msgdata) in ++Mailman/Handlers/Moderate.py ++ ++#0049 (smime) In Mailman/SMIMEUtils.py, implement ++encryptSignMessage(self,msg,recipients) ++ ++#0050 (smime) make sure posts get encrypted and signed if needed ++ ++#0051 (smime) reimplement specifying recipient for encrypting, check ++Handlers/GpgSMTPDirect.py: having one .pem-file for each member is suboptimal, ++but that's how it's done in sympa-5.1: use email adress in filename! ++ ++#0052 for both GPG and S/MIME: the list never sends out a list-key-signed message which ++ is not encrypted, no matter the list-privacy-settings. It should! ++ ++#0053 (smime) Make sure SMIMEUtils.py behaves sane when smime/*pem is lacking. ++ ++#0054 (smime) Deal with ++ ++ From: Werner Koch ++ To: Joost van Baal ++ Cc: GnuPG Users ++ Subject: Re: handling S/MIME messages with gpgsm ++ Date: Mon, 24 Oct 2005 09:13:51 +0200 ++ Message-ID: <8764rnbd5c.fsf@wheatstone.g10code.de> ++ ++ Find out how to make ++ gpgsm --verify signed.CMS signed.body ++ succeed: how to create signed.CMS and signed.body for an S/MIME ++ detached-signed message? ++ ++#0055 Deal with stuff in thread ++ ++ Date: Fri, 12 Aug 2005 11:26:30 +0200 ++ From: Joost van Baal ++ To: SURFnet Secure List Server Development List ++ Message-ID: <20050812092630.GA16126@banach.uvt.nl> ++ ++#0056 shamir's gpgsm and gnupg-agent is from gnupg2 (1.9.18-0.1); 9.19 is ++ available. Upgrade shamir from stuff at non-gnu. ++ Install 1.9.19 stuff from http://non-gnu.uvt.nl/debian/scratch/ on shamir. ++ ++#0057 Ask Stefan Schlott to acknowledge the added comment with a link to ++ http://non-gnu.uvt.nl/mailman-ssls on ++ http://medien.informatik.uni-ulm.de/~stefan/linux/gpg-mailman . ++ ++#0058 The sympa ( http://www.sympa.org/, GPL ) mailing list manager calls the ++ openssl binary from within Perl. Check out the smime_* functions in ++ sympa-4.1.5/src/tools.pl. (Or use the 5.1 sources) ++ ++#0059 (smime) KMail is said to use GPGME for S/MIME. Study its source. ++ ++#0060 emailf00f by Guus Sliepen deals with PGP. Study its source. ++ ++#0061 (smime) study RFC 2630 [CMS] and RFC 2315 [PKCS7] ++ ++#0062 (smime) _robustly_ identify incoming S/MIME posts: ++ ++ RFC 3851 3.9. Identifying an S/MIME Message ++ ++ MIME type: application/pkcs7-mime ++ parameters: any ++ file suffix: any ++ ++ MIME type: multipart/signed ++ parameters: protocol="application/pkcs7-signature" ++ file suffix: any ++ ++ MIME type: application/octet-stream ++ parameters: any ++ file suffix: p7m, p7s, p7c, p7z ++ ++#0063 (smime) Integrate all other useful private notes in ++ <20050913155839.GQ8055@banach.uvt.nl>. ++ ++#0064 (smime) Finish our small test scripts: ++ + Document genkey.py, so that our action is reproducable. ++ + Document decrypt.py, so that our action is reproducable. ++ + Write a script like descrypt.py which performs verification. ++ + Tidy up section "importing a secret key". Find out wether we can do this ++ without using CA.pl. Tidy up the description in scratch/simple.py . ++ + adjust documentation of scripts in scratch/ (simple.py) to no longer ++ use precooked keys from gpgme1.0_1.0.3/tests/gpgsm/. ++ + Send our hacked example scripts to pyme upstream. ++ + Merge decrypt.py and simple.py in mailman-smime.py. ++ ++#0066 It seems to be impossible to remove (or change) a public ++ key for a subscriber. Even after unsubscribing, the key seems to be kept. ++ Currently, one needs to do something like ++ # GNUPGHOME=/var/lib/mailman/lists/test-secure/gpg gpg --delete-key 88C6EDF6 ++ Under some circumstances (member is subscribed, key was purged later), ++ uploading a public key using the webgui fails. One might have to do: ++ # GNUPGHOME=/var/lib/mailman/lists/test-secure/gpg gpg --import < /tmp/a ++ It seems the list gets confused about the keyid belonging to the subscriber. ++ See also #0002. ++ . ++ When fixing this, be sure to get rid of the gpgkeyids and gpgkeys properties ++ of a MailList object. Store all this stuff in _one_ dictionary, keyed by ++ member-email-adresses. Be sure to adjust the unsubscribtion hook: currently, ++ the dictionaries are not cleaned after unsubscription. ++ ++#0067 If permissions on pubring.gpg are borked, Mailman gets hit by a Broken ++ pipe, and messages get shunted. ++ ++#0068 We are vulnerable for replay attacks. Likely it's useful to protect against ++ those: likely our subscribers will silently assume we're not vulnerable. ++ ++ Fri 13 13:48 < guus> joostvb: heeft SSLS al replay protection? ++ Fri 13 13:58 < joostvb> guus: is OpenPGP kwetsbaar voor replay-attacks? ++ Fri 13 13:58 < Fruit> alleen als je de datum niet controleert ++ Fri 13 13:59 < guus> joostvb: ja, maar S/MIME ook voor zover ik weet. ++ Fri 13 13:59 < guus> Je moet controleren dat niet twee keer hetzelfde mailtje ++ verwerkt wordt. ++ Fri 13 13:59 < joostvb> hrm, ik schat zo in dat SSLS niet kwetsbaarderder is ++ voor die aanvallen dan OpenPGP en S/MIME ++ Fri 13 13:59 < guus> OpenPGP en S/MIME zijn protocollen, SSLS is een listserver. ++ Fri 13 14:00 < joostvb> guus: hrm, mensen kunnen toch een oude post bouncen ++ naar een lijst ++ Fri 13 14:00 < joostvb> guus: ik weet eigenlijk niet wat dan het beste is ++ Fri 13 14:01 < guus> Met OpenPGP is het iig zo dat er een uniek nummertje in ++ elke signature zit. ++ Fri 13 14:01 < joostvb> hm hm hm ++ Fri 13 14:01 < guus> Dus je moet die nummertjes onthouden. ++ Fri 13 14:01 < joostvb> misschien dat er inderdaad wel iets in zit ja, en dat ++ je zo'n bounce wilt weigeren ++ Fri 13 14:01 < guus> Zeg, een dag ofzo. En als je mailtjes krijgt wiens ++ signature ouder is dan een dag, dan moet je die sowieso ++ bouncen. ++ ++ Thanks Guus Sliepen for bug report. ++ ++#0069 We choke on keys with subkeys for signing. E.g, when uploading a key ++ ++ pub 4096R/0B86B067 2005-10-12 ++ Key fingerprint = B8FA C2E2 5047 5B8C E940 A919 5793 0DAB 0B86 B067 ++ uid Joost E. van Baal (Nederland, 1970) ++ uid Joost van Baal ++ sub 4096R/24525E9E 2005-10-12 [expires: 2008-10-11] ++ sub 4096R/43FF7C14 2005-10-12 [expires: 2008-10-11] ++ ++ it is stored as 0x0b86b067. However, when verifying the signature on a post, ++ 0x24525e9e is found. The test wether the signature is from a member fails on this. ++ ++ ++ ++
+ Your Subscription Options +