'] = (
mlist.FormatButton('unsub', _('Unsubscribe')) + '
' +
CheckBox('unsubconfirm', 1, checked=0).Format() +
--- /usr/local/mm21b5-clean/Mailman/Gui/SecureList.py Sun Nov 24 06:38:38 2002
+++ /usr/local/mailman/Mailman/Gui/SecureList.py Tue Nov 26 02:20:09 2002
@@ -0,0 +1,93 @@
+# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""MailList mixin class managing the Secure List options.
+"""
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import Errors
+from Mailman.i18n import _
+from Mailman.Gui.GUIBase import GUIBase
+
+WIDTH = mm_cfg.TEXTFIELDWIDTH
+
+
+class SecureList(GUIBase):
+ def GetConfigCategory(self):
+ return 'secure', _('Secure List options')
+
+ def GetConfigInfo(self, mlist, category, subcat):
+ if category <> 'secure':
+ return None
+ return[
+ _('Secure List options.'),
+
+ ('secure', mm_cfg.Toggle, ('No', 'Yes'), 0,
+ _('Will the list use PGP?'),
+ _('''If the list uses PGP you can control if messages sent to the
+ list unencrypted are rejected (and the sender notified) or encrypted
+ with list's public key.''')),
+
+ ('reject_unencrypted_mail', mm_cfg.Toggle, ('No', 'Yes'), 0,
+ _('If messages come in unencrypted should we reject them?'),
+ _('''If message come to the list and they are not encrypted
+ do we reject them and send notification back to the user or
+ do we accept them and encrypt them with the list\'s key?''')),
+
+ ('suppres_subject', mm_cfg.Toggle, ('No', 'Yes'), 0,
+ _('Remove the subject line from all email?'),
+ _('''When yes, the subject line will be removed from all emails
+ to the list, leaving only the subject prefix.''')),
+
+ ('pgp_ring', mm_cfg.Text, (7, WIDTH), 0,
+ _('All the public keys for the list'),
+ _('''All the public keys you want your list users to be aware of
+ should be placed here in a single keyring. You must include a
+ public key for the mailing list entry address if you want
+ incoming unencrypted message to be automatically encrypted.''')),
+
+ ('accept_message', mm_cfg.Text, (5, WIDTH), 0,
+ _('Reminder text added to unencrypted mail.'),
+ _('''If we accept unencrypted messages, we can add a reminder
+ message to it before it is encrypted and sent out.''')),
+
+ ('reject_message', mm_cfg.Text, (5, WIDTH), 0,
+ _('Rejected messages will have this message.'),
+ _('''If we reject unencrypted messages the message body will be
+ replaced with this message''')),
+
+ ('_default_secure_settings', mm_cfg.Toggle, ('No', 'Yes'), 0,
+ _('Reset the list settings to the PGP defaults?'),
+ _('''The default setting for a list using PGP are: unencrypted
+ messages are rejected, digests are not allowed, no
+ reminders, the list is not advertised, subscribing requires
+ a list admin's approval, only list members can see the list
+ members, no archives are kept, and the list admin is notified of
+ any changes to list membership.''')),
+ ]
+
+ def _setValue(self, mlist, property, val, doc):
+ # Watch for the special, immediate action attributes
+ if property == '_default_secure_settings' and val:
+ mlist.TightenList()
+ else:
+ GUIBase._setValue(self, mlist, property, val, doc)
+
+ def getValue(self, mlist, kind, varname, params):
+ if varname <> 'pgp_ring':
+ mlist.ring_changed = 1
+
--- /usr/local/mm21b5-clean/Mailman/Gui/__init__.py Sun Nov 24 06:36:48 2002
+++ /usr/local/mailman/Mailman/Gui/__init__.py Tue Nov 26 02:20:09 2002
@@ -23,6 +23,7 @@
from NonDigest import NonDigest
from Passwords import Passwords
from Privacy import Privacy
+from SecureList import SecureList
from Topics import Topics
from Usenet import Usenet
from Language import Language
--- /usr/local/mm21b5-clean/Mailman/Commands/cmd_set.py Sun Nov 24 06:36:49 2002
+++ /usr/local/mailman/Mailman/Commands/cmd_set.py Tue Nov 26 02:20:09 2002
@@ -34,6 +34,7 @@
settings.
""")
+global DETAILS
DETAILS = _("""
set help
Show this detailed help.
@@ -95,6 +96,15 @@
reminder for this mailing list.
""")
+PGP_DETAILS="""
+ set pgpattach on
+ set pgpattach off
+ Use 'set pgpattach on' to receive encryped messages from a secure
+ mailing list as a pgp-mime attachment instead of in the body of the
+ emails. This will allow you to receive PGP messages in the format you
+ like, independent of how it was sent.
+"""
+
_ = i18n._
STOP = 1
@@ -113,18 +123,27 @@
def process(self, res, args):
if not args:
+ if res.mlist.IsListSecure():
+ global DETAILS
+ DETAILS += PGP_DETAILS
res.results.append(_(DETAILS))
return STOP
subcmd = args.pop(0)
methname = 'set_' + subcmd
method = getattr(self, methname, None)
if method is None:
+ if res.mlist.IsListSecure():
+ global DETAILS
+ DETAILS += PGP_DETAILS
res.results.append(_('Bad set command: %(subcmd)s'))
res.results.append(_(DETAILS))
return STOP
return method(res, args)
def set_help(self, res, args=1):
+ if res.mlist.IsListSecure():
+ global DETAILS
+ DETAILS += PGP_DETAILS
res.results.append(_(DETAILS))
if args:
return STOP
@@ -201,6 +220,10 @@
# sense is reversed
onoff = (not opt) and _('on') or _('off')
res.results.append(_(' reminders %(onoff)s'))
+ opt = mlist.getMemberOption(address, mm_cfg.ReceivePGPAsAttachment)
+ # sense NOT is reversed
+ onoff = (opt) and _('on') or _('off')
+ res.results.append(_(' pgpattach %(onoff)s'))
def set_authenticate(self, res, args):
mlist = res.mlist
@@ -344,7 +367,17 @@
not status)
res.results.append(_('reminder option set'))
-
+ def set_pgpattach(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ status = self._status(res, args[0])
+ if status < 0:
+ return STOP
+ # sense is NOT reversed
+ mlist.setMemberOption(self.__address, mm_cfg.ReceivePGPAsAttachment,
+ status)
+ res.results.append(_('pgpattach option set'))
def process(res, args):
# We need to keep some state between set commands
--- /usr/local/mm21b5-clean/Mailman/SecureList.py Sun Nov 24 06:38:38 2002
+++ /usr/local/mailman/Mailman/SecureList.py Tue Nov 26 02:20:09 2002
@@ -0,0 +1,306 @@
+# -*- python -*-
+
+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"Class for the added functionality of Secure Mailing Lists"
+
+import string, copy, re, os
+import email, mimetools
+import Message, Utils, mm_cfg
+import PGPClass, PGPErrors
+import SecureListHTMLFormat
+from i18n import _
+
+#This class hold all functions dealing with secure lists
+class SecureList (PGPClass.GPGMail,
+ SecureListHTMLFormat.SecureListHTMLFormat):
+ def __init__ (self):
+ try:
+ type (self.secure)
+ except:
+ self.secure=0
+
+ self.suppres_subject = 0
+ self.ring_changed = 0
+ self.reject_message = ''
+ self.accept_message = ''
+ self.reject_unencrypted_mail = 1
+ self.pgp_ring = ''
+ self.import_admin_alert = ''
+ self.import_user_alert = ''
+
+ def TightenList (self):
+ self.digestable = 0
+ self.send_reminders = 0
+ self.advertised = 0
+ self.subscribe_policy = 2 #"require approval"
+ self.private_roster = 1 #"List members only"
+ self.member_posting_only = 1
+ self.archive = 0
+ self.admin_notify_mchanges = 1 #admin gets notices of (un)subscribes
+ self.reject_unencrypted_mail = 1
+ self.accept_message = mm_cfg.PGP_MSG_ACCEPT
+ self.reject_message = mm_cfg.PGP_MSG_REJECT
+
+ non_digest = os.path.join(self.GetScriptURL('admin'), 'nondigest')
+ pgp = os.path.join(self.GetScriptURL('admin'), 'pgp')
+
+ self.import_admin_alert = mm_cfg.PGP_IMPORT_ADMIN_ALERT %(non_digest,
+ pgp)
+ self.import_user_alert = mm_cfg.PGP_IMPORT_USER_ALERT
+
+ ###################
+ #If PGP fails, either with no PGP data OR if the public key not found on
+ #the key ring, send notification emails to the list admins and users
+ ###################
+ def PGPFailedNotifyAdminsAndUsers(self):
+ msg = Message.UserNotification(
+ self.owner, self.GetOwnerEmail(),
+ _('List %s Disabled' %self.real_name),
+ self.import_admin_alert)
+ msg.send(self)
+
+ msg = Message.UserNotification(
+ self.getRegularMemberKeys(), self.GetListEmail(),
+ _('List %s Disabled' %self.real_name),
+ self.import_user_alert)
+ msg.send(self)
+
+ ##################
+ #Suppress Subject
+ #Remove the subject
+ ##################
+ def SuppressSubject(self, msg):
+ new_headers = []
+ for h in msg._headers:
+ if string.lower(h[0][:7]) == 'subject':
+ new_headers.append(("Subject", ''))
+ else:
+ new_headers.append(h)
+ msg._headers = new_headers
+ return msg
+
+ ###################
+ #Regularize Message
+ #Change the Line Feeds to Control Return/Line Feeds
+ ###################
+ def RegularizeMessage(self, msg):
+ msg._payload = email.Utils.fix_eols(msg._payload)
+
+ ###################
+ #Create a Body Mail
+ #Create a deep copy of the message object passed in & modifies it
+ #so the PGP is the body of the message
+ ###################
+ def CreateBodyMail(self, msg):
+ new_msg = copy.deepcopy(msg)
+ new_msg._payload = self.ExtractMessage(str(msg._payload)) + "\n"
+ new_msg._headers = self.CreateBodyHeaders(msg)
+
+ return new_msg
+
+ ###################
+ #Create the Header for the message with PGP Message in the body
+ ###################
+ def CreateBodyHeaders(self, msg):
+ new_headers = []
+ Content_Length = str(len(msg._payload))
+ Lines = str(str(msg._payload).count('\n'))
+ Content_Type = "TEXT/PLAIN; charset=%s" \
+ %Utils.GetCharSet(self.preferred_language)
+ for h in msg._headers:
+ if string.lower(h[0][:8]) == 'content-':
+ if string.lower(h[0][:14]) == 'content-length':
+ new_headers.append(("Content-Length", Content_Length))
+ if string.lower(h[0][:5]) == 'lines':
+ new_headers.append(("Lines", Lines))
+ if string.lower(h[0][:12]) == 'content-type':
+ new_headers.append(("Content-Type", Content_Type))
+ #throw away boundary line
+ elif string.find(string.lower(h[0]),"boundary") is not -1:
+ continue
+ else:
+ new_headers.append(h)
+ return new_headers
+
+ ###################
+ #Create an Attachment Mail
+ #Creates a deep copy of the message object passed in & modifies it,
+ #so the PGP Message is an attachment to the mail
+ ###################
+ def CreateAttachMail(self, msg):
+ new_msg = copy.deepcopy(msg)
+ boundary = mimetools.choose_boundary()
+
+ new_msg._payload = self.CreateAttachPayload(msg, boundary)
+ new_msg._headers = self.CreateAttachHeaders(msg, boundary)
+
+ #this is an ugly hack to dump the text repr of the msg back
+ #into email module to create stardardized object
+ text_msg = ''
+
+ for k, v in new_msg._headers:
+ text_msg += k + ": " + v + "\n"
+ text_msg += "\n" + new_msg._payload
+ new_msg = email.message_from_string(text_msg, Message.Message)
+ return new_msg
+
+ ##############
+ #Create Payload for PGP Attachment Mail
+ ##############
+ def CreateAttachPayload(self, msg, boundary):
+ msg_payload = self.ExtractMessage(str(msg._payload))
+ new_payload = """
+--%s
+Content-Type: application/pgp-encrypted
+Content-Disposition: inline; filename="msg.asc"
+
+Version: 1
+
+--%s
+Content-Type: application/octet-stream
+Content-Disposition: inline
+
+%s
+
+--%s--
+""" % (boundary, boundary, msg_payload, boundary)
+ return new_payload
+
+ ##############
+ #Create Header for PGP Attachment Mail
+ ##############
+ def CreateAttachHeaders(self, msg, boundary):
+ new_headers = []
+ Content_Length = str(len(msg._payload))
+ Lines = str(str(msg._payload).count('\n'))
+ Content_Type = "multipart/encrypted; "+ \
+ 'protocol="application/pgp-encrypted";' + \
+ '\n\tboundary="%s"' %boundary
+ for h in msg._headers:
+ if string.lower(h[0][:8]) == 'content-':
+ if string.lower(h[0][:14]) == 'content-length':
+ new_headers.append(("Content-Length", Content_Length))
+ if string.lower(h[0][:5]) == 'lines':
+ new_headers.append(("Lines", Lines))
+ if string.lower(h[0][:12]) == 'content-type':
+ new_headers.append(("Content-Type", Content_Type))
+ #throw away boundary line
+ elif string.find(string.lower(h[0]),"boundary") is not -1:
+ continue
+ else:
+ new_headers.append(h)
+ return new_headers
+
+ #############
+ #Create list of users who want to recieve PGP Block in the body
+ #############
+ def PGPBodyRecipients (self):
+ def fil_pgp_body (x, s=self, v=mm_cfg.ReceivePGPAsAttachment):
+ return not s.getMemberOption(x,v)
+ return filter(fil_pgp_body,self.getRegularMemberKeys())
+
+ #############
+ #Create list of users who want to recieve PGP Block as an attachment
+ #############
+ def PGPAttachRecipients(self):
+ def fil_pgp_attach (x, s=self, v=mm_cfg.ReceivePGPAsAttachment):
+ return s.getMemberOption(x,v)
+ return filter(fil_pgp_attach,self.getRegularMemberKeys())
+
+ #############
+ #Check if the list is disabled
+ #############
+ def IsListDisabled(self):
+ if not self.nondigestable and not self.digestable:
+ return 1
+ else:
+ return 0
+
+ ################
+ #Disable the list by turning off delivery to the digest and regular members
+ ################
+ def DisableList(self):
+ self.nondigestable = 0
+ self.digestable = 0
+
+ ################
+ #Enable the list by turning on delivery to regular members
+ ################
+ def EnableList(self):
+ self.nondigestable = 1
+
+ ############################
+ #Checks whether the list handles secure traffic
+ ###########################
+ def IsListSecure(self):
+ try:
+ if self.secure: return 1
+ else: return 0
+ except:
+ return 0
+
+ #############################
+ #This function checks for an even number of PGP Messege Beginning & Endings
+ ##############################
+ def IsEncryptedMessage(self, unknown_string):
+ num_enc = string.count(unknown_string, PGPClass.BEGIN_PGP_MSG)
+ if num_enc == 0:
+ return None
+ #If the number of BEGIN_PGP_MSG & END_PGP_MSG are not the same
+ if num_enc != string.count(unknown_string, PGPClass.END_PGP_MSG):
+ return None
+ else:
+ return 1
+
+ #############################
+ #This function checks for an even number of PGP Pub Key Beginning & Endings
+ ###############################
+ def IsEncryptedPubRing(self, unknown_string):
+ num_enc = string.count(unknown_string, PGPClass.BEGIN_PGP_PUB_RING)
+ if num_enc == 0:
+ return None
+ if num_enc != string.count(unknown_string, PGPClass.END_PGP_PUB_RING):
+ return None
+ else:
+ return 1
+
+ #############################
+ #This function will extract only the first PGP Message
+ ###############################
+ def ExtractMessage(self, combined_string):
+ if string.count(combined_string, PGPClass.BEGIN_PGP_MSG) == 0:
+ raise PGPErrors.NoMessageFoundError("No Message beginning was found")
+
+ msg = string.split(combined_string, PGPClass.BEGIN_PGP_MSG)
+ msg = string.split(msg[1], PGPClass.END_PGP_MSG)
+ msg = PGPClass.BEGIN_PGP_MSG + msg[0] + PGPClass.END_PGP_MSG
+ return msg
+
+ #############################
+ #This function will extract only the first PGP keyring
+ ###############################
+ def ExtractPublicKey(self, combined_string):
+ if string.count(combined_string, PGPClass.BEGIN_PGP_PUB_RING) == 0:
+ raise PGPErrors.NoKeyFoundError("No PGP key information was found")
+
+ key = string.split(combined_string, PGPClass.BEGIN_PGP_PUB_RING)
+ key = string.split(key[1], PGPClass.END_PGP_PUB_RING)
+ key = PGPClass.BEGIN_PGP_PUB_RING + key[0] + PGPClass.END_PGP_PUB_RING
+ return key
+
+
--- /usr/local/mm21b5-clean/Mailman/SecureListHTMLFormat.py Sun Nov 24 06:38:38 2002
+++ /usr/local/mailman/Mailman/SecureListHTMLFormat.py Tue Nov 26 02:20:09 2002
@@ -0,0 +1,26 @@
+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"Class for Formatting Options for all PGP modications to the Web UI"
+
+from Mailman.htmlformat import *
+from Mailman.i18n import _
+
+class SecureListHTMLFormat:
+ def FormatPgpRingBox(self):
+ text = _('All public keys related to the list are on this keyring.')
+ ta = TextArea('ring', self.pgp_ring, 10, 68, wrap=None)
+ return text + '
' + ''
--- /usr/local/mm21b5-clean/Mailman/PGP.py Sun Nov 24 06:38:38 2002
+++ /usr/local/mailman/Mailman/PGP.py Tue Nov 26 02:20:09 2002
@@ -0,0 +1,299 @@
+#COPYRIGHT:
+#
+#Copyright (C) 2001 Frank J. Tobin, ftobin@neverending.org
+#LICENSE:
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Lesser General Public
+#License as published by the Free Software Foundation; either
+#
+#This library 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
+#Lesser General Public License for more details.
+#
+#You should have received a copy of the GNU Lesser General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#or see http://www.gnu.org/copyleft/lesser.html
+
+"The direct calls to gpg or the pgp executable"
+
+import os
+import sys
+import fcntl, FCNTL
+
+from Mailman import mm_cfg
+
+# "standard" filehandles attached to processes
+_stds = [ 'stdin', 'stdout', 'stderr' ]
+
+# the permissions each type of file handle needs to be opened with
+_fd_modes = { 'stdin': 'w',
+ 'stdout': 'r',
+ 'stderr': 'r',
+ 'passphrase': 'w',
+ 'command': 'w',
+ 'logger': 'r',
+ 'status': 'r'
+ }
+
+# correlation between handle names and the arguments we'll pass
+_fd_options = { 'passphrase': '--passphrase-fd',
+ 'logger': '--logger-fd',
+ 'status': '--status-fd',
+ 'command': '--command-fd' }
+
+class GnuPG:
+ """Class instances represent GnuPG.
+
+ Instance attributes of a GnuPG object are:
+
+ * call -- string to call GnuPG with. Defaults to "gpg"
+
+ * passphrase -- Since it is a common operation
+ to pass in a passphrase to GnuPG,
+ and working with the passphrase filehandle mechanism directly
+ can be mundane, if set, the passphrase attribute
+ works in a special manner. If the passphrase attribute is set,
+ and no passphrase file object is sent in to run(),
+ then the GnuPG instance will take care of sending the passphrase to
+ GnuPG, the executable instead of having the user send it in manually.
+
+ * options -- Object of type GnuPGInterface.Options.
+ Attribute-setting in options determines
+ the command-line options used when calling GnuPG.
+ """
+ def __init__(self):
+ self.call = mm_cfg.PGP_CMD
+ self.passphrase = None
+ self.options = Options()
+
+ def run(self, gnupg_commands, args=None, create_fhs=None, attach_fhs=None):
+ if args == None: args = []
+ if create_fhs == None: create_fhs = []
+ if attach_fhs == None: attach_fhs = {}
+
+ for std in _stds:
+ if not attach_fhs.has_key(std) \
+ and std not in create_fhs:
+ attach_fhs.setdefault(std, getattr(sys, std))
+
+ handle_passphrase = 0
+
+ if self.passphrase != None \
+ and not attach_fhs.has_key('passphrase') \
+ and 'passphrase' not in create_fhs:
+ handle_passphrase = 1
+ create_fhs.append('passphrase')
+
+ process = self._attach_fork_exec(gnupg_commands, args,
+ create_fhs, attach_fhs)
+
+ if handle_passphrase:
+ passphrase_fh = process.handles['passphrase']
+ passphrase_fh.write( self.passphrase )
+ passphrase_fh.close()
+ del process.handles['passphrase']
+
+ return process
+
+ def _attach_fork_exec(self, gnupg_commands, args, create_fhs, attach_fhs):
+ """This is like run(), but without the passphrase-helping
+ (note that run() calls this)."""
+
+ process = Process()
+
+ for fh_name in create_fhs + attach_fhs.keys():
+ if not _fd_modes.has_key(fh_name):
+ raise KeyError, \
+ "unrecognized filehandle name '%s'; must be one of %s" \
+ % (fh_name, _fd_modes.keys())
+
+ for fh_name in create_fhs:
+ # make sure the user doesn't specify a filehandle
+ # to be created *and* attached
+ if attach_fhs.has_key(fh_name):
+ raise ValueError, \
+ "cannot have filehandle '%s' in both create_fhs and attach_fhs" \
+ % fh_name
+
+ pipe = os.pipe()
+ # fix by drt@un.bewaff.net noting
+ # that since pipes are unidirectional on some systems,
+ # so we have to 'turn the pipe around'
+ # if we are writing
+ if _fd_modes[fh_name] == 'w': pipe = (pipe[1], pipe[0])
+ process._pipes[fh_name] = Pipe(pipe[0], pipe[1], 0)
+
+ for fh_name, fh in attach_fhs.items():
+ process._pipes[fh_name] = Pipe(fh.fileno(), fh.fileno(), 1)
+
+ process.pid = os.fork()
+
+ if process.pid == 0: self._as_child(process, gnupg_commands, args)
+ return self._as_parent(process)
+
+ def _as_parent(self, process):
+ """Stuff run after forking in parent"""
+ for k, p in process._pipes.items():
+ if not p.direct:
+ os.close(p.child)
+ process.handles[k] = os.fdopen(p.parent, _fd_modes[k])
+
+ # user doesn't need these
+ del process._pipes
+
+ return process
+
+
+ def _as_child(self, process, gnupg_commands, args):
+ """Stuff run after forking in child"""
+ # child
+ for std in _stds:
+ p = process._pipes[std]
+ os.dup2( p.child, getattr(sys, "__%s__" % std).fileno() )
+
+ for k, p in process._pipes.items():
+ if p.direct and k not in _stds:
+ # we want the fh to stay open after execing
+ fcntl.fcntl( p.child, FCNTL.F_SETFD, 0 )
+
+ fd_args = []
+
+ for k, p in process._pipes.items():
+ # set command-line options for non-standard fds
+ if k not in _stds:
+ fd_args.extend([ _fd_options[k], "%d" % p.child ])
+
+ if not p.direct: os.close(p.parent)
+
+ command = [ self.call ] + fd_args + self.options.get_args() \
+ + gnupg_commands + args
+
+ os.execvp( command[0], command )
+
+class Pipe:
+ """simple struct holding stuff about pipes we use"""
+ def __init__(self, parent, child, direct):
+ self.parent = parent
+ self.child = child
+ self.direct = direct
+
+class Options:
+ def __init__(self):
+ # booleans
+ self.armor = 0
+ self.no_greeting = 0
+ self.verbose = 0
+ self.no_verbose = 0
+ self.quiet = 0
+ self.batch = 0
+ self.always_trust = 0
+ self.rfc1991 = 0
+ self.openpgp = 0
+ self.force_v3_sigs = 0
+ self.no_options = 0
+ self.textmode = 0
+
+ # meta-option booleans
+ self.meta_pgp_5_compatible = 0
+ self.meta_pgp_2_compatible = 0
+ self.meta_interactive = 1
+
+ # strings
+ self.homedir = None
+ self.default_key = None
+ self.comment = None
+ self.compress_algo = None
+ self.options = None
+
+ # lists
+ self.encrypt_to = []
+ self.recipients = []
+
+ # miscellaneous arguments
+ self.extra_args = []
+
+ def get_args( self ):
+ """Generate a list of GnuPG arguments based upon attributes."""
+
+ return self.get_meta_args() + self.get_standard_args() + self.extra_args
+
+ def get_standard_args( self ):
+ """Generate a list of standard, non-meta or extra arguments"""
+ args = []
+ if self.homedir != None: args.extend( [ '--homedir', self.homedir ] )
+ if self.options != None: args.extend( [ '--options', self.options ] )
+ if self.comment != None: args.extend( [ '--comment', self.comment ] )
+ if self.compress_algo != None: args.extend( [ '--compress-algo', self.compress_algo ] )
+ if self.default_key != None: args.extend( [ '--default-key', self.default_key ] )
+
+ if self.no_options: args.append( '--no-options' )
+ if self.armor: args.append( '--armor' )
+ if self.textmode: args.append( '--textmode' )
+ if self.no_greeting: args.append( '--no-greeting' )
+ if self.verbose: args.append( '--verbose' )
+ if self.no_verbose: args.append( '--no-verbose' )
+ if self.quiet: args.append( '--quiet' )
+ if self.batch: args.append( '--batch' )
+ if self.always_trust: args.append( '--always-trust' )
+ if self.force_v3_sigs: args.append( '--force-v3-sigs' )
+ if self.rfc1991: args.append( '--rfc1991' )
+ if self.openpgp: args.append( '--openpgp' )
+
+ for r in self.recipients: args.extend( [ '--recipient', r ] )
+ for r in self.encrypt_to: args.extend( [ '--encrypt-to', r ] )
+
+ return args
+
+ def get_meta_args( self ):
+ """Get a list of generated meta-arguments"""
+ args = []
+
+ if self.meta_pgp_5_compatible: args.extend( [ '--compress-algo', '1',
+ '--force-v3-sigs'
+ ] )
+ if self.meta_pgp_2_compatible: args.append( '--rfc1991' )
+ if not self.meta_interactive: args.extend( [ '--batch', '--no-tty' ] )
+
+ return args
+
+
+class Process:
+ """Objects of this class encompass properties of a GnuPG
+ process spawned by GnuPG.run().
+
+ # gnupg is a GnuPG object
+ process = gnupg.run( [ '--decrypt' ], stdout = 1 )
+ out = process.handles['stdout'].read()
+ ...
+ os.waitpid( process.pid, 0 )
+
+ Data Attributes
+
+ handles -- This is a map of filehandle-names to
+ the file handles, if any, that were requested via run() and hence
+ are connected to the running GnuPG process. Valid names
+ of this map are only those handles that were requested.
+
+ pid -- The PID of the spawned GnuPG process.
+ Useful to know, since once should call
+ os.waitpid() to clean up the process, especially
+ if multiple calls are made to run().
+ """
+
+ def __init__(self):
+ self._pipes = {}
+ self.handles = {}
+ self.pid = None
+ self._waited = None
+
+ def wait(self):
+ """Wait on the process to exit, allowing for child cleanup.
+ Will raise an IOError if the process exits non-zero."""
+
+ e = os.waitpid(self.pid, 0)[1]
+ if e != 0:
+ raise IOError, "GnuPG exited non-zero, with code %d" % (e << 8)
+
--- /usr/local/mm21b5-clean/Mailman/PGPClass.py Sun Nov 24 06:38:38 2002
+++ /usr/local/mailman/Mailman/PGPClass.py Tue Nov 26 02:20:09 2002
@@ -0,0 +1,116 @@
+# -*- python -*-
+
+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"Class abstraction of PGP"
+
+import sys
+import string
+import re
+import os
+
+import PGP
+import PGPErrors
+
+BEGIN_PGP_MSG = "-----BEGIN PGP MESSAGE-----"
+END_PGP_MSG = "-----END PGP MESSAGE-----"
+
+BEGIN_PGP_PUB_RING = "-----BEGIN PGP PUBLIC KEY BLOCK-----"
+END_PGP_PUB_RING = "-----END PGP PUBLIC KEY BLOCK-----"
+
+class GPGMail(PGP.GnuPG):
+
+ ####################
+ #This function is called a initialization time.
+ #It justs calls PGP.GnuPG.__init__(self) and self.SetOptions
+ ####################
+ def __init__(self, options={}):
+ PGP.GnuPG.__init__(self)
+ self.SetOptions(options)
+
+ ####################
+ #This function is called at initialization time. It sets the options for
+ #the GPG execution
+ ####################
+ def SetOptions(self, options = {}):
+ self.options.armor = 1
+ self.options.meta_interactive = 0
+ self.options.extra_args.append('--no-secmem-warning')
+
+ if options.has_key('homedir'): self.options.homedir = \
+ (options.get('homedir'))
+ if options.has_key('no-default-keyring'): \
+ self.options.extra_args.append('--no-default-key')
+ if options.has_key('ignore-time-conflict'): \
+ self.options.extra_args.append('--ignore-time-conflict')
+ if options.has_key('keyring'):
+ self.options.extra_args.append('--keyring')
+ self.options.extra_args.append(options.get('keyring'))
+
+ ###############################
+ #This function will encrypt a string to a list of recipients and
+ #will either return the PGP Message, raise PGPErrors.PublicKeyNotFoundError
+ #or one of the PGPErrors. Will raise UnknownGPGError when things are really
+ #wrong.
+ ###############################
+ def EncryptString(self, uncrypted_string, recipients):
+ self.options.recipients = recipients
+ self.options.always_trust = 1
+
+ proc = self.run(['--encrypt'],
+ create_fhs=['stdin', 'stdout', 'stderr'])
+
+ proc.handles['stdin'].write(uncrypted_string)
+ proc.handles['stdin'].close()
+ output = proc.handles['stdout'].read()
+ proc.handles['stdout'].close()
+ error = proc.handles['stderr'].read()
+ proc.handles['stdout'].close()
+
+ try:
+ proc.wait()
+ except IOError:
+ result = re.search("public key not found", error)
+ if result:
+ raise PGPErrors.PublicKeyNotFoundError(error)
+ else:
+ raise PGPErrors.UnknownGPGError(error)
+
+ return output
+
+ #############################
+ #This Function adds a public key ring to the lists' keyring.
+ #It returns nothing if successful and raises an error if not.
+ ###############################
+ def ImportPublicKey(self, ring):
+ self.options.verbose = 1
+
+ proc = self.run(['--import'], create_fhs=['stdin', 'stdout', 'stderr'])
+
+ proc.handles['stdin'].write(ring)
+ proc.handles['stdin'].close()
+ output = proc.handles['stdout'].read()
+ proc.handles['stdout'].close()
+ error = proc.handles['stderr'].read()
+ proc.handles['stderr'].close()
+
+ try:
+ proc.wait()
+ except IOError:
+ raise PGPErrors.NoValidDataFoundError(error)
+ else:
+ return output
--- /usr/local/mm21b5-clean/Mailman/PGPErrors.py Sun Nov 24 06:38:38 2002
+++ /usr/local/mailman/Mailman/PGPErrors.py Tue Nov 26 02:20:09 2002
@@ -0,0 +1,74 @@
+# -*- python -*-
+
+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import sys
+
+"Error classes raised by PGPClass"
+
+########################
+#This is the base Error class.
+########################
+class Error (Exception):
+ pass
+
+#########################
+#This Error is raised when the Public Key is not found.
+#This is should usually be called when trying to encrypt text to a
+#recipient that we don't have the public key for.
+#########################
+class PublicKeyNotFoundError (Error):
+ def __init__(self, value):
+ self.ErrorMessage = value
+
+
+########################
+#This is the Generic GPG error.
+#This is just here so we can catch errors that are none of the above.
+########################
+class UnknownGPGError (Error):
+ def __init__(self, value):
+ self.ErrorMessage = value
+
+#########################
+#This Error Should raised when no valid public key data is found.
+#This should be called when AddPublicRing is not giving valid data.
+#########################
+class NoValidDataFoundError (Error):
+ def __init__(self, value):
+ self.ErrorMessage = value
+
+
+#########################
+#This Error is raised when no Message is found in a text body
+#This should be called when ExtractMessage is given a string that doesn't
+#contain a message.
+#########################
+class NoMessageFoundError (Error):
+ def __init__(self, value):
+ self.ErrorMessage = value
+
+#########################
+#This Error Should raised when no Key is found in a text body
+#This should be called when ExtractPublicKey is given a string that doesn't
+#contain a key.
+#########################
+class NoKeyFoundError (Error):
+ def __init__(self, value):
+ self.ErrorMessage = value
+
+
--- /usr/local/mm21b5-clean/scripts/post Sun Nov 24 06:36:49 2002
+++ /usr/local/mailman/scripts/post Tue Nov 26 02:20:09 2002
@@ -29,6 +29,7 @@
import sys
import paths
+from Mailman import MailList
from Mailman import mm_cfg
from Mailman import Utils
from Mailman.i18n import _
@@ -58,10 +59,24 @@
# some MTAs have a hard limit to the time a filter prog can run. Postfix
# is a good example; if the limit is hit, the proc is SIGKILL'd giving us
# no chance to save the message.
- inq = get_switchboard(mm_cfg.INQUEUE_DIR)
- inq.enqueue(sys.stdin.read(),
- listname=listname,
- tolist=1, _plaintext=1)
+ try:
+ mlist = MailList.MailList(listname, lock=0)
+ except Errors.MMListError, e:
+ sys.stderr.write('Mailman error: post got bad listname: %s\n%s' %
+ (listname, e))
+ sys.exit(1)
+
+ #Redirect Message to a secure list to the secure runner
+ if mlist.IsListSecure():
+ inq = get_switchboard(mm_cfg.SECUREQUEUE_DIR)
+ inq.enqueue(sys.stdin.read(),
+ listname=listname,
+ tosecure=1, _plaintext=1)
+ else:
+ inq = get_switchboard(mm_cfg.INQUEUE_DIR)
+ inq.enqueue(sys.stdin.read(),
+ listname=listname,
+ tolist=1, _plaintext=1)
--- /usr/local/mm21b5-clean/templates/en/listinfo.html Sun Nov 24 06:36:53 2002
+++ /usr/local/mailman/templates/en/listinfo.html Tue Nov 26 02:20:09 2002
@@ -138,6 +138,11 @@
+
+
+
+ |
+