jruby-ldap-patched 0.0.3

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.
@@ -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