This is some fine documentation
[mirror/userdir-ldap.git] / ud-gpgimport
1 #!/usr/bin/env python
2 # -*- mode: python -*-
3
4 #   Copyright (c) 1999-2000  Jason Gunthorpe <jgg@debian.org>
5 #   Copyright (c) 2004       Joey Schulze <joey@debian.org>
6 #   Copyright (c) 2008, 2009, 2010 Peter Palfrader <peter@palfrader.org>
7 #   Copyright (c) 2010       Martin Zobel-Helas <zobel@debian.org>
8 #
9 #   This program is free software; you can redistribute it and/or modify
10 #   it under the terms of the GNU General Public License as published by
11 #   the Free Software Foundation; either version 2 of the License, or
12 #   (at your option) any later version.
13 #
14 #   This program is distributed in the hope that it will be useful,
15 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 #   GNU General Public License for more details.
18 #
19 #   You should have received a copy of the GNU General Public License
20 #   along with this program; if not, write to the Free Software
21 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
23 # This script tries to match key fingerprints from a keyring with user
24 # name in a directory. When an unassigned key is found a heuristic match
25 # against the keys given cn/sn and the directory is performed to try to get
26 # a matching. Generally this works about 90% of the time, matching is fairly
27 # strict. In the event a non-match a fuzzy sounds-alike search is performed
28 # and the results printed to aide the user.
29 #
30 # GPG is automatically invoked with the correct magic special options,
31 # pass the names of all the valid key rings on the command line.
32 #
33 # The output report will list what actions were taken. Keys that are present
34 # in the directory but not in the key ring will be removed from the 
35 # directory. 
36
37 import re, time, ldap, getopt, sys, pwd, os;
38 from userdir_ldap import *;
39 from userdir_gpg import *;
40
41 # This map deals with people who put the wrong sort of stuff in their pgp
42 # key entries
43 UnknownMap = {};
44 NoAct = 1;
45
46 # Read the override file into the unknown map. The override file is a list
47 # of colon delimited entires mapping PGP email addresess to local users
48 def LoadOverride(File):
49    List = open(File,"r");
50    while(1):
51       Line = List.readline();
52       if Line == "":
53          break;
54       Split = re.split("[:\n]",Line);
55       UnknownMap[Split[0]] = Split[1].strip()
56
57
58 def load_keys_from_gpg(keyrings):
59    keys = {}
60
61    # Popen GPG with the correct magic special options
62    ClearKeyrings()
63    SetKeyrings(keyrings)
64
65    Args = [GPGPath] + GPGBasicOptions + GPGKeyRings + GPGSearchOptions + [" 2> /dev/null"]
66    Keys = os.popen(" ".join(Args),"r");
67
68    # Loop over the GPG key file
69    Outstanding = 0;
70    while(1):
71       Line = Keys.readline();
72       if Line == "":
73          break;
74
75       Split = Line.split(":")
76       if len(Split) < 8 or Split[0] != "pub":
77          continue;
78
79       while (1):
80           Line2 = Keys.readline();
81           if Line2 == "":
82              break;
83           Split2 = Line2.split(":");
84           if len(Split2) < 11 or Split2[0] != "fpr":
85              continue;
86           break;
87       if Line2 == "":
88          break;
89
90       pgp_uid = Split[9]
91       fingerprint = Split2[9]
92
93       if fingerprint in keys:
94          print "Duplicate key in keyrings: %s, belonging to %s"%(fingerprint, pgp_uid)
95          continue
96       keys[fingerprint] = pgp_uid
97
98    if Keys.close() != None:
99       raise "Error","GPG failed"
100
101    return keys
102
103
104
105
106
107
108 # Process options
109 AdminUser = pwd.getpwuid(os.getuid())[0];
110 (options, arguments) = getopt.getopt(sys.argv[1:], "ahu:m:")
111 for (switch, val) in options:
112    if (switch == '-u'):
113       AdminUser = val
114    elif (switch == '-m'):
115        LoadOverride(val);
116    elif (switch == '-h'):
117        print "Usage: ud-gpgimport <options>"
118        print "Available options:"
119        print "        -h         Show this help"
120        print "        -u=<user>  Admin user (defaults to current username)"
121        print "        -m=<file>  Override file to use"
122        print "        -a         actually do changes, not dry-run"
123        sys.exit(0)
124    elif (switch == '-a'):
125        NoAct = 0;
126
127
128 # Main program starts here
129
130 # Connect to the ldap server
131 if NoAct == 0:
132    l = passwdAccessLDAP(BaseDn, AdminUser)
133 else:
134    l = connectLDAP()
135    l.simple_bind_s("","");
136
137 # Download the existing key list and put it into a map
138 print "Fetching key list..",
139 sys.stdout.flush();
140 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyFingerPrint=*",["keyFingerPrint","uid"]);
141 KeyMap = {};
142 KeyCount = {};
143 for x in Attrs:
144   try:
145      # Sense a bad fingerprint.. Slapd has problems, it will store a null
146      # value that ldapsearch doesn't show up.. detect and remove
147      if len(x[1]["keyFingerPrint"]) == 0 or x[1]["keyFingerPrint"][0] == "":
148        print;
149        print "Fixing bad fingerprint for",x[1]["uid"][0],
150        sys.stdout.flush();
151        if NoAct == 0:
152          l.modify_s("uid="+x[1]["uid"][0]+","+BaseDn,\
153                      [(ldap.MOD_DELETE,"keyFingerPrint",None)]);
154      else:
155        for I in x[1]["keyFingerPrint"]:
156          KeyMap[I] = [x[1]["uid"][0],0];
157          if KeyCount.has_key(x[1]["uid"][0]):
158             KeyCount[x[1]["uid"][0]] = KeyCount[x[1]["uid"][0]] + 1;
159          else:
160             KeyCount[x[1]["uid"][0]] = 1;
161   except:
162      continue;
163 Attrs = None;
164 print;
165
166
167 pgpkeys = load_keys_from_gpg( ConfModule.add_keyrings.split(":") )
168 pgpkeys_extra = load_keys_from_gpg( ConfModule.add_keyrings_guest.split(":") )
169
170 Ignored = 0;
171 for fpr in pgpkeys:
172    pgp_uid = pgpkeys[fpr]
173    if fpr in KeyMap:
174       Ignored = Ignored + 1;
175       # print "Ignoring keyID",fpr,"belonging to",KeyMap[fpr][0];
176       KeyMap[fpr][1] = 1;
177       continue;
178
179    UID = GetUID(l,SplitEmail(pgp_uid),UnknownMap);
180    if UID[0] == None:
181       print "Unassigned key in keyrings: %s, belonging to %s"%(fpr, pgp_uid)
182       if UID[1] != None:
183          for x in UID[1]: print x;
184       print "MISSING " + fpr;
185       continue;
186
187    UID = UID[0]
188    Rec = [(ldap.MOD_ADD,"keyFingerPrint",fpr)];
189    Dn = "uid=" + UID + "," + BaseDn;
190    print "Adding key "+fpr,"to",UID;
191    if KeyCount.has_key(UID):
192       KeyCount[UID] = KeyCount[UID] + 1;
193    else:
194       KeyCount[UID] = 1;
195
196    if NoAct == 1:
197       continue;
198
199    # Send the modify request
200    l.modify(Dn,Rec);
201    Outstanding = Outstanding + 1;
202    Outstanding = FlushOutstanding(l,Outstanding,1);
203    sys.stdout.flush();
204
205 if NoAct == 0:
206    FlushOutstanding(l,Outstanding);
207
208 print Ignored,"keys already in the directory (ignored)";
209
210 # Look for unmatched keys
211 for x in KeyMap.keys():
212    if KeyMap[x][1] == 0 and not x in pgpkeys_extra:
213       print "key %s belonging to %s removed"%(x,KeyMap[x][0]);
214       if KeyCount.has_key(KeyMap[x][0]) :
215          KeyCount[KeyMap[x][0]] = KeyCount[KeyMap[x][0]] - 1
216          if KeyCount[KeyMap[x][0]] <= 0:
217             print "**",KeyMap[x][0],"no longer has any keys";
218       if NoAct == 0:
219          l.modify_s("uid="+KeyMap[x][0]+","+BaseDn,\
220                      [(ldap.MOD_DELETE,"keyFingerPrint",x)]);
221
222 # vim:set et:
223 # vim:set ts=3:
224 # vim:set shiftwidth=3: