~ubuntu-branches/ubuntu/maverick/pyca/maverick

« back to all changes in this revision

Viewing changes to bin/certs2ldap.py

  • Committer: Bazaar Package Importer
  • Author(s): Lars Bahner
  • Date: 2003-12-02 19:39:35 UTC
  • Revision ID: james.westby@ubuntu.com-20031202193935-fzzt289mntvy6a8q
Tags: upstream-20031118
ImportĀ upstreamĀ versionĀ 20031118

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
"""
 
4
certs2ldap.py - Upload all EE certificates on LDAP server
 
5
(c) by Michael Stroeder, michael@stroeder.com
 
6
"""
 
7
 
 
8
__version__ = '0.6.6'
 
9
 
 
10
import sys, string, os, getopt
 
11
 
 
12
ldap_attrtype = {
 
13
  'ST':'st',
 
14
  'Email':'mail',
 
15
  'emailAddress':'mail',
 
16
  'E':'mail',
 
17
  'L':'l',
 
18
  'O':'o',
 
19
  'OU':'ou',
 
20
  'CN':'cn',
 
21
}
 
22
 
 
23
def findoption(options,paramname):
 
24
  for i in options:
 
25
    if i[0]==paramname:
 
26
      return i
 
27
  return ()
 
28
 
 
29
 
 
30
def PrintUsage(ErrorMsg='',ErrorCode=1):
 
31
  script_name = string.split(sys.argv[0],os.sep)[-1]
 
32
  sys.stderr.write("""*** %s *** (C) by Michael Stroeder, 1999
 
33
 
 
34
usage: %s [options]
 
35
 
 
36
Options:
 
37
 
 
38
  -h or --help
 
39
        Print out this message
 
40
 
 
41
  --config=[pathname]
 
42
        Pathname of OpenSSL configuration file.
 
43
        You may also use env variable OPENSSL_CONF.
 
44
        Default: /etc/openssl/openssl.cnf
 
45
 
 
46
  --pycalib=[directory]
 
47
        Specify directory containing the pyCA modules
 
48
        You may also use env variable PYCALIB.
 
49
        Default: /usr/local/pyca/pylib
 
50
 
 
51
  --host=[LDAP host]
 
52
        Specify an alternate host:port on which the ldap server
 
53
        is running.
 
54
        Default: localhost:389
 
55
 
 
56
  --basedn=[searchbase]
 
57
        Use searchbase as the starting point for the search
 
58
        instead of the default.
 
59
        Default: emtpy string
 
60
 
 
61
  --binddn=[binddn]
 
62
        Use binddn to bind to the LDAP directory.
 
63
        Default: cn=root[,searchbase]
 
64
 
 
65
  --bindpasswd=[password]
 
66
        Use password to bind to the LDAP directory. For security
 
67
        reasons it is better to set this with the env variable
 
68
        LDAP_PASSWD if you really have to provide the password
 
69
        in a non-interactive script.
 
70
        Default: emtpy string
 
71
 
 
72
  --filtertemplate=[Python dict string]
 
73
        A Python string used as template for searching the
 
74
        LDAP entries of certificate owners.
 
75
        E.g. (&(cn=%%(CN)s)(mail=%%(Email)s))
 
76
        Default: (mail=%%(Email)s)
 
77
 
 
78
  --certdnfilter=[regex]
 
79
        Specify a filter as comma separated list of regular expressions
 
80
        for DNs of the certificates which should be sent to the LDAP host.
 
81
        E.g. C=DE,CN=.*,Email=.*@domain.my
 
82
        Default: Email=.*
 
83
 
 
84
  --objectclasses=[objectClass]
 
85
        Add objectclass: [objectClass] to the entry. Might be
 
86
        a comma-separated list for specifying multiple object classes.
 
87
 
 
88
  --replace
 
89
        Replace existing userCertificate;binary attributes
 
90
 
 
91
  --create
 
92
        Create LDAP entries if no entry for a user certificate
 
93
        was found.
 
94
 
 
95
  --dntemplate=[Python dict string]
 
96
        A Python string used as template for the distinguished
 
97
        name of LDAP entries to be created.
 
98
        E.g. cn=%%(CN)s+mail=%%(Email)s,ou=Testing,dc=stroeder,dc=com
 
99
 
 
100
""" % (script_name,script_name))
 
