#!/usr/bin/env python # -*- mode: python -*- # This script tries to match key fingerprints from a keyring with user # name in a directory. When an unassigned key is found a heuristic match # against the keys given cn/sn and the directory is performed to try to get # a matching. Generally this works about 90% of the time, matching is fairly # strict. In the event a non-match a fuzzy sounds-alike search is performed # and the results printed to aide the user. # # GPG is automatically invoked with the correct magic special options, # pass the names of all the valid key rings on the command line. # # The output report will list what actions were taken. Keys that are present # in the directory but not in the key ring will be removed from the # directory. import string, re, time, ldap, getopt, sys, pwd, posix; from userdir_ldap import *; from userdir_gpg import *; # This map deals with people who put the wrong sort of stuff in their pgp # key entries UnknownMap = {}; NoAct = 1; AddressSplit = re.compile("(.*).*<([^@]*)@([^>]*)>"); # Read the override file into the unknown map. The override file is a list # of colon delimited entires mapping PGP email addresess to local users def LoadOverride(File): List = open(File,"r"); while(1): Line = List.readline(); if Line == "": break; Split = re.split("[:\n]",Line); UnknownMap[Split[0]] = string.strip(Split[1]); # Convert the PGP name string to a uid value def GetUID(l,Name): # Crack up the email address into a best guess first/middle/last name (cn,mn,sn) = NameSplit(re.sub('["]','',Name[0])) # Brackets anger the ldap searcher cn = re.sub('[(")]','?',cn); sn = re.sub('[(")]','?',sn); # First check the unknown map for the email address if UnknownMap.has_key(Name[1] + '@' + Name[2]): print "unknown map hit for",Name; return UnknownMap[Name[1] + '@' + Name[2]]; # Then the cruft component (ie there was no email address to match) if UnknownMap.has_key(Name[2]): print "unknown map hit for",Name; return UnknownMap[Name[2]]; # Search for a possible first/last name hit try: Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(&(cn=%s)(sn=%s))"%(cn,sn),["uid"]); except ldap.FILTER_ERROR: print "Filter failure:","(&(cn=%s)(sn=%s))"%(cn,sn); return None; # Hmm, more than one/no return if (len(Attrs) != 1): # Key claims a local address if Name[2] == EmailAppend: # Pull out the record for the claimed user Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(uid=%s)"%(Name[1]),["uid","sn","cn"]); # We require the UID surname to be someplace in the key name, this # deals with special purpose keys like 'James Troup (Alternate Debian key)' # Some people put their names backwards on their key too.. check that as well if len(Attrs) == 1 and \ (string.find(string.lower(sn),string.lower(Attrs[0][1]["sn"][0])) != -1 or \ string.find(string.lower(cn),string.lower(Attrs[0][1]["sn"][0])) != -1): print EmailAppend,"hit for",Name; return Name[1]; # Attempt to give some best guess suggestions for use in editing the # override file. print "None for",Name; Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"(sn~=%s)"%(sn),["uid","sn","cn"]); for x in Attrs: print " But might be:",x[1]["cn"][0],x[1]["sn"][0],"<" + x[1]["uid"][0] + "@debian.org>"; else: return Attrs[0][1]["uid"][0]; return None; # Process options AdminUser = pwd.getpwuid(posix.getuid())[0]; (options, arguments) = getopt.getopt(sys.argv[1:], "au:m:n") for (switch, val) in options: if (switch == '-u'): AdminUser = val elif (switch == '-m'): LoadOverride(val); elif (switch == '-a'): NoAct = 0; if len(arguments) == 0: print "Give some keyrings to probe"; os.exit(0); # Main program starts here # Connect to the ldap server l = ldap.open(LDAPServer); if NoAct == 0: print "Accessing LDAP directory as '" + AdminUser + "'"; Password = getpass(AdminUser + "'s password: "); UserDn = "uid=" + AdminUser + "," + BaseDn; l.simple_bind_s(UserDn,Password); else: l.simple_bind_s("",""); # Download the existing key list and put it into a map print "Fetching key list..", sys.stdout.flush(); Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyfingerprint=*",["keyfingerprint","uid"]); KeyMap = {}; KeyCount = {}; for x in Attrs: try: # Sense a bad fingerprint.. Slapd has problems, it will store a null # value that ldapsearch doesn't show up.. detect and remove if len(x[1]["keyfingerprint"]) == 0 or x[1]["keyfingerprint"][0] == "": print; print "Fixing bad fingerprint for",x[1]["uid"][0], sys.stdout.flush(); if NoAct == 0: l.modify_s("uid="+x[1]["uid"][0]+","+BaseDn,\ [(ldap.MOD_DELETE,"keyfingerprint",None)]); else: for I in x[1]["keyfingerprint"]: KeyMap[I] = [x[1]["uid"][0],0]; if KeyCount.has_key(x[1]["uid"][0]): KeyCount[x[1]["uid"][0]] = KeyCount[x[1]["uid"][0]] + 1; else: KeyCount[x[1]["uid"][0]] = 1; except: continue; Attrs = None; print; # Popen GPG with the correct magic special options Args = [GPGPath] + GPGBasicOptions; for x in arguments: Args.append("--keyring"); if string.find(x,"/") == -1: Args.append("./"+x); else: Args.append(x); Args = Args + GPGSearchOptions + [" 2> /dev/null"] Keys = os.popen(string.join(Args," "),"r"); # Loop over the GPG key file Outstanding = 0; Ignored = 0; SeenKeys = {}; while(1): Line = Keys.readline(); if Line == "": break; Split = string.split(Line,":"); if len(Split) < 8 or Split[0] != "pub": continue; while (1): Line2 = Keys.readline(); if Line2 == "": break; Split2 = string.split(Line2,":"); if len(Split2) < 11 or Split2[0] != "fpr": continue; break; if Line2 == "": break; if SeenKeys.has_key(Split2[9]): print "Dup key 0x",Split2[9],"belonging to",KeyMap[Split2[9]][0]; continue; SeenKeys[Split2[9]] = None; if KeyMap.has_key(Split2[9]): Ignored = Ignored + 1; # print "Ignoring keyID",Split2[9],"belonging to",KeyMap[Split2[9]][0]; KeyMap[Split2[9]][1] = 1; continue; Match = AddressSplit.match(Split[9]); if Match == None: UID = GetUID(l,("","",Split[9])); else: UID = GetUID(l,Match.groups()); if UID == None: print "MISSING 0x" + Split2[9]; continue; Rec = [(ldap.MOD_ADD,"keyfingerprint",Split2[9])]; Dn = "uid=" + UID + "," + BaseDn; print "Adding key 0x",Split2[9],"to",UID; if KeyCount.has_key(UID): KeyCount[UID] = KeyCount[UID] + 1; else: KeyCount[UID] = 1; if NoAct == 1: continue; # Send the modify request l.modify(Dn,Rec); Outstanding = Outstanding + 1; Outstanding = FlushOutstanding(l,Outstanding,1); sys.stdout.flush(); if NoAct == 0: FlushOutstanding(l,Outstanding); if Keys.close() != None: raise "Error","GPG failed" print Ignored,"keys already in the directory (ignored)"; # Look for unmatched keys for x in KeyMap.keys(): if KeyMap[x][1] == 0: print "key 0x",x,"belonging to",KeyMap[x][0],"removed"; if KeyCount.has_key(KeyMap[x][0]) : KeyCount[KeyMap[x][0]] = KeyCount[KeyMap[x][0]] - 1 if KeyCount[KeyMap[x][0]] <= 0: print "**",KeyMap[x][0],"no longer has any keys"; if NoAct == 0: l.modify_s("uid="+KeyMap[x][0]+","+BaseDn,\ [(ldap.MOD_DELETE,"keyfingerprint",x)]);