Echelon
[mirror/userdir-ldap.git] / ud-echelon
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 EX_TEMPFAIL = 75;
9 EX_PERMFAIL = 65;      # EX_DATAERR
10
11 # Try to extract a key fingerprint from a PGP siged message
12 def TryGPG(Email):
13    # Try to get a pgp text
14    Msg = GetClearSig(Email);
15    if string.find(Msg[0],"-----BEGIN PGP SIGNED MESSAGE-----") == -1:
16       return None;
17       
18    Res = GPGCheckSig(Msg[0]);
19
20    # Failed to find a matching sig
21    if Res[0] != None:
22       return None;
23       
24    # Search for the matching key fingerprint
25    Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyfingerprint=" + Res[2][1]);
26    if len(Attrs) == 0:
27       return None;
28    if len(Attrs) != 1:
29       raise Error, "Oddly your key fingerprint is assigned to more than one account.."
30    
31    return (Attrs[0][1]["uid"][0],"PGP",FormatPGPKey(Res[2][1]));
32
33 # Convert the PGP name string to a uid value
34 def GetUID(l,Name,UnknownMap = {}):
35    # Crack up the email address into a best guess first/middle/last name
36    (cn,mn,sn) = NameSplit(re.sub('["]','',Name[0]))
37    
38    # Brackets anger the ldap searcher
39    cn = re.sub('[(")]','?',cn);
40    sn = re.sub('[(")]','?',sn);
41
42    # First check the unknown map for the email address
43    if UnknownMap.has_key(Name[1] + '@' + Name[2]):
44       Stat = "unknown map hit for "+str(Name);
45       return (UnknownMap[Name[1] + '@' + Name[2]],[Stat]);
46
47    # Then the cruft component (ie there was no email address to match)
48    if UnknownMap.has_key(Name[2]):
49       Stat = "unknown map hit for"+str(Name);
50       return (UnknownMap[Name[2]],[Stat]);
51
52    # Search for a possible first/last name hit
53    try:
54       Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(&(cn=%s)(sn=%s))"%(cn,sn),["uid"]);
55    except ldap.FILTER_ERROR:
56       Stat = "Filter failure: (&(cn=%s)(sn=%s))"%(cn,sn);
57       return (None,[Stat]);
58
59    # Try matching on the email address
60    if (len(Attrs) != 1):
61       try:
62          Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"emailforward=%s"%(Name[2]),["uid"]);
63       except ldap.FILTER_ERROR:
64          pass;
65
66    # Hmm, more than one/no return
67    if (len(Attrs) != 1):
68       # Key claims a local address
69       if Name[2] == EmailAppend:
70
71          # Pull out the record for the claimed user
72          Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(uid=%s)"%(Name[1]),["uid","sn","cn"]);
73
74          # We require the UID surname to be someplace in the key name, this
75          # deals with special purpose keys like 'James Troup (Alternate Debian key)'
76          # Some people put their names backwards on their key too.. check that as well
77          if len(Attrs) == 1 and \
78             (string.find(string.lower(sn),string.lower(Attrs[0][1]["sn"][0])) != -1 or \
79             string.find(string.lower(cn),string.lower(Attrs[0][1]["sn"][0])) != -1):
80             Stat = EmailAppend+" hit for "+str(Name);
81             return (Name[1],[Stat]);
82
83       # Attempt to give some best guess suggestions for use in editing the
84       # override file.
85       Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(sn~=%s)"%(sn),["uid","sn","cn"]);
86
87       Stat = [];
88       if len(Attrs) != 0:
89          Stat = ["None for %s"%(str(Name))];
90       for x in Attrs:
91          Stat.append("But might be: %s %s <%s@debian.org>"%(x[1]["cn"][0],x[1]["sn"][0],x[1]["uid"][0]));
92       return (None,Stat);        
93    else:
94       return (Attrs[0][1]["uid"][0],None);
95
96    return (None,None);
97
98 # Try to guess the name from the email address
99 def TryMatcher(Email):
100    Sender = Email.getheader("From");
101    if Sender == None:
102       return None;
103       
104    # Split up the address and invoke the matcher routine
105    UID = GetUID(l,SplitEmail(Sender));
106    
107    if UID[0] == None:
108       if UID[1] == None or len(UID[1]) == 0:
109          return None;
110
111       # Print out an error message
112       S = "%s: %s -> Address matching failed '%s'\n" %(Now,MsgID,Sender);
113       for x in UID[1]:
114          S = S + " " + x + "\n";
115       ErrLog.write(S);
116       return None;
117     
118    return (UID[0],"FROM",Sender);
119    
120 # Open the log files
121 MainLog = open(Ech_MainLog,"a+",0);
122 ErrLog = open(Ech_ErrorLog,"a+",0);
123
124 # Start of main program
125 ErrMsg = "Indeterminate Error";
126 ErrType = EX_TEMPFAIL;
127 Now = time.strftime("%a, %d %b %Y %H:%M:%S",time.gmtime(time.time()));
128 MsgID = None;
129 try:
130    # Get the email 
131    ErrType = EX_PERMFAIL;
132    ErrMsg = "Failed to understand the email or find a signature:";
133    Email = mimetools.Message(sys.stdin,0);
134    MsgID = Email.getheader("Message-ID");
135    
136    # Connect to the ldap server
137    ErrType = EX_TEMPFAIL;
138    ErrMsg = "An error occured while performing the LDAP lookup";
139    global l;
140    l = ldap.open(LDAPServer);
141    F = open(PassDir+"/pass-"+pwd.getpwuid(posix.getuid())[0],"r");
142    AccessPass = string.split(string.strip(F.readline())," ");
143    l.simple_bind_s("uid="+AccessPass[0]+","+BaseDn,AccessPass[1]);
144    F.close();
145
146    # Try to decode
147    ErrType = EX_TEMPFAIL;
148    ErrMsg = "An error occured while trying GPG decoding";
149    User = TryGPG(Email);
150    if User == None:
151       ErrMsg = "An error occured while trying Matcher decoding";
152       User = TryMatcher(Email);
153
154    # Get any mailing list information   
155    List = Email.getheader("X-Mailing-List");
156    if List == None:
157       List = "-";
158
159    # Tada, write a log message
160    if User != None:
161       Msg = "[%s] \"%s\" \"%s\" \"%s\""%(Now,User[2],List,MsgID);
162       MainLog.write("%s %s %s\n"%(User[0],User[1],Msg));
163       Dn = "uid=" + User[0] + "," + BaseDn;
164       Rec = [(ldap.MOD_REPLACE,"activity-%s"%(User[1]),Msg)];
165       l.modify_s(Dn,Rec);
166    else:
167       User = ("-","UKN",Email.getheader("From"));
168       Msg = "[%s] \"%s\" \"%s\" \"%s\""%(Now,User[2],List,MsgID);
169       MainLog.write("%s %s %s\n"%(User[0],User[1],Msg));
170
171 except:
172    # Log an exception..
173    S = "%s: %s -> %s\n" %(Now,MsgID,ErrMsg);
174    S = S + "==> %s: %s\n" %(sys.exc_type,sys.exc_value);
175    List = traceback.extract_tb(sys.exc_traceback);
176    if len(List) > 1:
177       for x in List:
178          S = S + "   %s %s:%u: %s\n" %(x[2],x[0],x[1],x[3]);
179    ErrLog.write(S);
180    sys.exit(ErrType);
181    
182 sys.exit(0);