101
  if ErrorMsg:
 
102
    sys.stderr.write('Error: %s\n' % ErrorMsg)
 
103
  sys.exit(ErrorCode)
 
104
 
 
105
script_name=sys.argv[0]
 
106
 
 
107
try:
 
108
  options,args=getopt.getopt(
 
109
    sys.argv[1:],'h',
 
110
    [
 
111
      'help',
 
112
      'config=',
 
113
      'pycalib=',
 
114
      'host=',
 
115
      'basedn=',
 
116
      'binddn=',
 
117
      'bindpasswd=',
 
118
      'filtertemplate=',
 
119
      'certdnfilter=',
 
120
      'objectclasses=',
 
121
      'replace',
 
122
      'create',
 
123
      'dntemplate='
 
124
    ]
 
125
  )
 
126
except getopt.error,e:
 
127
  PrintUsage(str(e))
 
128
 
 
129
if findoption(options,'-h')!=() or findoption(options,'--help')!=():
 
130
  PrintUsage()
 
131
 
 
132
if findoption(options,'--config')!=():
 
133
  opensslcnfname = findoption(options,'--config')[1]
 
134
else:
 
135
  opensslcnfname = os.environ.get('OPENSSL_CONF','/etc/openssl/openssl.cnf')
 
136
 
 
137
if not os.path.isfile(opensslcnfname):
 
138
  PrintUsage('Config file %s not found.' % (opensslcnfname))
 
139
  sys.exit(1)
 
140
 
 
141
if findoption(options,'--pycalib')!=():
 
142
  pycalib = findoption(options,'--pycalib')[1]
 
143
else:
 
144
  pycalib = os.environ.get('PYCALIB','/usr/local/pyca/pylib')
 
145
 
 
146
if not os.path.exists(pycalib) or not os.path.isdir(pycalib):
 
147
  PrintUsage('Directory %s with pyCA modules not found!' % (pycalib))
 
148
 
 
149
sys.path.append(pycalib)
 
150
 
 
151
try:
 
152
  import ldap
 
153
except ImportError:
 
154
  PrintUsage('python-ldap module not found.' % (pycalib))
 
155
  sys.exit(1)
 
156
 
 
157
try:
 
158
  import openssl,charset
 
159
except ImportError:
 
160
  PrintUsage('pyCA modules not found in directory %s.' % (pycalib))
 
161
  sys.exit(1)
 
162
 
 
163
# Read the configuration file
 
164
if os.path.isfile('%s.pickle' % (opensslcnfname)):
 
165
  # Try to read OpenSSL's config file from a pickled copy
 
166
  f=open('%s.pickle' % (opensslcnfname),'rb')
 
167
  try:
 
168
    # first try to use the faster cPickle module
 
169
    from cPickle import load
 
170
  except ImportError:
 
171
    from pickle import load
 
172
  opensslcnf=load(f)
 
173
  f.close()
 
174
else:
 
175
  # Parse OpenSSL's config file from source
 
176
  opensslcnf=openssl.cnf.OpenSSLConfigClass(opensslcnfname)
 
177
 
 
178
delete_reason = {openssl.db.DB_TYPE_EXP:'expired',openssl.db.DB_TYPE_REV:'revoked'}
 
179
 
 
180
pyca_section = opensslcnf.data.get('pyca',{})
 
181
openssl.bin_filename = pyca_section.get('OpenSSLExec','/usr/local/ssl/bin/openssl')
 
182
if not os.path.isfile(openssl.bin_filename):
 
183
  sys.stderr.write('Did not find OpenSSL executable %s.\n' % (openssl.bin_filename))
 
184
  sys.exit(1)
 
185
 
 
186
if findoption(options,'--host')!=():
 
187
  ldap_host = findoption(options,'--host')[1]
 
188
else:
 
189
  ldap_host = 'localhost:389'
 
190
 
 
191
if findoption(options,'--basedn')!=():
 
192
  basedn = findoption(options,'--basedn')[1]
 
193
else:
 
194
  basedn = ''
 
195
 
 
196
if findoption(options,'--binddn')!=():
 
197
  binddn = findoption(options,'--binddn')[1]
 
