Initial import
[mirror/userdir-ldap.git] / ud-mailgate
1 #!/usr/bin/env python
2 # -*- mode: python -*-
3 import userdir_gpg, userdir_ldap, sys, traceback, time, ldap, posix;
4 import string, pwd
5 from userdir_gpg import *;
6 from userdir_ldap import *;
7
8 # Error codes from /usr/include/sysexits.h
9 ReplyTo = ConfModule.replyto;
10 PingFrom = ConfModule.pingfrom;
11 ChPassFrom = ConfModule.chpassfrom;
12 ReplayCacheFile = ConfModule.replaycachefile;
13
14 EX_TEMPFAIL = 75;
15 EX_PERMFAIL = 65;      # EX_DATAERR
16 Error = 'Message Error';
17
18 # Handle ping handles an email sent to the 'ping' address (ie this program
19 # called with a ping argument) It replies with a dump of the public records.
20 def HandlePing(Reply,DnRecord,Key):
21    Subst = {};
22    Subst["__FROM__"] = PingFrom;
23    Subst["__EMAIL__"] = EmailAddress(DnRecord);
24    Subst["__LDAPFIELDS__"] = PrettyShow(Attrs[0]);
25    Subst["__ADMIN__"] = ReplyTo;
26
27    return Reply + TemplateSubst(Subst,open(TemplatesDir+"ping-reply","r").read());
28
29 # Handle a change password email sent to the change password address
30 # (this program called with the chpass argument)
31 def HandleChPass(Reply,DnRecord,Key):
32    # Generate a random password
33    Password = GenPass();
34    Pass = HashPass(Password);
35       
36    # Use GPG to encrypt it      
37    Message = GPGEncrypt("Your new password is '" + Password + "'\n",\
38                         "0x"+Key[1],Key[4]);
39    Password = None;
40
41    if Message == None:
42       raise Error, "Unable to generate the encrypted reply, gpg failed.";
43
44    if (Key[4] == 1):
45       Type = "Your message was encrypted using PGP 2.x\ncompatibility mode.";
46    else:
47       Type = "Your message was encrypted using GPG (OpenPGP)\ncompatibility "\
48              "mode, without IDEA. This message cannot be decoded using PGP 2.x";
49    
50    Subst = {};
51    Subst["__FROM__"] = ChPassFrom;
52    Subst["__EMAIL__"] = EmailAddress(DnRecord);
53    Subst["__CRYPTTYPE__"] = Type;
54    Subst["__PASSWORD__"] = Message;
55    Subst["__ADMIN__"] = ReplyTo;
56    Reply = Reply + TemplateSubst(Subst,open(TemplatesDir+"passwd-changed","r").read());
57    
58    # Connect to the ldap server
59    l = ldap.open(LDAPServer);
60    F = open(PassDir+"/pass-"+pwd.getpwuid(posix.getuid())[0],"r");
61    AccessPass = string.split(string.strip(F.readline())," ");
62    F.close();
63
64    # Modify the password
65    l.simple_bind_s("uid="+AccessPass[0]+","+BaseDn,AccessPass[1]);
66    Rec = [(ldap.MOD_REPLACE,"userPassword","{crypt}"+Pass)];
67    Dn = "uid=" + GetAttr(DnRecord,"uid") + "," + BaseDn;
68    l.modify_s(Dn,Rec);
69    
70    return Reply;
71       
72 # Start of main program
73 ErrMsg = "Indeterminate Error";
74 ErrType = EX_TEMPFAIL;
75 try:
76    # Startup the replay cache
77    ErrType = EX_TEMPFAIL;
78    ErrMsg = "Failed to initialize the replay cache:";
79    RC = ReplayCache(ReplayCacheFile);
80    RC.Clean();
81
82    # Get the email 
83    ErrType = EX_PERMFAIL;
84    ErrMsg = "Failed to understand the email or find a signature:";
85    Email = mimetools.Message(sys.stdin,0);
86    Msg = GetClearSig(Email);
87
88    # Check the signature   
89    ErrMsg = "Unable to check the signature or the signature was invalid:";
90    Res = GPGCheckSig(Msg[0]);
91
92    if Res[0] != None:
93       raise Error, Res[0];
94       
95    if Res[3] == None:
96       raise Error, "Null signature text";
97
98    # Extract the plain message text in the event of mime encoding
99    ErrMsg = "Problem stripping MIME headers from the decoded message"
100    if Msg[1] == 1:
101       try:
102          Index = string.index(Res[3],"\n\n") + 2;
103       except ValueError:
104          Index = string.index(Res[3],"\n\r\n") + 3;
105       PlainText = Res[3][Index:];
106    else:
107       PlainText = Res[3];   
108
109    # Check the signature against the replay cache
110    ErrMsg = "The replay cache rejected your message. Check your clock!";
111    Rply = RC.Check(Res[1]);
112    if Rply != None:
113       raise Error, Rply;
114    RC.Add(Res[1]);
115
116    # Connect to the ldap server
117    ErrType = EX_TEMPFAIL;
118    ErrMsg = "An error occured while performing the LDAP lookup";
119    l = ldap.open(LDAPServer);
120    l.simple_bind_s("","");
121
122    # Search for the matching key fingerprint
123    Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyfingerprint=" + Res[2][1]);
124    if len(Attrs) == 0:
125       raise Error, "Key not found"
126    if len(Attrs) != 1:
127       raise Error, "Oddly your key fingerprint is assigned to more than one account.."
128
129    # Determine the sender address
130    ErrType = EX_PERMFAIL;
131    ErrMsg = "A problem occured while trying to formulate the reply";
132    Sender = Email.getheader("Reply-To");
133    if Sender == None:
134       Sender = Email.getheader("From");
135    if Sender == None:
136       raise Error, "Unable to determine the sender's address";
137
138    # Formulate a reply
139    Date = time.strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime(time.time()));
140    Reply = "To: %s\nReply-To: %s\nDate: %s\n" % (Sender,ReplyTo,Date);
141
142    # Dispatch
143    if sys.argv[1] == "ping":
144       Reply = HandlePing(Reply,Attrs[0],Res[2]);
145    elif sys.argv[1] == "chpass":
146       if string.find(string.strip(PlainText),"Please change my Debian password") != 0:
147          raise Error,"Please send a signed message where the first line of text is the string 'Please change my Debian password'";
148       Reply = HandleChPass(Reply,Attrs[0],Res[2]);
149    else:
150       print sys.argv;
151       raise Error, "Incorrect Invokation";
152
153    # Send the message through sendmail      
154    ErrMsg = "A problem occured while trying to send the reply";
155    Child = posix.popen("/usr/sbin/sendmail -t","w");
156 #   Child = posix.popen("cat","w");
157    Child.write(Reply);
158    if Child.close() != None:
159       raise Error, "Sendmail gave a non-zero return code";
160
161 except:
162    print ErrMsg;
163    print "==> %s: %s" %(sys.exc_type,sys.exc_value);
164    List = traceback.extract_tb(sys.exc_traceback);
165    if len(List) > 1:
166       print "Trace: ";
167       for x in List:
168          print "   %s %s:%u: %s" %(x[2],x[0],x[1],x[3]);
169    sys.exit(ErrType);
170