From a4df7e3989cce6bdc8e30badc53e88ccb59cb429 Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Sat, 21 May 2011 14:52:28 +0200 Subject: [PATCH] try to properly handle some more mime stuff. - use email module instead of deprecated mimetools and multifile modules - changes: sigcheck ud-echelon ud-mailgate userdir_gpg.py - move ud-echelon and sigcheck to GPGCheckSig2 interface. --- debian/changelog | 6 ++- sigcheck | 36 ++++++-------- ud-echelon | 36 +++++++------- ud-mailgate | 29 ++++------- userdir_gpg.py | 122 ++++++++++++++++++++--------------------------- 5 files changed, 99 insertions(+), 130 deletions(-) diff --git a/debian/changelog b/debian/changelog index 8ad08cd..874c268 100644 --- a/debian/changelog +++ b/debian/changelog @@ -17,8 +17,12 @@ userdir-ldap (0.3.7X) Xnstable; urgency=low - Do not mess with sudo passwords if nothing changed. * templates/change-reply: say a word about subjects in mail to admin@db. * move gpgwrapper to unmaintained/ - it is now using obsolete interfaces. + * try to properly handle some more mime stuff. + - use email module instead of deprecated mimetools and multifile modules + - changes: sigcheck ud-echelon ud-mailgate userdir_gpg.py + * move ud-echelon and sigcheck to GPGCheckSig2 interface. - -- Peter Palfrader Sat, 21 May 2011 14:49:52 +0200 + -- Peter Palfrader Sat, 21 May 2011 14:50:32 +0200 userdir-ldap (0.3.78) unstable; urgency=low diff --git a/sigcheck b/sigcheck index 1ba2a8d..fc16d1f 100755 --- a/sigcheck +++ b/sigcheck @@ -26,6 +26,7 @@ import sys, traceback, time, os; import pwd, getopt; +import email, email.parser from userdir_gpg import *; EX_TEMPFAIL = 75; @@ -124,17 +125,16 @@ try: if ReplayCacheFile != None: ErrMsg = "Failed to initialize the replay cache:"; RC = ReplayCache(ReplayCacheFile); - RC.Clean(); - + # Get the email ErrType = EX_PERMFAIL; ErrMsg = "Failed to understand the email or find a signature:"; - Email = mimetools.Message(sys.stdin,0); - MsgID = Email.getheader("Message-ID"); + email = email.parser.Parser().parse(sys.stdin); + MsgID = email["Message-ID"] + print "Inspecting message %s"%MsgID; verbmsg("Processing message %s" % MsgID) - Msg = GetClearSig(Email,1); - # print Msg + Msg = GetClearSig(email,1); if AllowMIME == 0 and Msg[1] != 0: raise Error, "PGP/MIME disallowed"; @@ -144,34 +144,28 @@ try: # Check the signature ErrMsg = "Unable to check the signature or the signature was invalid:"; - Res = GPGCheckSig(Msg[0]); + pgp = GPGCheckSig2(Msg[0]) - if Res[0] != None: - raise Error, Res[0]; - - if Res[3] == None: - raise Error, "Null signature text"; + if not pgp.ok: + raise UDFormatError, pgp.why + if pgp.text is None: + raise UDFormatError, "Null signature text" # Check the signature against the replay cache if ReplayCacheFile != None: - ErrMsg = "The replay cache rejected your message. Check your clock!"; - Rply = RC.Check(Res[1]); - if Rply != None: - raise Error, Rply; - RC.Add(Res[1]); - RC.close(); + RC.process(pgp.sig_info) # Do LDAP stuff if LDAPDn != None: - CheckLDAP(Res[2][1]); - + CheckLDAP(pgp.key_fpr) + ErrMsg = "Verifying message:"; if Phrases != None: F = open(Phrases,"r"); while 1: Line = F.readline(); if Line == "": break; - if Res[3].find(Line.strip()) == -1: + if pgp.text.find(Line.strip()) == -1: raise Error,"Phrase '%s' was not found" % (Line.strip()) except: diff --git a/ud-echelon b/ud-echelon index 6c07a0a..2c0f984 100755 --- a/ud-echelon +++ b/ud-echelon @@ -2,6 +2,7 @@ # -*- mode: python -*- import userdir_gpg, userdir_ldap, sys, traceback, time, ldap, os, getopt; import pwd +import email, email.parser from userdir_gpg import *; from userdir_ldap import *; @@ -10,10 +11,10 @@ EX_PERMFAIL = 65; # EX_DATAERR Debug = None; # Try to extract a key fingerprint from a PGP siged message -def TryGPG(Email): +def TryGPG(email): # Try to get a pgp text try: - Msg = GetClearSig(Email); + Msg = GetClearSig(email); except: # Log an exception.. but continue. This is to deal with 'sort of' # PGP-MIME things @@ -25,26 +26,26 @@ def TryGPG(Email): if Msg[0].find("-----BEGIN PGP SIGNED MESSAGE-----") == -1: return None; - Res = GPGCheckSig(Msg[0]); + pgp = GPGCheckSig2(Msg[0]); # Failed to find a matching sig - if Res[0] != None: - S = "%s: %s -> PGP Checking failed '%s': %s %s\n" %(Now,MsgID,Email.getheader("From"),str(Res[0]),str(Res[2])); + if not pgp.ok: + S = "%s: %s -> PGP Checking failed '%s': %s %s\n" %(Now,MsgID,email["From"],str(pgp.why),str(pgp.key_info)); ErrLog.write(S); return None; # Search for the matching key fingerprint - Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyFingerPrint=" + Res[2][1]); + Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyFingerPrint=" + pgp.key_fpr); if len(Attrs) == 0: return None; if len(Attrs) != 1: raise Error, "Oddly your key fingerprint is assigned to more than one account.." - return (Attrs[0][1]["uid"][0],"PGP",FormatPGPKey(Res[2][1])); + return (Attrs[0][1]["uid"][0],"PGP",FormatPGPKey(pgp.key_fpr)); # Try to guess the name from the email address -def TryMatcher(Email): - Sender = Email.getheader("From"); +def TryMatcher(email): + Sender = email["From"]; if Sender == None: return None; @@ -87,9 +88,9 @@ try: # Get the email ErrType = EX_PERMFAIL; ErrMsg = "Failed to understand the email or find a signature:"; - Email = mimetools.Message(sys.stdin,0); - MsgID = Email.getheader("Message-ID"); - + email = email.parser.Parser().parse(sys.stdin); + MsgID = email["Message-ID"] + # Connect to the ldap server ErrType = EX_TEMPFAIL; ErrMsg = "An error occured while performing the LDAP lookup"; @@ -106,14 +107,15 @@ try: # Try to decode ErrType = EX_TEMPFAIL; ErrMsg = "An error occured while trying GPG decoding"; - User = TryGPG(Email); + User = TryGPG(email); if User == None: ErrMsg = "An error occured while trying Matcher decoding"; - User = TryMatcher(Email); + User = TryMatcher(email); # Get any mailing list information - List = Email.getheader("X-Mailing-List"); - if List == None: + if 'X-Mailing-List' in email: + List = email['X-Mailing-List'] + else: List = "-"; # Tada, write a log message @@ -127,7 +129,7 @@ try: else: print Rec; else: - User = ("-","UKN",Email.getheader("From")); + User = ("-","UKN",email("From")); Msg = "[%s] \"%s\" \"%s\" \"%s\""%(Now,User[2],List,MsgID); MainLog.write("%s %s %s\n"%(User[0],User[1],Msg)); diff --git a/ud-mailgate b/ud-mailgate index 30c9514..8439037 100755 --- a/ud-mailgate +++ b/ud-mailgate @@ -10,6 +10,7 @@ import userdir_gpg, userdir_ldap, sys, traceback, time, ldap, os, commands import pwd, tempfile import subprocess +import email, email.parser from userdir_gpg import * from userdir_ldap import * @@ -751,8 +752,8 @@ try: # Get the email ErrType = EX_PERMFAIL; ErrMsg = "Failed to understand the email or find a signature:"; - Email = mimetools.Message(sys.stdin,0); - Msg = GetClearSig(Email); + email = email.parser.Parser().parse(sys.stdin); + Msg = GetClearSig(email); ErrMsg = "Message is not PGP signed:" if Msg[0].find("-----BEGIN PGP SIGNED MESSAGE-----") == -1 and \ @@ -773,11 +774,8 @@ try: global PlainText; ErrMsg = "Problem stripping MIME headers from the decoded message" if Msg[1] == 1: - try: - Index = pgp.text.index("\n\n") + 2 - except ValueError: - Index = pgp.text.index("\n\r\n") + 3 - PlainText = pgp.text[Index:] + e = email.parser.Parser().parsestr(pgp.text) + PlainText = e.get_payload(decode=True) else: PlainText = pgp.text @@ -800,22 +798,13 @@ try: # Check the signature against the replay cache RC = ReplayCache(ReplayCacheFile); - RC.Clean(); - ErrMsg = "The replay cache rejected your message. Check your clock!"; - Rply = RC.Check(pgp.sig_info); - if Rply != None: - RC.close() - raise UDNotAllowedError, Rply; - RC.Add(pgp.sig_info); - RC.close() + RC.process(pgp.sig_info) # Determine the sender address ErrMsg = "A problem occured while trying to formulate the reply"; - Sender = Email.getheader("Reply-To"); - if Sender == None: - Sender = Email.getheader("From"); - if Sender == None: - raise UDFormatError, "Unable to determine the sender's address"; + Sender = email['Reply-To'] + if not Sender: Sender = email['From'] + if not Sender: raise UDFormatError, "Unable to determine the sender's address"; # Formulate a reply Date = time.strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime(time.time())); diff --git a/userdir_gpg.py b/userdir_gpg.py index 7a6862b..a6bfa55 100644 --- a/userdir_gpg.py +++ b/userdir_gpg.py @@ -26,8 +26,11 @@ # packets so I can tell if a signature is made by pgp2 to enable the # pgp2 encrypting mode. -import mimetools, multifile, sys, StringIO, os, tempfile, re; +import sys, StringIO, os, tempfile, re; import time, fcntl, anydbm +import email, email.message + +from userdir_exceptions import * # General GPG options GPGPath = "gpg" @@ -75,109 +78,78 @@ def SetKeyrings(Rings): # Paranoid will check the message text to make sure that all the plaintext is # in fact signed (bounded by a PGP packet) def GetClearSig(Msg,Paranoid = 0): - Error = 'MIME Error'; + if not Msg.__class__ == email.message.Message: + raise RuntimeError, "GetClearSign() not called with a email.message.Message" + # See if this is a MIME encoded multipart signed message - if Msg.gettype() == "multipart/signed": - Boundary = Msg.getparam("boundary"); - if not Boundary: - raise Error, "multipart/* without a boundary parameter"; - - # Create the multipart handler. Regrettably their implementation - # Needs seeking.. - SkMessage = StringIO.StringIO(); - SkMessage.write(Msg.fp.read()); - SkMessage.seek(0); - mf = multifile.MultiFile(SkMessage) - mf.push(Msg.getparam("boundary")); - - # Check the first bit of the message.. - if Paranoid != 0: - Pos = mf.tell(); - while 1: - x = mf.readline(); - if not x: break; - if len(x.strip()) != 0: - raise Error,"Unsigned text in message (at start)"; - mf.seek(Pos); - - # Get the first part of the multipart message - if not mf.next(): - raise Error, "Invalid pgp/mime encoding [no section]"; - - # Get the part as a safe seekable stream - Signed = StringIO.StringIO(); - Signed.write(mf.read()); - InnerMsg = mimetools.Message(Signed); - - # Make sure it is the right type - if InnerMsg.gettype() != "text/plain": - raise Error, "Invalid pgp/mime encoding [wrong plaintext type]"; - - # Get the next part of the multipart message - if not mf.next(): - raise Error, "Invalid pgp/mime encoding [no section]"; - InnerMsg = mimetools.Message(mf); - if InnerMsg.gettype() != "application/pgp-signature": - raise Error, "Invalid pgp/mime encoding [wrong signature type]"; - Signature = ''.join(mf.readlines()) - - # Check the last bit of the message.. - if Paranoid != 0: - mf.pop(); - Pos = mf.tell(); - while 1: - x = mf.readline(); - if not x: break; - if len(x.strip()) != 0: - raise Error,"Unsigned text in message (at end)"; - mf.seek(Pos); + if Msg.is_multipart(): + if not Msg.get_content_type() == "multipart/signed": + raise UDFormatError, "Cannot handle multipart messages not of type multipart/signed"; + + if Paranoid: + if Msg.preamble is not None and Msg.preamble.strip() != "": + raise UDFormatError,"Unsigned text in message (at start)"; + if Msg.epilogue is not None and Msg.epilogue.strip() != "": + raise UDFormatError,"Unsigned text in message (at end)"; + + payloads = Msg.get_payload() + if len(payloads) != 2: + raise UDFormatError, "multipart/signed message with number of payloads != 2"; + + (Signed, Signature) = payloads + + if Signed.get_content_type() != "text/plain": + raise UDFormatError, "Invalid pgp/mime encoding [wrong plaintext type]"; + if Signature.get_content_type() != "application/pgp-signature": + raise UDFormatError, "Invalid pgp/mime encoding [wrong signature type]"; # Append the PGP boundary header and the signature text to re-form the # original signed block [needs to convert to \r\n] Output = "-----BEGIN PGP SIGNED MESSAGE-----\r\n"; # Semi-evil hack to get the proper hash type inserted in the message - if Msg.getparam('micalg') != None: - Output = Output + "Hash: MD5,SHA1,%s\r\n"%(Msg.getparam('micalg')[4:].upper()) + if Msg.get_param('micalg') != None: + Output = Output + "Hash: MD5,SHA1,%s\r\n"%(Msg.get_param('micalg')[4:].upper()) Output = Output + "\r\n"; - Output = Output + Signed.getvalue().replace("\n-","\n- -") + Signature + Output = Output + Signed.as_string().replace("\n-","\n- -") + "\n" + Signature.get_payload(decode=True) return (Output,1); else: if Paranoid == 0: # Just return the message body - return (''.join(Msg.fp.readlines()),0); + return (Msg.get_payload(decode=True), 0); - Body = ""; + Body = []; State = 1; - for x in Msg.fp.readlines(): - Body = Body + x; - Tmp = x.strip() - if len(Tmp) == 0: + for x in Msg.get_payload(decode=True).split('\n'): + Body.append(x) + + if x == "": continue; # Leading up to the signature if State == 1: - if Tmp == "-----BEGIN PGP SIGNED MESSAGE-----": + if x == "-----BEGIN PGP SIGNED MESSAGE-----": State = 2; else: - raise Error,"Unsigned text in message (at start)"; + raise UDFormatError,"Unsigned text in message (at start)"; continue; # In the signature plain text if State == 2: - if Tmp == "-----BEGIN PGP SIGNATURE-----": + if x == "-----BEGIN PGP SIGNATURE-----": State = 3; continue; # In the signature if State == 3: - if Tmp == "-----END PGP SIGNATURE-----": + if x == "-----END PGP SIGNATURE-----": State = 4; continue; # Past the end if State == 4: - raise Error,"Unsigned text in message (at end)"; - return (Body,0); + raise UDFormatError,"Unsigned text in message (at end)"; + + return ("\n".join(Body), 0); # This opens GPG in 'write filter' mode. It takes Message and sends it # to GPGs standard input, pipes the standard output to a temp file along @@ -546,6 +518,7 @@ class ReplayCache: self.CleanCutOff = CleanCutOff; self.AgeCutOff = AgeCutOff; self.FutureCutOff = FutureCutOff; + self.Clean() # Close the cache and lock def __del__(self): @@ -588,6 +561,13 @@ class ReplayCache: else: self.DB[Key] = str(int(Sig[1])); + def process(self, sig_info): + r = self.Check(sig_info); + if r != None: + raise RuntimeError, "The replay cache rejected your message: %s."%(r); + self.Add(sig_info); + self.close(); + # vim:set et: # vim:set ts=3: # vim:set shiftwidth=3: -- 2.20.1