198
else:
 
199
  if basedn:
 
200
    binddn = 'cn=root,%s' % basedn
 
201
  else:
 
202
    binddn = 'cn=root'
 
203
 
 
204
if findoption(options,'--bindpasswd')!=():
 
205
  bindpasswd = findoption(options,'--bindpasswd')[1]
 
206
else:
 
207
  if os.environ.has_key('LDAP_PASSWD'):
 
208
    bindpasswd = os.environ.get['LDAP_PASSWD']
 
209
  else:
 
210
    from getpass import getpass
 
211
    sys.stdout.write('Enter password for bind DN "%s".\n' % (binddn))
 
212
    bindpasswd = getpass()
 
213
 
 
214
if findoption(options,'--filtertemplate')!=():
 
215
  filtertemplate = findoption(options,'--filtertemplate')[1]
 
216
else:
 
217
  filtertemplate = r'(mail=%(Email)s)'
 
218
 
 
219
if findoption(options,'--objectclasses')!=():
 
220
  objectclasses = map(
 
221
    string.strip,
 
222
    map(
 
223
      None,string.split(findoption(options,'--objectclasses')[1],',')
 
224
    )
 
225
  )
 
226
else:
 
227
  objectclasses = None
 
228
 
 
229
replace = findoption(options,'--replace')!=()
 
230
 
 
231
if findoption(options,'--certdnfilter')!=():
 
232
  certdnfilterlist = string.split(findoption(options,'--certdnfilter')[1],',')
 
233
  certdnfilter = {}
 
234
  for i in certdnfilterlist:
 
235
    attr,filter = string.split(i,'=',1)
 
236
    if filter:
 
237
      certdnfilter[attr]=filter
 
238
else:
 
239
  certdnfilter = {'Email':'.*'}
 
240
 
 
241
create = findoption(options,'--create')!=()
 
242
 
 
243
if findoption(options,'--dntemplate')!=():
 
244
  dntemplate = findoption(options,'--dntemplate')[1]
 
245
else:
 
246
  dntemplate = r'mail=%(Email)s,'+basedn
 
247
 
 
248
print repr(dntemplate)
 
249
 
 
250
# FIX ME!!!
 
251
# This should be surrounded by a nice try: except: clause
 
252
# which catches specific exceptions and outputs
 
253
# nicer error messages.
 
254
l = ldap.open(ldap_host)
 
255
l.bind_s(binddn,bindpasswd,ldap.AUTH_SIMPLE)
 
256
 
 
257
ca_names = opensslcnf.sectionkeys.get('ca',[])
 
258
 
 
259
old_db_filenames = []
 
260
 
 
261
for ca_name in ca_names:
 
262
 
 
263
  sys.stdout.write('*** Processing %s ***\n\n' % (ca_name))
 
264
 
 
265
  ca = opensslcnf.getcadata(ca_name)
 
266
 
 
267
  # Ist der Zertifikattyp 'S/MIME for client use' ?
 
268
  if ca.isclientcert() and \
 
269
     not ca.database in old_db_filenames and \
 
270
     os.path.isfile(ca.database):
 
271
 
 
272
    old_db_filenames.append(ca.database)
 
273
 
 
274
    # Anfrage starten
 
275
    certs_found = openssl.db.GetEntriesbyDN(ca.database,certdnfilter,casesensitive=1,onlyvalid=0)
 
276
 
 
277
    for cert_entry in certs_found:
 
278
 
 
279
      certdn = charset.asn12iso(cert_entry[openssl.db.DB_name])
 
280
      certdndict = openssl.db.SplitDN(charset.iso2utf(certdn))
 
281
      ldap_filter = filtertemplate % certdndict
 
282
      try:
 
283
        ldap_result = l.search_s(
 
284
          basedn,
 
285
          ldap.SCOPE_SUBTREE,
 
286
          ldap_filter,
 
287
          ['objectclass','userCertificate;binary','userSMIMECertificate;binary'],
 
288
          0
 
289
        )
 
290
      except ldap.NO_SUCH_OBJECT:
 
291
        sys.stdout.write('Certificate subject "%s" not found with filter "%s".\n' % (certdn,ldap_filter))
 
292
        ldap_result=[]
 
293
      except:
 
