ruby-ldap 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +24 -0
- data/ChangeLog +762 -0
- data/FAQ +62 -0
- data/NOTES +77 -0
- data/README +266 -0
- data/TODO +15 -0
- data/conn.c +1810 -0
- data/entry.c +215 -0
- data/extconf.rb +268 -0
- data/ldap.c +577 -0
- data/lib/ldap/control.rb +50 -0
- data/lib/ldap/ldif.rb +569 -0
- data/lib/ldap/schema.rb +129 -0
- data/misc.c +512 -0
- data/mod.c +355 -0
- data/rbldap.h +194 -0
- data/saslconn.c +176 -0
- data/sslconn.c +377 -0
- data/test/add.rb +31 -0
- data/test/add2.rb +31 -0
- data/test/add3.rb +33 -0
- data/test/bind-ldaps.rb +25 -0
- data/test/bind-sasl.rb +17 -0
- data/test/bind-ssl.rb +25 -0
- data/test/bind.rb +34 -0
- data/test/compare.rb +17 -0
- data/test/conf.rb +12 -0
- data/test/delete.rb +13 -0
- data/test/ext.rb +49 -0
- data/test/misc1.rb +49 -0
- data/test/misc2.rb +40 -0
- data/test/modrdn.rb +23 -0
- data/test/search.rb +20 -0
- data/test/search2.rb +34 -0
- data/test/search3.rb +23 -0
- data/test/setup.rb +38 -0
- data/test/subschema.rb +21 -0
- data/test/tc_conn.rb +124 -0
- data/test/tc_ldif.rb +174 -0
- data/test/tc_schema.rb +32 -0
- data/test/tc_search.rb +137 -0
- data/test/ts_ldap.rb +8 -0
- data/win/winlber.h +21 -0
- data/win/winldap.h +324 -0
- metadata +100 -0
data/lib/ldap/control.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Manipulation of LDAP control data.
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# $Id: control.rb,v 1.2 2005/02/28 05:02:25 ianmacd Exp $
|
5
|
+
#++
|
6
|
+
#
|
7
|
+
# Copyright (C) 2004 Ian Macdonald <ian@caliban.org>
|
8
|
+
#
|
9
|
+
|
10
|
+
module LDAP
|
11
|
+
class Control
|
12
|
+
|
13
|
+
require 'openssl'
|
14
|
+
|
15
|
+
# Take +vals+, produce an Array of values in ASN.1 format and then
|
16
|
+
# convert the Array to DER.
|
17
|
+
#
|
18
|
+
def Control.encode( *vals )
|
19
|
+
encoded_vals = []
|
20
|
+
|
21
|
+
vals.each do |val|
|
22
|
+
encoded_vals <<
|
23
|
+
case val
|
24
|
+
when Integer
|
25
|
+
OpenSSL::ASN1::Integer( val )
|
26
|
+
when String
|
27
|
+
OpenSSL::ASN1::OctetString.new( val )
|
28
|
+
else
|
29
|
+
# What other types may exist?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
OpenSSL::ASN1::Sequence.new( encoded_vals ).to_der
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Take an Array of ASN.1 data and return an Array of decoded values.
|
38
|
+
#
|
39
|
+
def decode
|
40
|
+
values = []
|
41
|
+
|
42
|
+
OpenSSL::ASN1::decode( self.value ).value.each do |val|
|
43
|
+
values << val.value
|
44
|
+
end
|
45
|
+
|
46
|
+
values
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/ldap/ldif.rb
ADDED
@@ -0,0 +1,569 @@
|
|
1
|
+
# Manipulation of LDIF data.
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# $Id: ldif.rb,v 1.11 2005/03/03 01:32:07 ianmacd Exp $
|
5
|
+
#++
|
6
|
+
#
|
7
|
+
# Copyright (C) 2005 Ian Macdonald <ian@caliban.org>
|
8
|
+
#
|
9
|
+
|
10
|
+
module LDAP
|
11
|
+
|
12
|
+
# Record objects are embodiments of LDAP operations. They possess a DN,
|
13
|
+
# a change type (*LDAP_MOD_ADD*, *LDAP_MOD_DELETE* or *LDAP_MOD_REPLACE*
|
14
|
+
# [any of which can be logically AND'ed with *LDAP_MOD_BVALUES*]), a hash of
|
15
|
+
# attributes and value arrays, a hash of modification operations (useful
|
16
|
+
# only when the change type is *LDAP_MOD_REPLACE*) and an array of
|
17
|
+
# LDAP controls.
|
18
|
+
#
|
19
|
+
# The Record class's primary use is as a transitional medium for LDIF
|
20
|
+
# operations parsed by the LDAP::LDIF module. You are unlikely to want to
|
21
|
+
# use it in application code.
|
22
|
+
#
|
23
|
+
class Record
|
24
|
+
attr_reader :dn, :change_type, :attrs, :mods, :controls
|
25
|
+
|
26
|
+
def initialize(dn, change_type, attrs, mods=nil, ctls=nil)
|
27
|
+
@dn = dn
|
28
|
+
@change_type = change_type
|
29
|
+
@attrs = attrs
|
30
|
+
@mods = mods
|
31
|
+
@controls = ctls
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Send the operation embodied in the Record object to the LDAP::Conn
|
36
|
+
# object specified in +conn+.
|
37
|
+
#
|
38
|
+
def send( conn )
|
39
|
+
if @change_type == :MODRDN
|
40
|
+
# TODO: How do we deal with 'newsuperior'?
|
41
|
+
# The LDAP API's ldap_modrdn2_s() function doesn't seem to use it.
|
42
|
+
return conn.modrdn( @dn, @attrs['newrdn'], @attrs['deleteoldrdn'] )
|
43
|
+
end
|
44
|
+
|
45
|
+
# Mask out the LDAP_MOD_BVALUES bit, as it's irrelevant here.
|
46
|
+
case @change_type & ~LDAP_MOD_BVALUES
|
47
|
+
when LDAP_MOD_ADD
|
48
|
+
@controls == [] ? conn.add( @dn, @attrs ) :
|
49
|
+
conn.add_ext( @dn, @attrs, @controls, [] )
|
50
|
+
when LDAP_MOD_DELETE
|
51
|
+
@controls == [] ? conn.delete( @dn ) :
|
52
|
+
conn.delete_ext( @dn, @controls, [] )
|
53
|
+
when LDAP_MOD_REPLACE
|
54
|
+
@controls == [] ? conn.modify( @dn, @mods ) :
|
55
|
+
conn.modify_ext( @dn, @mods, @controls, [] )
|
56
|
+
end
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Remove common operational attributes from a Record object. This is
|
63
|
+
# useful if you have Record objects formed from LDIF data that contained
|
64
|
+
# operational attributes. Using LDAP::Record#send to send such an object
|
65
|
+
# to an LDAP server is likely to meet with an exception unless the data is
|
66
|
+
# first cleaned.
|
67
|
+
#
|
68
|
+
# In addition, attributes with duplicate values are pruned, as this can
|
69
|
+
# also result in an exception.
|
70
|
+
#
|
71
|
+
def clean
|
72
|
+
|
73
|
+
# TODO: These operational attributes are those commonly used by
|
74
|
+
# OpenLDAP 2.2. Others should probably be supported.
|
75
|
+
#
|
76
|
+
%w[ creatorsname createtimestamp modifiersname modifytimestamp
|
77
|
+
entrycsn entryuuid structuralobjectclass ].each do |attr|
|
78
|
+
@attrs.delete( attr )
|
79
|
+
end
|
80
|
+
|
81
|
+
# Clean out duplicate attribute values.
|
82
|
+
@attrs.each_key { |k| @attrs[k].uniq! }
|
83
|
+
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# This module provides the ability to process LDIF entries and files.
|
91
|
+
#
|
92
|
+
module LDIF
|
93
|
+
LINE_LENGTH = 77
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
class Entry < String; end
|
98
|
+
class Mod < String; end
|
99
|
+
class LDIFError < LDAP::Error; end
|
100
|
+
|
101
|
+
|
102
|
+
# return *true* if +str+ contains a character with an ASCII value > 127 or
|
103
|
+
# a NUL, LF or CR. Otherwise, *false* is returned.
|
104
|
+
#
|
105
|
+
def LDIF.unsafe_char?( str )
|
106
|
+
# This could be written as a single regex, but this is faster.
|
107
|
+
str =~ /^[ :]/ || str =~ /[\x00-\x1f\x7f-\xff]/
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Perform Base64 decoding of +str+. If +concat+ is *true*, LF characters
|
112
|
+
# are stripped.
|
113
|
+
#
|
114
|
+
def LDIF.base64_encode( str, concat=false )
|
115
|
+
str = [ str ].pack( 'm' )
|
116
|
+
str.gsub!( /\n/, '' ) if concat
|
117
|
+
str
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Perform Base64 encoding of +str+.
|
122
|
+
#
|
123
|
+
def LDIF.base64_decode( str )
|
124
|
+
str.unpack( 'm*' )[0]
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Read a file from the URL +url+. At this time, the only type of URL
|
129
|
+
# supported is the +file://+ URL.
|
130
|
+
#
|
131
|
+
def LDIF.read_file( url )
|
132
|
+
unless url.sub!( %r(^file://), '' )
|
133
|
+
raise ArgumentError, "Bad external file reference: #{url}"
|
134
|
+
end
|
135
|
+
|
136
|
+
# Slurp an external file.
|
137
|
+
# TODO: Support other URL types in the future.
|
138
|
+
File.open( url ).readlines( nil )[0]
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# This converts an attribute and array of values to LDIF.
|
143
|
+
#
|
144
|
+
def LDIF.to_ldif( attr, vals )
|
145
|
+
ldif = ''
|
146
|
+
|
147
|
+
vals.each do |val|
|
148
|
+
sep = ':'
|
149
|
+
if unsafe_char?( val )
|
150
|
+
sep = '::'
|
151
|
+
val = base64_encode( val, true )
|
152
|
+
end
|
153
|
+
|
154
|
+
firstline_len = LINE_LENGTH - ( "%s%s " % [ attr, sep ] ).length
|
155
|
+
ldif << "%s%s %s\n" % [ attr, sep, val.slice!( 0..firstline_len ) ]
|
156
|
+
|
157
|
+
while val.length > 0
|
158
|
+
ldif << " %s\n" % val.slice!( 0..LINE_LENGTH - 1 )
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
ldif
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
public
|
168
|
+
|
169
|
+
|
170
|
+
# Parse the LDIF entry contained in +lines+ and return an LDAP::Record
|
171
|
+
# object. +lines+ should be an object that responds to each, such as a
|
172
|
+
# string or an array of lines, separated by \n characters.
|
173
|
+
#
|
174
|
+
def LDIF.parse_entry( lines )
|
175
|
+
header = true
|
176
|
+
comment = false
|
177
|
+
change_type = nil
|
178
|
+
sep = nil
|
179
|
+
attr = nil
|
180
|
+
bvalues = []
|
181
|
+
controls = nil
|
182
|
+
hash = {}
|
183
|
+
mods = {}
|
184
|
+
mod_type = nil
|
185
|
+
|
186
|
+
lines.each do |line|
|
187
|
+
# Skip (continued) comments.
|
188
|
+
if line =~ /^#/ || ( comment && line[0..0] == ' ' )
|
189
|
+
comment = true
|
190
|
+
next
|
191
|
+
end
|
192
|
+
|
193
|
+
# Skip blank lines.
|
194
|
+
next if line =~ /^$/
|
195
|
+
|
196
|
+
# Reset mod type if this entry has more than one mod to make.
|
197
|
+
# A '-' continuation is only valid if we've already had a
|
198
|
+
# 'changetype: modify' line.
|
199
|
+
if line =~ /^-$/ && change_type == LDAP_MOD_REPLACE
|
200
|
+
next
|
201
|
+
end
|
202
|
+
|
203
|
+
line.chomp!
|
204
|
+
|
205
|
+
# N.B. Attributes and values can be separated by one or two colons,
|
206
|
+
# or one colon and a '<'. Either of these is then followed by zero
|
207
|
+
# or one spaces.
|
208
|
+
if md = line.match( /^[^ ].*?((:[:<]?) ?)/ )
|
209
|
+
|
210
|
+
# If previous value was Base64-encoded and is not continued,
|
211
|
+
# we need to decode it now.
|
212
|
+
if sep == '::'
|
213
|
+
if mod_type
|
214
|
+
mods[mod_type][attr][-1] =
|
215
|
+
base64_decode( mods[mod_type][attr][-1] )
|
216
|
+
bvalues << attr if unsafe_char?( mods[mod_type][attr][-1] )
|
217
|
+
else
|
218
|
+
hash[attr][-1] = base64_decode( hash[attr][-1] )
|
219
|
+
bvalues << attr if unsafe_char?( hash[attr][-1] )
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
# Found a attr/value line.
|
225
|
+
attr, val = line.split( md[1], 2 )
|
226
|
+
attr.downcase!
|
227
|
+
|
228
|
+
# Attribute must be ldap-oid / (ALPHA *(attr-type-chars))
|
229
|
+
if attr !~ /^(?:(?:\d+\.)*\d+|[[:alnum:]-]+)(?:;[[:alnum:]-]+)*$/
|
230
|
+
raise LDIFError, "Invalid attribute: #{attr}"
|
231
|
+
end
|
232
|
+
|
233
|
+
if attr == 'dn'
|
234
|
+
header = false
|
235
|
+
change_type = nil
|
236
|
+
controls = []
|
237
|
+
end
|
238
|
+
sep = md[2]
|
239
|
+
|
240
|
+
val = read_file( val ) if sep == ':<'
|
241
|
+
|
242
|
+
case attr
|
243
|
+
when 'version'
|
244
|
+
# Check the LDIF version.
|
245
|
+
if header
|
246
|
+
if val != '1'
|
247
|
+
raise LDIFError, "Unsupported LDIF version: #{val}"
|
248
|
+
else
|
249
|
+
header = false
|
250
|
+
next
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
when 'changetype'
|
255
|
+
change_type = case val
|
256
|
+
when 'add' then LDAP_MOD_ADD
|
257
|
+
when 'delete' then LDAP_MOD_DELETE
|
258
|
+
when 'modify' then LDAP_MOD_REPLACE
|
259
|
+
when /^modr?dn$/ then :MODRDN
|
260
|
+
end
|
261
|
+
|
262
|
+
raise LDIFError, "Invalid change type: #{attr}" unless change_type
|
263
|
+
|
264
|
+
when 'add', 'delete', 'replace'
|
265
|
+
unless change_type == LDAP_MOD_REPLACE
|
266
|
+
raise LDIFError, "Cannot #{attr} here."
|
267
|
+
end
|
268
|
+
|
269
|
+
mod_type = case attr
|
270
|
+
when 'add' then LDAP_MOD_ADD
|
271
|
+
when 'delete' then LDAP_MOD_DELETE
|
272
|
+
when 'replace' then LDAP_MOD_REPLACE
|
273
|
+
end
|
274
|
+
|
275
|
+
mods[mod_type] ||= {}
|
276
|
+
mods[mod_type][val] ||= []
|
277
|
+
|
278
|
+
when 'control'
|
279
|
+
|
280
|
+
oid, criticality = val.split( / /, 2 )
|
281
|
+
|
282
|
+
unless oid =~ /(?:\d+\.)*\d+/
|
283
|
+
raise LDIFError, "Bad control OID: #{oid}"
|
284
|
+
end
|
285
|
+
|
286
|
+
if criticality
|
287
|
+
md = criticality.match( /(:[:<]?) ?/ )
|
288
|
+
ctl_sep = md[1] if md
|
289
|
+
criticality, value = criticality.split( /:[:<]? ?/, 2 )
|
290
|
+
|
291
|
+
if criticality !~ /^(?:true|false)$/
|
292
|
+
raise LDIFError, "Bad control criticality: #{criticality}"
|
293
|
+
end
|
294
|
+
|
295
|
+
# Convert 'true' or 'false'. to_boolean would be nice. :-)
|
296
|
+
criticality = eval( criticality )
|
297
|
+
end
|
298
|
+
|
299
|
+
if value
|
300
|
+
value = base64_decode( value ) if ctl_sep == '::'
|
301
|
+
value = read_file( value ) if ctl_sep == ':<'
|
302
|
+
value = Control.encode( value )
|
303
|
+
end
|
304
|
+
|
305
|
+
controls << Control.new( oid, value, criticality )
|
306
|
+
else
|
307
|
+
|
308
|
+
# Convert modrdn's deleteoldrdn from '1' to true, anything else
|
309
|
+
# to false. Should probably raise an exception if not '0' or '1'.
|
310
|
+
#
|
311
|
+
if change_type == :MODRDN && attr == 'deleteoldrdn'
|
312
|
+
val = val == '1' ? true : false
|
313
|
+
end
|
314
|
+
|
315
|
+
if change_type == LDAP_MOD_REPLACE
|
316
|
+
mods[mod_type][attr] << val
|
317
|
+
else
|
318
|
+
hash[attr] ||= []
|
319
|
+
hash[attr] << val
|
320
|
+
end
|
321
|
+
|
322
|
+
comment = false
|
323
|
+
|
324
|
+
# Make a note of this attribute if value is binary.
|
325
|
+
bvalues << attr if unsafe_char?( val )
|
326
|
+
end
|
327
|
+
|
328
|
+
else
|
329
|
+
|
330
|
+
# Check last line's separator: if not a binary value, the
|
331
|
+
# continuation line must be indented. If a comment makes it this
|
332
|
+
# far, that's also an error.
|
333
|
+
#
|
334
|
+
if sep == ':' && line[0..0] != ' ' || comment
|
335
|
+
raise LDIFError, "Improperly continued line: #{line}"
|
336
|
+
end
|
337
|
+
|
338
|
+
# OK; this is a valid continuation line.
|
339
|
+
|
340
|
+
# Append line except for initial space.
|
341
|
+
line[0] = '' if line[0..0] == ' '
|
342
|
+
|
343
|
+
if change_type == LDAP_MOD_REPLACE
|
344
|
+
# Append to last value of current mod type.
|
345
|
+
mods[mod_type][attr][-1] << line
|
346
|
+
else
|
347
|
+
# Append to last value.
|
348
|
+
hash[attr][-1] << line
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
|
354
|
+
# If last value in LDIF entry was Base64-encoded, we need to decode
|
355
|
+
# it now.
|
356
|
+
if sep == '::'
|
357
|
+
if mod_type
|
358
|
+
mods[mod_type][attr][-1] =
|
359
|
+
base64_decode( mods[mod_type][attr][-1] )
|
360
|
+
bvalues << attr if unsafe_char?( mods[mod_type][attr][-1] )
|
361
|
+
else
|
362
|
+
hash[attr][-1] = base64_decode( hash[attr][-1] )
|
363
|
+
bvalues << attr if unsafe_char?( hash[attr][-1] )
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Remove and remember DN.
|
368
|
+
dn = hash.delete( 'dn' )[0]
|
369
|
+
|
370
|
+
# This doesn't really matter, but let's be anal about it, because it's
|
371
|
+
# not an attribute and doesn't belong here.
|
372
|
+
bvalues.delete( 'dn' )
|
373
|
+
|
374
|
+
# If there's no change type, it's just plain LDIF data, so we'll treat
|
375
|
+
# it like an addition.
|
376
|
+
change_type ||= LDAP_MOD_ADD
|
377
|
+
|
378
|
+
case change_type
|
379
|
+
when LDAP_MOD_ADD
|
380
|
+
|
381
|
+
mods[LDAP_MOD_ADD] = []
|
382
|
+
|
383
|
+
hash.each do |attr_local, val|
|
384
|
+
if bvalues.include?( attr_local )
|
385
|
+
ct = LDAP_MOD_ADD | LDAP_MOD_BVALUES
|
386
|
+
else
|
387
|
+
ct = LDAP_MOD_ADD
|
388
|
+
end
|
389
|
+
|
390
|
+
mods[LDAP_MOD_ADD] << LDAP.mod( ct, attr_local, val )
|
391
|
+
end
|
392
|
+
|
393
|
+
when LDAP_MOD_DELETE
|
394
|
+
|
395
|
+
# Nothing to do.
|
396
|
+
|
397
|
+
when LDAP_MOD_REPLACE
|
398
|
+
|
399
|
+
raise LDIFError, "mods should not be empty" if mods == {}
|
400
|
+
|
401
|
+
new_mods = {}
|
402
|
+
|
403
|
+
mods.each do |mod_type_local,attrs|
|
404
|
+
attrs.each_key do |attr_local|
|
405
|
+
if bvalues.include?( attr_local )
|
406
|
+
mt = mod_type_local | LDAP_MOD_BVALUES
|
407
|
+
else
|
408
|
+
mt = mod_type_local
|
409
|
+
end
|
410
|
+
|
411
|
+
new_mods[mt] ||= {}
|
412
|
+
new_mods[mt][attr_local] = mods[mod_type_local][attr_local]
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
mods = new_mods
|
417
|
+
|
418
|
+
when :MODRDN
|
419
|
+
|
420
|
+
# Nothing to do.
|
421
|
+
|
422
|
+
end
|
423
|
+
|
424
|
+
Record.new( dn, change_type, hash, mods, controls )
|
425
|
+
end
|
426
|
+
|
427
|
+
|
428
|
+
# Open and parse a file containing LDIF entries. +file+ should be a string
|
429
|
+
# containing the path to the file. If +sort+ is true, the resulting array
|
430
|
+
# of LDAP::Record objects will be sorted on DN length, which can be useful
|
431
|
+
# to avoid a later attempt to process an entry whose parent does not yet
|
432
|
+
# exist. This can easily happen if your LDIF file is unordered, which is
|
433
|
+
# likely if it was produced with a tool such as <em>slapcat(8)</em>.
|
434
|
+
#
|
435
|
+
# If a block is given, each LDAP::Record object will be yielded to the
|
436
|
+
# block and *nil* will be returned instead of the array. This is much less
|
437
|
+
# memory-intensive when parsing a large LDIF file.
|
438
|
+
#
|
439
|
+
def LDIF.parse_file( file, sort=false ) # :yield: record
|
440
|
+
|
441
|
+
File.open( file ) do |f|
|
442
|
+
entries = []
|
443
|
+
entry = false
|
444
|
+
header = true
|
445
|
+
version = false
|
446
|
+
|
447
|
+
while line = f.gets
|
448
|
+
|
449
|
+
if line =~ /^dn:/
|
450
|
+
header = false
|
451
|
+
|
452
|
+
if entry && ! version
|
453
|
+
if block_given?
|
454
|
+
yield parse_entry( entry )
|
455
|
+
else
|
456
|
+
entries << parse_entry( entry )
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
if version
|
461
|
+
entry << line
|
462
|
+
version = false
|
463
|
+
else
|
464
|
+
entry = [ line ]
|
465
|
+
end
|
466
|
+
|
467
|
+
next
|
468
|
+
end
|
469
|
+
|
470
|
+
if header && line.downcase =~ /^version/
|
471
|
+
entry = [ line ]
|
472
|
+
version = true
|
473
|
+
next
|
474
|
+
end
|
475
|
+
|
476
|
+
entry << line
|
477
|
+
end
|
478
|
+
|
479
|
+
if block_given?
|
480
|
+
yield parse_entry( entry )
|
481
|
+
nil
|
482
|
+
else
|
483
|
+
entries << parse_entry( entry )
|
484
|
+
|
485
|
+
# Sort entries if sorting has been requested.
|
486
|
+
entries.sort! { |x,y| x.dn.length <=> y.dn.length } if sort
|
487
|
+
entries
|
488
|
+
end
|
489
|
+
|
490
|
+
end
|
491
|
+
|
492
|
+
end
|
493
|
+
|
494
|
+
|
495
|
+
# Given the DN, +dn+, convert a single LDAP::Mod or an array of
|
496
|
+
# LDAP::Mod objects, given in +mods+, to LDIF.
|
497
|
+
#
|
498
|
+
def LDIF.mods_to_ldif( dn, *mods )
|
499
|
+
ldif = "dn: %s\nchangetype: modify\n" % dn
|
500
|
+
plural = false
|
501
|
+
|
502
|
+
mods.flatten.each do |mod|
|
503
|
+
# TODO: Need to dynamically assemble this case statement to add
|
504
|
+
# OpenLDAP's increment change type, etc.
|
505
|
+
change_type = case mod.mod_op & ~LDAP_MOD_BVALUES
|
506
|
+
when LDAP_MOD_ADD then 'add'
|
507
|
+
when LDAP_MOD_DELETE then 'delete'
|
508
|
+
when LDAP_MOD_REPLACE then 'replace'
|
509
|
+
end
|
510
|
+
|
511
|
+
ldif << "-\n" if plural
|
512
|
+
ldif << LDIF.to_ldif( change_type, mod.mod_type )
|
513
|
+
ldif << LDIF.to_ldif( mod.mod_type, mod.mod_vals )
|
514
|
+
|
515
|
+
plural = true
|
516
|
+
end
|
517
|
+
|
518
|
+
LDIF::Mod.new( ldif )
|
519
|
+
end
|
520
|
+
|
521
|
+
end
|
522
|
+
|
523
|
+
|
524
|
+
class Entry
|
525
|
+
|
526
|
+
# Convert an LDAP::Entry to LDIF.
|
527
|
+
#
|
528
|
+
def to_ldif
|
529
|
+
ldif = "dn: %s\n" % get_dn
|
530
|
+
|
531
|
+
get_attributes.each do |attr|
|
532
|
+
get_values( attr ).each do |val|
|
533
|
+
ldif << LDIF.to_ldif( attr, [ val ] )
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
LDIF::Entry.new( ldif )
|
538
|
+
end
|
539
|
+
|
540
|
+
alias_method :to_s, :to_ldif
|
541
|
+
end
|
542
|
+
|
543
|
+
|
544
|
+
class Mod
|
545
|
+
|
546
|
+
# Convert an LDAP::Mod with the DN given in +dn+ to LDIF.
|
547
|
+
#
|
548
|
+
def to_ldif( dn )
|
549
|
+
ldif = "dn: %s\n" % dn
|
550
|
+
|
551
|
+
# TODO: Need to dynamically assemble this case statement to add
|
552
|
+
# OpenLDAP's increment change type, etc.
|
553
|
+
case mod_op & ~LDAP_MOD_BVALUES
|
554
|
+
when LDAP_MOD_ADD
|
555
|
+
ldif << "changetype: add\n"
|
556
|
+
when LDAP_MOD_DELETE
|
557
|
+
ldif << "changetype: delete\n"
|
558
|
+
when LDAP_MOD_REPLACE
|
559
|
+
return LDIF.mods_to_ldif( dn, self )
|
560
|
+
end
|
561
|
+
|
562
|
+
ldif << LDIF.to_ldif( mod_type, mod_vals )
|
563
|
+
LDIF::Mod.new( ldif )
|
564
|
+
end
|
565
|
+
|
566
|
+
alias_method :to_s, :to_ldif
|
567
|
+
end
|
568
|
+
|
569
|
+
end
|