- #!/usr/bin/env python
-# -*- mode: python -*-
+# Copyright (c) 1999-2001 Jason Gunthorpe <jgg@debian.org>
+# Copyright (c) 2005 Joey Schulze <joey@infodrom.org>
+#
+# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
# GPG issues -
# - gpgm with a status FD being fed keymaterial and other interesting
# packets so I can tell if a signature is made by pgp2 to enable the
# pgp2 encrypting mode.
-import string, mimetools, multifile, sys, StringIO, os, tempfile, re;
-import rfc822, time, fcntl, FCNTL, anydbm
+import mimetools, multifile, sys, StringIO, os, tempfile, re;
+import rfc822, time, fcntl, anydbm
# General GPG options
GPGPath = "gpg"
-GPGBasicOptions = ["--no-options","--batch","--load-extension","rsa",\
- "--no-default-keyring","--always-trust"];
-GPGKeyRings = ["--keyring","/usr/share/keyrings/debian-keyring.pgp",\
- "--keyring","/usr/share/keyrings/debian-keyring.gpg"];
+# "--load-extension","rsa",
+GPGBasicOptions = [
+ "--no-options",
+ "--batch",
+ "--no-default-keyring",
+ "--secret-keyring", "/dev/null",
+ "--always-trust"];
+GPGKeyRings = [];
GPGSigOptions = ["--output","-"];
GPGSearchOptions = ["--dry-run","--with-colons","--fingerprint"];
GPGEncryptOptions = ["--output","-","--quiet","--always-trust",\
AgeCutOff = 4*24*60*60;
FutureCutOff = 3*24*60*60;
+# Set the keyrings, the input is a list of keyrings
+def SetKeyrings(Rings):
+ for x in Rings:
+ GPGKeyRings.append("--keyring");
+ GPGKeyRings.append(x);
+
# GetClearSig takes an un-seekable email message stream (mimetools.Message)
# and returns a standard PGP '---BEGIN PGP SIGNED MESSAGE---' bounded
# clear signed text.
# present in the signature text! The return result is a tuple, the first
# element is the text itself the second is a mime flag indicating if the
# result should be mime processed after sig checking.
-def GetClearSig(Msg):
+#
+# 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';
# See if this is a MIME encoded multipart signed message
if Msg.gettype() == "multipart/signed":
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]";
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]";
InnerMsg = mimetools.Message(mf);
if InnerMsg.gettype() != "application/pgp-signature":
raise Error, "Invalid pgp/mime encoding [wrong signature type]";
- Signature = string.joinfields(mf.readlines(),'');
-
+ 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);
+
# 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: %s\r\n"%(string.upper(Msg.getparam('micalg')[4:]));
+ Output = Output + "Hash: MD5,SHA1,%s\r\n"%(Msg.getparam('micalg')[4:].upper())
Output = Output + "\r\n";
- Output = Output + Signed.getvalue() + Signature;
+ Output = Output + Signed.getvalue().replace("\n-","\n- -") + Signature
return (Output,1);
else:
- # Just return the message body
- return (string.joinfields(Msg.fp.readlines(),''),0);
+ if Paranoid == 0:
+ # Just return the message body
+ return (''.join(Msg.fp.readlines()),0);
+
+ Body = "";
+ State = 1;
+ for x in Msg.fp.readlines():
+ Body = Body + x;
+ Tmp = x.strip()
+ if len(Tmp) == 0:
+ continue;
+
+ # Leading up to the signature
+ if State == 1:
+ if Tmp == "-----BEGIN PGP SIGNED MESSAGE-----":
+ State = 2;
+ else:
+ raise Error,"Unsigned text in message (at start)";
+ continue;
+
+ # In the signature plain text
+ if State == 2:
+ if Tmp == "-----BEGIN PGP SIGNATURE-----":
+ State = 3;
+ continue;
+
+ # In the signature
+ if State == 3:
+ if Tmp == "-----END PGP SIGNATURE-----":
+ State = 4;
+ continue;
+
+ # Past the end
+ if State == 4:
+ raise Error,"Unsigned text in message (at end)";
+ return (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
if Why == None:
GoodSig = 1;
KeyID = Split[2];
- Owner = string.join(Split[3:],' ');
+ Owner = ' '.join(Split[3:])
# Bad signature response
if Split[1] == "BADSIG":
Why = "Unable to verify signature, signing key missing.";
# Expired signature
- if Split[1] == "SIGEXPIRED":
+ if Split[1] == "SIGEXPIRED" or Split[1] == "EXPSIG":
GoodSig = 0;
Why = "Signature has expired";
# Revoked key
- if Split[1] == "KEYREVOKED":
+ if Split[1] == "KEYREVOKED" or Split[1] == "REVKEYSIG":
GoodSig = 0;
Why = "Signing key has been revoked";
# ValidSig has the key finger print
if Split[1] == "VALIDSIG":
- KeyFinger = Split[2];
+ # Use the fingerprint of the primary key when available
+ if len(Split) >= 12:
+ KeyFinger = Split[11];
+ else:
+ KeyFinger = Split[2];
# Reopen the stream as a readable stream
Text = Res[2].read();
Result = [];
Owner = "";
KeyID = "";
+ Hits = {};
+
+ dir = os.path.expanduser("~/.gnupg")
+ if not os.path.isdir(dir):
+ os.mkdir(dir, 0700)
+
try:
- Strm = os.popen(string.join(Args," "),"r");
+ Strm = os.popen(" ".join(Args),"r")
while(1):
# Grab and split up line
Line = Strm.readline();
if Line == "":
break;
- Split = string.split(Line,":");
-
- # Store some of the key fields
+ Split = Line.split(":")
+
+ # Store some of the key fields
if Split[0] == 'pub':
KeyID = Split[4];
Owner = Split[9];
- Length = int(Split[2]);
+ Length = int(Split[2]);
# Output the key
if Split[0] == 'fpr':
+ if Hits.has_key(Split[9]):
+ continue;
+ Hits[Split[9]] = None;
Result.append( (KeyID,Split[9],Owner,Length) );
finally:
if Strm != None:
# Perform a substition of template
def TemplateSubst(Map,Template):
for x in Map.keys():
- Template = string.replace(Template,x,Map[x]);
+ Template = Template.replace(x, Map[x])
return Template;
# The replay class uses a python DB (BSD db if avail) to implement
class ReplayCache:
def __init__(self,Database):
self.Lock = open(Database + ".lock","w",0600);
- fcntl.flock(self.Lock.fileno(),FCNTL.LOCK_EX);
+ fcntl.flock(self.Lock.fileno(),fcntl.LOCK_EX);
self.DB = anydbm.open(Database,"c",0600);
self.CleanCutOff = CleanCutOff;
self.AgeCutOff = AgeCutOff;