294
        exc_obj,exc_value,exc_traceback = sys.exc_info()
 
295
        sys.stderr.write('Unexpected error during searching with filter "%s":\n%s\n' % (ldap_filter,exc_value))
 
296
        sys.exit(1)
 
297
 
 
298
      if ldap_result:
 
299
 
 
300
        # Read certificate data
 
301
        certfilename = os.path.join(ca.certs,'%s.pem' % (cert_entry[openssl.db.DB_serial]))
 
302
        cert = openssl.cert.X509CertificateClass(certfilename)
 
303
        local_cert = cert.readcertfile('der')
 
304
 
 
305
        for entry in ldap_result:
 
306
 
 
307
          ldap_dn = entry[0]
 
308
 
 
309
          old_objectclasses = {}
 
310
          for oc in entry[1].get('objectClass',entry[1].get('objectclass',[])):
 
311
            old_objectclasses[string.lower(oc)] = None
 
312
 
 
313
          existing_usercert_attrtype = None
 
314
          for a in [
 
315
            'userCertificate;binary','userCertificate',
 
316
            'usercertificate;binary','usercertificate',
 
317
          ]:
 
318
            if entry[1].has_key(a):
 
319
              existing_usercert_attrtype = a
 
320
              break
 
321
 
 
322
          old_usercertificate_attr = {}
 
323
          if existing_usercert_attrtype!=None:
 
324
            for ldap_cert in entry[1][existing_usercert_attrtype]:
 
325
              old_usercertificate_attr[ldap_cert] = None
 
326
 
 
327
          ldap_modlist = []
 
328
 
 
329
          if cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_VAL:
 
330
 
 
331
            if existing_usercert_attrtype is None:
 
332
              # Add new certificate attribute
 
333
              ldap_modlist.append((ldap.MOD_ADD,'userCertificate;binary',[local_cert]))
 
334
              sys.stdout.write('Adding new certificate attribute usercertificate;binary with certificate serial %s of LDAP entry "%s".\n' % (cert_entry[openssl.db.DB_serial],charset.utf2iso(ldap_dn)))
 
335
            elif replace:
 
336
              # Replace existing certificate attribute
 
337
              ldap_modlist.append((ldap.MOD_DELETE,existing_usercert_attrtype,None))
 
338
              ldap_modlist.append((ldap.MOD_ADD,existing_usercert_attrtype,[local_cert]))
 
339
              sys.stdout.write('Replacing attribute %s of entry %s with certificate serial %s.\n' % (
 
340
                  existing_usercert_attrtype,
 
341
                  charset.utf2iso(ldap_dn),
 
342
                  cert_entry[openssl.db.DB_serial]
 
343
                )
 
344
              )
 
345
            elif not old_usercertificate_attr.has_key(local_cert):
 
346
              # Add new certificate attribute value
 
347
              ldap_modlist.append((ldap.MOD_DELETE,existing_usercert_attrtype,None))
 
348
              ldap_modlist.append((ldap.MOD_ADD,existing_usercert_attrtype,old_usercertificate_attr.keys()+[local_cert]))
 
349
              sys.stdout.write(
 
350
                'Adding certificate with certificate serial %s to existing attribute %s of LDAP entry "%s".\n' % (
 
351
                  cert_entry[openssl.db.DB_serial],
 
352
                  existing_usercert_attrtype,
 
353
                  charset.utf2iso(ldap_dn)
 
354
                )
 
355
              )
 
356
            else:
 
357
              sys.stdout.write('Leaving attribute %s of entry %s untouched.\n' % (
 
358
                  existing_usercert_attrtype,
 
359
                  charset.utf2iso(ldap_dn)
 
360
                )
 
361
              )
 
362
 
 
363
            if ldap_modlist and objectclasses:
 
364
              # New object classes were specified at command-line
 
365
              # => add to modify list if necessary
 
366
              new_objectclasses = []
 
367
              for oc in objectclasses:
 
368
                if not old_objectclasses.has_key(string.lower(oc)):
 
369
                  new_objectclasses.append(oc)
 
370
              if new_objectclasses:
 
371
                ldap_modlist.append((ldap.MOD_ADD,'objectClass',new_objectclasses))
 
372
 
 
373
 
 
374
          elif (cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_EXP) or \
 
