jruby-ldap-fixes 0.0.3

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