Initial import
[mirror/userdir-ldap.git] / ud-gpgimport
1 #!/usr/bin/env python
2 # -*- mode: python -*-
3 # This script tries to match key fingerprints from a keyring with user
4 # name in a directory. When an unassigned key is found a heuristic match
5 # against the keys given cn/sn and the directory is performed to try to get
6 # a matching. Generally this works about 90% of the time, matching is fairly
7 # strict. In the event a non-match a fuzzy sounds-alike search is performed
8 # and the results printed to aide the user.
9 #
10 # GPG is automatically invoked with the correct magic special options,
11 # pass the names of all the valid key rings on the command line.
12 #
13 # The output report will list what actions were taken. Keys that are present
14 # in the directory but not in the key ring will be removed from the 
15 # directory. 
16
17 import string, re, time, ldap, getopt, sys, pwd, posix;
18 from userdir_ldap import *;
19 from userdir_gpg import *;
20
21 # This map deals with people who put the wrong sort of stuff in their pgp
22 # key entries
23 UnknownMap = {};
24 NoAct = 1;
25
26 AddressSplit = re.compile("(.*).*<([^@]*)@([^>]*)>");
27
28 # Read the override file into the unknown map. The override file is a list
29 # of colon delimited entires mapping PGP email addresess to local users
30 def LoadOverride(File):
31    List = open(File,"r");
32    while(1):
33       Line = List.readline();
34       if Line == "":
35          break;
36       Split = re.split("[:\n]",Line);
37       UnknownMap[Split[0]] = string.strip(Split[1]);
38
39 # Convert the PGP name string to a uid value
40 def GetUID(l,Name):
41    # Crack up the email address into a best guess first/middle/last name
42    (cn,mn,sn) = NameSplit(re.sub('["]','',Name[0]))
43    
44    # Brackets anger the ldap searcher
45    cn = re.sub('[(")]','?',cn);
46    sn = re.sub('[(")]','?',sn);
47
48    # First check the unknown map for the email address
49    if UnknownMap.has_key(Name[1] + '@' + Name[2]):
50       print "unknown map hit for",Name;
51       return UnknownMap[Name[1] + '@' + Name[2]];
52
53    # Then the cruft component (ie there was no email address to match)
54    if UnknownMap.has_key(Name[2]):
55       print "unknown map hit for",Name;
56       return UnknownMap[Name[2]];
57
58    # Search for a possible first/last name hit
59    try:
60       Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(&(cn=%s)(sn=%s))"%(cn,sn),["uid"]);
61    except ldap.FILTER_ERROR:
62       print "Filter failure:","(&(cn=%s)(sn=%s))"%(cn,sn);
63       return None;
64
65    # Hmm, more than one/no return
66    if (len(Attrs) != 1):
67       # Key claims a local address
68       if Name[2] == EmailAppend:
69
70          # Pull out the record for the claimed user
71          Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(uid=%s)"%(Name[1]),["uid","sn","cn"]);
72
73          # We require the UID surname to be someplace in the key name, this
74          # deals with special purpose keys like 'James Troup (Alternate Debian key)'
75          # Some people put their names backwards on their key too.. check that as well
76          if len(Attrs) == 1 and \
77             (string.find(string.lower(sn),string.lower(Attrs[0][1]["sn"][0])) != -1 or \
78             string.find(string.lower(cn),string.lower(Attrs[0][1]["sn"][0])) != -1):
79             print EmailAppend,"hit for",Name;
80             return Name[1];
81
82       # Attempt to give some best guess suggestions for use in editing the
83       # override file.
84       print "None for",Name;
85       Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(sn~=%s)"%(sn),["uid","sn","cn"]);
86       for x in Attrs:
87          print "  But might be:",x[1]["cn"][0],x[1]["sn"][0],"<" + x[1]["uid"][0] + "@debian.org>";
88    else:
89       return Attrs[0][1]["uid"][0];
90
91    return None;
92
93 # Process options
94 AdminUser = pwd.getpwuid(posix.getuid())[0];
95 (options, arguments) = getopt.getopt(sys.argv[1:], "au:m:n")
96 for (switch, val) in options:
97    if (switch == '-u'):
98       AdminUser = val
99    elif (switch == '-m'):
100        LoadOverride(val);
101    elif (switch == '-a'):
102        NoAct = 0;
103 if len(arguments) == 0:
104    print "Give some keyrings to probe";
105    os.exit(0);
106
107 # Main program starts here
108
109 # Connect to the ldap server
110 l = ldap.open(LDAPServer);
111 if NoAct == 0:
112    print "Accessing LDAP directory as '" + AdminUser + "'";
113    Password = getpass(AdminUser + "'s password: ");
114    UserDn = "uid=" + AdminUser + "," + BaseDn;
115    l.simple_bind_s(UserDn,Password);
116 else:
117    l.simple_bind_s("","");
118
119 # Download the existing key list and put it into a map
120 print "Fetching key list..",
121 sys.stdout.flush();
122 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyfingerprint=*",["keyfingerprint","uid"]);
123 KeyMap = {};
124 KeyCount = {};
125 for x in Attrs:
126   try:
127      # Sense a bad fingerprint.. Slapd has problems, it will store a null
128      # value that ldapsearch doesn't show up.. detect and remove
129      if len(x[1]["keyfingerprint"]) == 0 or x[1]["keyfingerprint"][0] == "":
130        print;
131        print "Fixing bad fingerprint for",x[1]["uid"][0],
132        sys.stdout.flush();
133        if NoAct == 0:
134          l.modify_s("uid="+x[1]["uid"][0]+","+BaseDn,\
135                      [(ldap.MOD_DELETE,"keyfingerprint",None)]);
136      else:
137        for I in x[1]["keyfingerprint"]:
138          KeyMap[I] = [x[1]["uid"][0],0];
139          if KeyCount.has_key(x[1]["uid"][0]):
140             KeyCount[x[1]["uid"][0]] = KeyCount[x[1]["uid"][0]] + 1;
141          else:
142             KeyCount[x[1]["uid"][0]] = 1;
143   except:
144      continue;
145 Attrs = None;
146 print;
147
148 # Popen GPG with the correct magic special options
149 Args = [GPGPath] + GPGBasicOptions;
150 for x in arguments:
151    Args.append("--keyring");
152    if string.find(x,"/") == -1:
153       Args.append("./"+x);
154    else:
155       Args.append(x);
156 Args = Args + GPGSearchOptions + [" 2> /dev/null"]
157 Keys = os.popen(string.join(Args," "),"r");
158
159 # Loop over the GPG key file
160 Outstanding = 0;
161 Ignored = 0;
162 while(1):
163    Line = Keys.readline();
164    if Line == "":
165       break;
166    
167    Split = string.split(Line,":");
168    if len(Split) < 8 or Split[0] != "pub":
169       continue;
170
171    while (1):
172        Line2 = Keys.readline();
173        if Line2 == "":
174           break;
175        Split2 = string.split(Line2,":");
176        if len(Split2) < 11 or Split2[0] != "fpr":
177           continue;
178        break;
179    if Line2 == "":
180       break;
181
182    if KeyMap.has_key(Split2[9]):
183       Ignored = Ignored + 1;
184       # print "Ignoring keyID",Split2[9],"belonging to",KeyMap[Split2[9]][0];
185       KeyMap[Split2[9]][1] = 1;
186       continue;
187
188    Match = AddressSplit.match(Split[9]);
189    if Match == None:
190       UID = GetUID(l,("","",Split[9]));
191    else:
192       UID = GetUID(l,Match.groups());
193
194    if UID == None:
195       print "MISSING 0x" + Split2[9];
196       continue;
197
198    Rec = [(ldap.MOD_ADD,"keyfingerprint",Split2[9])];
199    Dn = "uid=" + UID + "," + BaseDn;
200    print "Adding keyID",Split2[9],"to",UID;
201    if KeyCount.has_key(UID):
202       KeyCount[UID] = KeyCount[UID] + 1;
203    else:
204       KeyCount[UID] = 1;
205    
206    if NoAct == 1:
207       continue;
208
209    # Send the modify request
210    l.modify(Dn,Rec);
211    Outstanding = Outstanding + 1;
212    Outstanding = FlushOutstanding(l,Outstanding,1);
213    sys.stdout.flush();
214
215 if NoAct == 0:
216    FlushOutstanding(l,Outstanding);
217
218 if Keys.close() != None:
219    raise "Error","GPG failed"
220
221 print Ignored,"keys already in the directory (ignored)";
222
223 # Look for unmatched keys
224 for x in KeyMap.keys():
225    if KeyMap[x][1] == 0:
226       print "keyID",x,"belonging to",KeyMap[x][0],"removed";
227       if KeyCount.has_key(KeyMap[x][0]) :
228          KeyCount[KeyMap[x][0]] = KeyCount[KeyMap[x][0]] - 1
229          if KeyCount[KeyMap[x][0]] <= 0:
230             print "**",KeyMap[x][0],"no longer has any keys";
231       if NoAct == 0:
232          l.modify_s("uid="+KeyMap[x][0]+","+BaseDn,\
233                      [(ldap.MOD_DELETE,"keyfingerprint",x)]);
234