375
               (cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_REV):
 
376
 
 
377
            sys.stdout.write('Certificate (serial %s) %s.\n' % (cert_entry[openssl.db.DB_serial],delete_reason[cert_entry[openssl.db.DB_type]]))
 
378
 
 
379
            if old_usercertificate_attr.has_key(local_cert):
 
380
              del old_usercertificate_attr[local_cert]
 
381
              sys.stdout.write('Deleting certificate with certificate serial %s from attribute usercertificate;binary of LDAP entry "%s".\n' % (cert_entry[openssl.db.DB_serial],charset.utf2iso(ldap_dn)))
 
382
              ldap_modlist.append((ldap.MOD_REPLACE,existing_usercert_attrtype,old_usercertificate_attr.keys()))
 
383
 
 
384
            if ldap_modlist and objectclasses:
 
385
              new_objectclasses = []
 
386
              for oc in objectclasses:
 
387
                if old_objectclasses.has_key(string.lower(oc)):
 
388
                  new_objectclasses.append(oc)
 
389
              if new_objectclasses:
 
390
                ldap_modlist.append((ldap.MOD_DELETE,'objectClass',new_objectclasses))
 
391
 
 
392
 
 
393
          # Do modifications on directory if modlist is not empty
 
394
          if ldap_modlist:
 
395
            try:
 
396
              l.modify_s(ldap_dn,ldap_modlist)
 
397
            except ldap.NO_SUCH_OBJECT:
 
398
              sys.stderr.write('No such object "%s": Probably a parent entry is missing.\n' % (
 
399
                  charset.utf2iso(ldap_dn)
 
400
                )
 
401
              )
 
402
            except ldap.INSUFFICIENT_ACCESS,e:
 
403
              sys.stderr.write('You are not allowed to modify entry "%s": %s.\n' % (
 
404
                  charset.utf2iso(ldap_dn),str(e)
 
405
                )
 
406
              )
 
407
            except ldap.LDAPError,e:
 
408
              sys.stderr.write('LDAPError: %s.\n' % str(e))
 
409
 
 
410
      else:
 
411
 
 
412
        if cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_VAL:
 
413
 
 
414
          if create:
 
415
 
 
416
            # Read certificate data
 
417
            certfilename = os.path.join(ca.certs,'%s.pem' % (cert_entry[openssl.db.DB_serial]))
 
418
            cert = openssl.cert.X509CertificateClass(certfilename)
 
419
            local_cert = cert.readcertfile('der')
 
420
 
 
421
            ldap_dn = dntemplate % certdndict
 
422
 
 
423
            ldap_modlist = [
 
424
              ('objectClass',['person','organizationalPerson','inetOrgPerson']),
 
425
              ('userCertificate;binary',[local_cert]),
 
426
              ('sn',['-'])
 
427
            ]
 
428
            for k in certdndict.keys():
 
429
              try:
 
430
                ldap_modlist.append((ldap_attrtype[k],certdndict[k]))
 
431
              except KeyError:
 
432
                pass
 
433
 
 
434
            try:
 
435
#              print ldap_modlist
 
436
              l.add_s(ldap_dn,ldap_modlist)
 
437
            except ldap.NO_SUCH_OBJECT:
 
438
              sys.stderr.write('No such object "%s": Probably a parent entry is missing.\n' % (
 
439
                  charset.utf2iso(ldap_dn)
 
440
                )
 
441
              )
 
442
            except ldap.INSUFFICIENT_ACCESS,e:
 
443
              sys.stderr.write('You are not allowed to add entry "%s": %s.\n' % (
 
444
                  charset.utf2iso(ldap_dn),str(e)
 
445
                )
 
446
              )
 
447
            except ldap.LDAPError,e:
 
448
              sys.stderr.write('LDAPError: %s.\n' % str(e))
 
449
            else:
 
450
              sys.stdout.write('Added new entry "%s" for certificate serial %s.\n' % (charset.utf2iso(ldap_dn),cert_entry[openssl.db.DB_serial]))
 
451
            
 
452
          else:
 
453
            sys.stderr.write('No entry found with filter "%s" for %s.\n' % (ldap_filter,certdn))     
 
454
 
 
455
l.unbind_s()