4 # Check and decode PGP signed emails.
5 # This script implements a wrapper around another program. It takes a mail
6 # on stdin and processes off a PGP signature, verifying it and seperating
7 # out the checked plaintext. It then invokes a sub process and feeds it
8 # the verified plain text and sets environment vairables indicating the
9 # result of the PGP check. If PGP checking fails then the subprocess is
10 # is never run and a bounce message is generated. The wrapper can understand
11 # PGP-MIME and all signatures supported by GPG. It completely decodes
12 # PGP-MIME before running the subprocess. It also can do optional
13 # anti-replay checking on the signatures.
15 # If enabled it can also do LDAP checking to determine the uniq UID owner
19 # -r Replay cache file, if unset replay checking is disabled
20 # -e Bounce error message template file, if unset very ugly bounces are
22 # -k Colon seperated list of keyrings to use
23 # -a Reply to address (mail daemon administrator)
24 # -d LDAP search base DN
26 # -m Email address to use when prettying up LDAP_EMAIL
28 # It exports the following environment variables:
29 # LDAP_EMAIL="Adam Di Carlo <aph@debian.org>"
31 # PGP_FINGERPRINT="E21E5D13FAD42A54F1AA5A00D801CE55"
32 # PGP_KEYID="8FFC405EFD5A67CD"
33 # PGP_KEYNAME="Adam Di Carlo <aph@debian.org> "
34 # SENDER (from mailer - envelope sender for bounces)
35 # REPLYTO (generated from message headers)
37 # Typical Debian invokation may look like:
38 # ./gpgwrapper -k /usr/share/keyrings/debian-keyring.gpg:/usr/share/keyrings/debian-keyring.pgp \
39 # -d ou=users,dc=debian,dc=org -l db.debian.org \
40 # -m debian.org -a admin@db.debian.org \
41 # -e /etc/userdir-ldap/templtes/error-reply -- test.sh
43 import sys, traceback, time, os;
45 from userdir_gpg import *;
48 EX_PERMFAIL = 65; # EX_DATAERR
49 Error = 'Message Error';
53 ReplayCacheFile = None;
59 # Safely get an attribute from a tuple representing a dn and an attribute
60 # list. It returns the first attribute if there are multi.
61 def GetAttr(DnRecord,Attribute,Default = ""):
63 return DnRecord[1][Attribute][0];
70 # Return a printable email address from the attributes.
71 def EmailAddress(DnRecord):
72 cn = GetAttr(DnRecord,"cn");
73 sn = GetAttr(DnRecord,"sn");
74 uid = GetAttr(DnRecord,"uid");
75 if cn == "" and sn == "":
76 return "<" + uid + "@" + EmailAppend + ">";
77 return cn + " " + sn + " <" + uid + "@" + EmailAppend + ">"
79 # Match the key fingerprint against an LDAP directory
80 def CheckLDAP(FingerPrint):
83 # Connect to the ldap server
84 global ErrTyp, ErrMsg;
85 ErrType = EX_TEMPFAIL;
86 ErrMsg = "An error occured while performing the LDAP lookup";
88 l = connectLDAP(LDAPServer);
89 l.simple_bind_s("","");
91 # Search for the matching key fingerprint
92 Attrs = l.search_s(LDAPDn,ldap.SCOPE_ONELEVEL,"keyfingerprint=" + FingerPrint);
94 raise Error, "Key not found"
96 raise Error, "Oddly your key fingerprint is assigned to more than one account.."
98 os.environ["LDAP_UID"] = GetAttr(Attrs[0],"uid");
99 os.environ["LDAP_EMAIL"] = EmailAddress(Attrs[0]);
101 # Start of main program
103 (options, arguments) = getopt.getopt(sys.argv[1:], "r:e:k:a:d:l:m:");
104 for (switch, val) in options:
106 ReplayCacheFile = val;
107 elif (switch == '-e'):
109 elif (switch == '-k'):
110 SetKeyrings(val.split(":"));
111 elif (switch == '-a'):
113 elif (switch == '-d'):
115 elif (switch == '-l'):
117 elif (switch == '-m'):
120 # Drop messages from a mailer daemon. (empty sender)
121 if os.environ.has_key('SENDER') == 0 or len(os.environ['SENDER']) == 0:
124 ErrMsg = "Indeterminate Error";
125 ErrType = EX_TEMPFAIL;
127 # Startup the replay cache
128 ErrType = EX_TEMPFAIL;
129 if ReplayCacheFile != None:
130 ErrMsg = "Failed to initialize the replay cache:";
131 RC = ReplayCache(ReplayCacheFile);
135 ErrType = EX_PERMFAIL;
136 ErrMsg = "Failed to understand the email or find a signature:";
137 Email = mimetools.Message(sys.stdin,0);
138 Msg = GetClearSig(Email);
140 ErrMsg = "Message is not PGP signed:"
141 if Msg[0].find("-----BEGIN PGP SIGNED MESSAGE-----") == -1:
142 raise Error, "No PGP signature";
144 # Check the signature
145 ErrMsg = "Unable to check the signature or the signature was invalid:";
146 Res = GPGCheckSig(Msg[0]);
152 raise Error, "Null signature text";
154 # Extract the plain message text in the event of mime encoding
156 ErrMsg = "Problem stripping MIME headers from the decoded message"
159 Index = Res[3].index("\n\n") + 2;
161 Index = Res[3].index("\n\r\n") + 3;
162 PlainText = Res[3][Index:];
166 # Check the signature against the replay cache
167 if ReplayCacheFile != None:
168 ErrMsg = "The replay cache rejected your message. Check your clock!";
169 Rply = RC.Check(Res[1]);
176 CheckLDAP(Res[2][1]);
178 # Determine the sender address
179 ErrType = EX_PERMFAIL;
180 ErrMsg = "A problem occured while trying to formulate the reply";
181 Sender = Email.getheader("Reply-To");
183 Sender = Email.getheader("From");
185 raise Error, "Unable to determine the sender's address";
187 # Setup the environment
188 ErrType = EX_TEMPFAIL;
189 ErrMsg = "Problem calling the child process"
190 os.environ["PGP_KEYID"] = Res[2][0];
191 os.environ["PGP_FINGERPRINT"] = Res[2][1];
192 os.environ["PGP_KEYNAME"] = Res[2][2];
193 os.environ["REPLYTO"] = Sender;
196 Child = os.popen(" ".join(arguments),"w");
197 Child.write(PlainText);
198 if Child.close() != None:
199 raise Error, "Child gave a non-zero return code";
203 Date = time.strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime(time.time()));
204 ErrReplyHead = "To: %s\nReply-To: %s\nDate: %s\n" % (os.environ['SENDER'],ReplyTo,Date);
208 Subst["__ERROR__"] = ErrMsg;
209 Subst["__ADMIN__"] = ReplyTo;
211 Trace = "==> %s: %s\n" %(sys.exc_type,sys.exc_value);
212 List = traceback.extract_tb(sys.exc_traceback);
214 Trace = Trace + "Python Stack Trace:\n";
216 Trace = Trace + " %s %s:%u: %s\n" %(x[2],x[0],x[1],x[3]);
218 Subst["__TRACE__"] = Trace;
220 # Try to send the bounce
222 if ErrorTemplate != None:
223 ErrReply = TemplateSubst(Subst,open(ErrorTemplate,"r").read());
225 ErrReply = "\n"+str(Subst)+"\n";
227 Child = os.popen("/usr/sbin/sendmail -t","w");
228 Child.write(ErrReplyHead);
229 Child.write(ErrReply);
230 if Child.close() != None:
231 raise Error, "Sendmail gave a non-zero return code";
233 sys.exit(EX_TEMPFAIL);
235 if ErrType != EX_PERMFAIL: