net-ldap 0.3.1 → 0.17.0
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.
- checksums.yaml +7 -0
- data/Contributors.rdoc +4 -0
- data/Hacking.rdoc +3 -8
- data/History.rdoc +181 -0
- data/README.rdoc +44 -12
- data/lib/net-ldap.rb +1 -1
- data/lib/net/ber.rb +41 -7
- data/lib/net/ber/ber_parser.rb +21 -7
- data/lib/net/ber/core_ext.rb +11 -18
- data/lib/net/ber/core_ext/array.rb +14 -0
- data/lib/net/ber/core_ext/integer.rb +74 -0
- data/lib/net/ber/core_ext/string.rb +24 -4
- data/lib/net/ber/core_ext/true_class.rb +2 -3
- data/lib/net/ldap.rb +441 -639
- data/lib/net/ldap/auth_adapter.rb +29 -0
- data/lib/net/ldap/auth_adapter/gss_spnego.rb +41 -0
- data/lib/net/ldap/auth_adapter/sasl.rb +62 -0
- data/lib/net/ldap/auth_adapter/simple.rb +34 -0
- data/lib/net/ldap/connection.rb +716 -0
- data/lib/net/ldap/dataset.rb +23 -9
- data/lib/net/ldap/dn.rb +13 -14
- data/lib/net/ldap/entry.rb +27 -9
- data/lib/net/ldap/error.rb +49 -0
- data/lib/net/ldap/filter.rb +58 -32
- data/lib/net/ldap/instrumentation.rb +23 -0
- data/lib/net/ldap/password.rb +23 -14
- data/lib/net/ldap/pdu.rb +70 -6
- data/lib/net/ldap/version.rb +5 -0
- data/lib/net/snmp.rb +237 -241
- metadata +71 -116
- data/.autotest +0 -11
- data/.gemtest +0 -0
- data/.rspec +0 -2
- data/Manifest.txt +0 -49
- data/Rakefile +0 -74
- data/autotest/discover.rb +0 -1
- data/lib/net/ber/core_ext/bignum.rb +0 -22
- data/lib/net/ber/core_ext/fixnum.rb +0 -66
- data/net-ldap.gemspec +0 -58
- data/spec/integration/ssl_ber_spec.rb +0 -36
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -5
- data/spec/unit/ber/ber_spec.rb +0 -109
- data/spec/unit/ber/core_ext/string_spec.rb +0 -51
- data/spec/unit/ldap/dn_spec.rb +0 -80
- data/spec/unit/ldap/entry_spec.rb +0 -51
- data/spec/unit/ldap/filter_spec.rb +0 -84
- data/spec/unit/ldap_spec.rb +0 -48
- data/test/common.rb +0 -3
- data/test/test_entry.rb +0 -59
- data/test/test_filter.rb +0 -122
- data/test/test_ldap_connection.rb +0 -24
- data/test/test_ldif.rb +0 -79
- data/test/test_password.rb +0 -17
- data/test/test_rename.rb +0 -77
- data/test/test_snmp.rb +0 -114
- data/test/testdata.ldif +0 -101
- data/testserver/ldapserver.rb +0 -210
- data/testserver/testdata.ldif +0 -101
data/lib/net/ber/core_ext.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- ruby encoding: utf-8 -*-
|
2
|
-
|
2
|
+
require_relative 'ber_parser'
|
3
3
|
# :stopdoc:
|
4
4
|
class IO
|
5
5
|
include Net::BER::BERParser
|
@@ -19,44 +19,37 @@ end
|
|
19
19
|
module Net::BER::Extensions # :nodoc:
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
require_relative 'core_ext/string'
|
23
23
|
# :stopdoc:
|
24
24
|
class String
|
25
25
|
include Net::BER::BERParser
|
26
26
|
include Net::BER::Extensions::String
|
27
27
|
end
|
28
28
|
|
29
|
-
|
29
|
+
require_relative 'core_ext/array'
|
30
30
|
# :stopdoc:
|
31
|
-
class Array
|
31
|
+
class Array
|
32
32
|
include Net::BER::Extensions::Array
|
33
33
|
end
|
34
34
|
# :startdoc:
|
35
35
|
|
36
|
-
|
36
|
+
require_relative 'core_ext/integer'
|
37
37
|
# :stopdoc:
|
38
|
-
class
|
39
|
-
include Net::BER::Extensions::
|
38
|
+
class Integer
|
39
|
+
include Net::BER::Extensions::Integer
|
40
40
|
end
|
41
41
|
# :startdoc:
|
42
42
|
|
43
|
-
|
43
|
+
require_relative 'core_ext/true_class'
|
44
44
|
# :stopdoc:
|
45
|
-
class
|
46
|
-
include Net::BER::Extensions::Fixnum
|
47
|
-
end
|
48
|
-
# :startdoc:
|
49
|
-
|
50
|
-
require 'net/ber/core_ext/true_class'
|
51
|
-
# :stopdoc:
|
52
|
-
class TrueClass
|
45
|
+
class TrueClass
|
53
46
|
include Net::BER::Extensions::TrueClass
|
54
47
|
end
|
55
48
|
# :startdoc:
|
56
49
|
|
57
|
-
|
50
|
+
require_relative 'core_ext/false_class'
|
58
51
|
# :stopdoc:
|
59
|
-
class FalseClass
|
52
|
+
class FalseClass
|
60
53
|
include Net::BER::Extensions::FalseClass
|
61
54
|
end
|
62
55
|
# :startdoc:
|
@@ -79,4 +79,18 @@ module Net::BER::Extensions::Array
|
|
79
79
|
oid = ary.pack("w*")
|
80
80
|
[6, oid.length].pack("CC") + oid
|
81
81
|
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Converts an array into a set of ber control codes
|
85
|
+
# The expected format is [[control_oid, criticality, control_value(optional)]]
|
86
|
+
# [['1.2.840.113556.1.4.805',true]]
|
87
|
+
#
|
88
|
+
def to_ber_control
|
89
|
+
#if our array does not contain at least one array then wrap it in an array before going forward
|
90
|
+
ary = self[0].kind_of?(Array) ? self : [self]
|
91
|
+
ary = ary.collect do |control_sequence|
|
92
|
+
control_sequence.collect(&:to_ber).to_ber_sequence.reject_empty_ber_arrays
|
93
|
+
end
|
94
|
+
ary.to_ber_sequence.reject_empty_ber_arrays
|
95
|
+
end
|
82
96
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
2
|
+
##
|
3
|
+
# BER extensions to the Integer class, affecting Fixnum and Bignum objects.
|
4
|
+
module Net::BER::Extensions::Integer
|
5
|
+
##
|
6
|
+
# Converts the Integer to BER format.
|
7
|
+
def to_ber
|
8
|
+
"\002#{to_ber_internal}"
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Converts the Integer to BER enumerated format.
|
13
|
+
def to_ber_enumerated
|
14
|
+
"\012#{to_ber_internal}"
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Converts the Integer to BER length encoding format.
|
19
|
+
def to_ber_length_encoding
|
20
|
+
if self <= 127
|
21
|
+
[self].pack('C')
|
22
|
+
else
|
23
|
+
i = [self].pack('N').sub(/^[\0]+/, "")
|
24
|
+
[0x80 + i.length].pack('C') + i
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Generate a BER-encoding for an application-defined INTEGER. Examples of
|
30
|
+
# such integers are SNMP's Counter, Gauge, and TimeTick types.
|
31
|
+
def to_ber_application(tag)
|
32
|
+
[0x40 + tag].pack("C") + to_ber_internal
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Used to BER-encode the length and content bytes of an Integer. Callers
|
37
|
+
# must prepend the tag byte for the contained value.
|
38
|
+
def to_ber_internal
|
39
|
+
# Compute the byte length, accounting for negative values requiring two's
|
40
|
+
# complement.
|
41
|
+
size = 1
|
42
|
+
size += 1 until (((self < 0) ? ~self : self) >> (size * 8)).zero?
|
43
|
+
|
44
|
+
# Padding for positive, negative values. See section 8.5 of ITU-T X.690:
|
45
|
+
# http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
|
46
|
+
|
47
|
+
# For positive integers, if most significant bit in an octet is set to one,
|
48
|
+
# pad the result (otherwise it's decoded as a negative value).
|
49
|
+
if self > 0 && (self & (0x80 << (size - 1) * 8)) > 0
|
50
|
+
size += 1
|
51
|
+
end
|
52
|
+
|
53
|
+
# And for negative integers, pad if the most significant bit in the octet
|
54
|
+
# is not set to one (othwerise, it's decoded as positive value).
|
55
|
+
if self < 0 && (self & (0x80 << (size - 1) * 8)) == 0
|
56
|
+
size += 1
|
57
|
+
end
|
58
|
+
|
59
|
+
# Store the size of the Integer in the result
|
60
|
+
result = [size]
|
61
|
+
|
62
|
+
# Appends bytes to result, starting with higher orders first. Extraction
|
63
|
+
# of bytes is done by right shifting the original Integer by an amount
|
64
|
+
# and then masking that with 0xff.
|
65
|
+
while size > 0
|
66
|
+
# right shift size - 1 bytes, mask with 0xff
|
67
|
+
result << ((self >> ((size - 1) * 8)) & 0xff)
|
68
|
+
size -= 1
|
69
|
+
end
|
70
|
+
|
71
|
+
result.pack('C*')
|
72
|
+
end
|
73
|
+
private :to_ber_internal
|
74
|
+
end
|
@@ -16,11 +16,27 @@ module Net::BER::Extensions::String
|
|
16
16
|
[code].pack('C') + raw_string.length.to_ber_length_encoding + raw_string
|
17
17
|
end
|
18
18
|
|
19
|
+
##
|
20
|
+
# Converts a string to a BER string but does *not* encode to UTF-8 first.
|
21
|
+
# This is required for proper representation of binary data for Microsoft
|
22
|
+
# Active Directory
|
23
|
+
def to_ber_bin(code = 0x04)
|
24
|
+
[code].pack('C') + length.to_ber_length_encoding + self
|
25
|
+
end
|
26
|
+
|
19
27
|
def raw_utf8_encoded
|
20
28
|
if self.respond_to?(:encode)
|
21
29
|
# Strings should be UTF-8 encoded according to LDAP.
|
22
30
|
# However, the BER code is not necessarily valid UTF-8
|
23
|
-
|
31
|
+
begin
|
32
|
+
self.encode('UTF-8').force_encoding('ASCII-8BIT')
|
33
|
+
rescue Encoding::UndefinedConversionError
|
34
|
+
self
|
35
|
+
rescue Encoding::ConverterNotFoundError
|
36
|
+
self
|
37
|
+
rescue Encoding::InvalidByteSequenceError
|
38
|
+
self
|
39
|
+
end
|
24
40
|
else
|
25
41
|
self
|
26
42
|
end
|
@@ -46,15 +62,19 @@ module Net::BER::Extensions::String
|
|
46
62
|
def read_ber(syntax = nil)
|
47
63
|
StringIO.new(self).read_ber(syntax)
|
48
64
|
end
|
49
|
-
|
65
|
+
|
50
66
|
##
|
51
|
-
# Destructively reads a BER object from the string.
|
67
|
+
# Destructively reads a BER object from the string.
|
52
68
|
def read_ber!(syntax = nil)
|
53
69
|
io = StringIO.new(self)
|
54
70
|
|
55
71
|
result = io.read_ber(syntax)
|
56
72
|
self.slice!(0...io.pos)
|
57
|
-
|
73
|
+
|
58
74
|
return result
|
59
75
|
end
|
76
|
+
|
77
|
+
def reject_empty_ber_arrays
|
78
|
+
self.gsub(/0\000/n, '')
|
79
|
+
end
|
60
80
|
end
|
@@ -5,8 +5,7 @@ module Net::BER::Extensions::TrueClass
|
|
5
5
|
##
|
6
6
|
# Converts +true+ to the BER wireline representation of +true+.
|
7
7
|
def to_ber
|
8
|
-
#
|
9
|
-
|
10
|
-
"\001\001\001"
|
8
|
+
# http://tools.ietf.org/html/rfc4511#section-5.1
|
9
|
+
"\001\001\xFF".force_encoding("ASCII-8BIT")
|
11
10
|
end
|
12
11
|
end
|
data/lib/net/ldap.rb
CHANGED
@@ -17,12 +17,22 @@ module Net # :nodoc:
|
|
17
17
|
end
|
18
18
|
require 'socket'
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
require_relative 'ber'
|
21
|
+
require_relative 'ldap/pdu'
|
22
|
+
require_relative 'ldap/filter'
|
23
|
+
require_relative 'ldap/dataset'
|
24
|
+
require_relative 'ldap/password'
|
25
|
+
require_relative 'ldap/entry'
|
26
|
+
require_relative 'ldap/instrumentation'
|
27
|
+
require_relative 'ldap/connection'
|
28
|
+
require_relative 'ldap/version'
|
29
|
+
require_relative 'ldap/error'
|
30
|
+
require_relative 'ldap/auth_adapter'
|
31
|
+
require_relative 'ldap/auth_adapter/simple'
|
32
|
+
require_relative 'ldap/auth_adapter/sasl'
|
33
|
+
|
34
|
+
Net::LDAP::AuthAdapter.register([:simple, :anon, :anonymous], Net::LDAP::AuthAdapter::Simple)
|
35
|
+
Net::LDAP::AuthAdapter.register(:sasl, Net::LDAP::AuthAdapter::Sasl)
|
26
36
|
|
27
37
|
# == Quick-start for the Impatient
|
28
38
|
# === Quick Example of a user-authentication against an LDAP directory:
|
@@ -69,6 +79,14 @@ require 'net/ldap/entry'
|
|
69
79
|
#
|
70
80
|
# p ldap.get_operation_result
|
71
81
|
#
|
82
|
+
# === Setting connect timeout
|
83
|
+
#
|
84
|
+
# By default, Net::LDAP uses TCP sockets with a connection timeout of 5 seconds.
|
85
|
+
#
|
86
|
+
# This value can be tweaked passing the :connect_timeout parameter.
|
87
|
+
# i.e.
|
88
|
+
# ldap = Net::LDAP.new ...,
|
89
|
+
# :connect_timeout => 3
|
72
90
|
#
|
73
91
|
# == A Brief Introduction to LDAP
|
74
92
|
#
|
@@ -241,15 +259,19 @@ require 'net/ldap/entry'
|
|
241
259
|
# and then keeps it open while it executes a user-supplied block.
|
242
260
|
# Net::LDAP#open closes the connection on completion of the block.
|
243
261
|
class Net::LDAP
|
244
|
-
|
245
|
-
|
246
|
-
class LdapError < StandardError; end
|
262
|
+
include Net::LDAP::Instrumentation
|
247
263
|
|
248
264
|
SearchScope_BaseObject = 0
|
249
265
|
SearchScope_SingleLevel = 1
|
250
266
|
SearchScope_WholeSubtree = 2
|
251
|
-
SearchScopes = [
|
252
|
-
SearchScope_WholeSubtree
|
267
|
+
SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel,
|
268
|
+
SearchScope_WholeSubtree]
|
269
|
+
|
270
|
+
DerefAliases_Never = 0
|
271
|
+
DerefAliases_Search = 1
|
272
|
+
DerefAliases_Find = 2
|
273
|
+
DerefAliases_Always = 3
|
274
|
+
DerefAliasesArray = [DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always]
|
253
275
|
|
254
276
|
primitive = { 2 => :null } # UnbindRequest body
|
255
277
|
constructed = {
|
@@ -301,42 +323,129 @@ class Net::LDAP
|
|
301
323
|
:constructed => constructed,
|
302
324
|
}
|
303
325
|
|
326
|
+
universal = {
|
327
|
+
constructed: {
|
328
|
+
107 => :array, #ExtendedResponse (PasswdModifyResponseValue)
|
329
|
+
},
|
330
|
+
}
|
331
|
+
|
304
332
|
AsnSyntax = Net::BER.compile_syntax(:application => application,
|
333
|
+
:universal => universal,
|
305
334
|
:context_specific => context_specific)
|
306
335
|
|
307
336
|
DefaultHost = "127.0.0.1"
|
308
337
|
DefaultPort = 389
|
309
338
|
DefaultAuth = { :method => :anonymous }
|
310
339
|
DefaultTreebase = "dc=com"
|
311
|
-
|
312
|
-
|
313
|
-
|
340
|
+
DefaultForceNoPage = false
|
341
|
+
|
342
|
+
StartTlsOid = '1.3.6.1.4.1.1466.20037'
|
343
|
+
PasswdModifyOid = '1.3.6.1.4.1.4203.1.11.1'
|
344
|
+
|
345
|
+
# https://tools.ietf.org/html/rfc4511#section-4.1.9
|
346
|
+
# https://tools.ietf.org/html/rfc4511#appendix-A
|
347
|
+
ResultCodeSuccess = 0
|
348
|
+
ResultCodeOperationsError = 1
|
349
|
+
ResultCodeProtocolError = 2
|
350
|
+
ResultCodeTimeLimitExceeded = 3
|
351
|
+
ResultCodeSizeLimitExceeded = 4
|
352
|
+
ResultCodeCompareFalse = 5
|
353
|
+
ResultCodeCompareTrue = 6
|
354
|
+
ResultCodeAuthMethodNotSupported = 7
|
355
|
+
ResultCodeStrongerAuthRequired = 8
|
356
|
+
ResultCodeReferral = 10
|
357
|
+
ResultCodeAdminLimitExceeded = 11
|
358
|
+
ResultCodeUnavailableCriticalExtension = 12
|
359
|
+
ResultCodeConfidentialityRequired = 13
|
360
|
+
ResultCodeSaslBindInProgress = 14
|
361
|
+
ResultCodeNoSuchAttribute = 16
|
362
|
+
ResultCodeUndefinedAttributeType = 17
|
363
|
+
ResultCodeInappropriateMatching = 18
|
364
|
+
ResultCodeConstraintViolation = 19
|
365
|
+
ResultCodeAttributeOrValueExists = 20
|
366
|
+
ResultCodeInvalidAttributeSyntax = 21
|
367
|
+
ResultCodeNoSuchObject = 32
|
368
|
+
ResultCodeAliasProblem = 33
|
369
|
+
ResultCodeInvalidDNSyntax = 34
|
370
|
+
ResultCodeAliasDereferencingProblem = 36
|
371
|
+
ResultCodeInappropriateAuthentication = 48
|
372
|
+
ResultCodeInvalidCredentials = 49
|
373
|
+
ResultCodeInsufficientAccessRights = 50
|
374
|
+
ResultCodeBusy = 51
|
375
|
+
ResultCodeUnavailable = 52
|
376
|
+
ResultCodeUnwillingToPerform = 53
|
377
|
+
ResultCodeNamingViolation = 64
|
378
|
+
ResultCodeObjectClassViolation = 65
|
379
|
+
ResultCodeNotAllowedOnNonLeaf = 66
|
380
|
+
ResultCodeNotAllowedOnRDN = 67
|
381
|
+
ResultCodeEntryAlreadyExists = 68
|
382
|
+
ResultCodeObjectClassModsProhibited = 69
|
383
|
+
ResultCodeAffectsMultipleDSAs = 71
|
384
|
+
ResultCodeOther = 80
|
385
|
+
|
386
|
+
# https://tools.ietf.org/html/rfc4511#appendix-A.1
|
387
|
+
ResultCodesNonError = [
|
388
|
+
ResultCodeSuccess,
|
389
|
+
ResultCodeCompareFalse,
|
390
|
+
ResultCodeCompareTrue,
|
391
|
+
ResultCodeReferral,
|
392
|
+
ResultCodeSaslBindInProgress,
|
393
|
+
]
|
394
|
+
|
395
|
+
# nonstandard list of "successful" result codes for searches
|
396
|
+
ResultCodesSearchSuccess = [
|
397
|
+
ResultCodeSuccess,
|
398
|
+
ResultCodeTimeLimitExceeded,
|
399
|
+
ResultCodeSizeLimitExceeded,
|
400
|
+
]
|
401
|
+
|
402
|
+
# map of result code to human message
|
314
403
|
ResultStrings = {
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
404
|
+
ResultCodeSuccess => "Success",
|
405
|
+
ResultCodeOperationsError => "Operations Error",
|
406
|
+
ResultCodeProtocolError => "Protocol Error",
|
407
|
+
ResultCodeTimeLimitExceeded => "Time Limit Exceeded",
|
408
|
+
ResultCodeSizeLimitExceeded => "Size Limit Exceeded",
|
409
|
+
ResultCodeCompareFalse => "False Comparison",
|
410
|
+
ResultCodeCompareTrue => "True Comparison",
|
411
|
+
ResultCodeAuthMethodNotSupported => "Auth Method Not Supported",
|
412
|
+
ResultCodeStrongerAuthRequired => "Stronger Auth Needed",
|
413
|
+
ResultCodeReferral => "Referral",
|
414
|
+
ResultCodeAdminLimitExceeded => "Admin Limit Exceeded",
|
415
|
+
ResultCodeUnavailableCriticalExtension => "Unavailable crtical extension",
|
416
|
+
ResultCodeConfidentialityRequired => "Confidentiality Required",
|
417
|
+
ResultCodeSaslBindInProgress => "saslBindInProgress",
|
418
|
+
ResultCodeNoSuchAttribute => "No Such Attribute",
|
419
|
+
ResultCodeUndefinedAttributeType => "Undefined Attribute Type",
|
420
|
+
ResultCodeInappropriateMatching => "Inappropriate Matching",
|
421
|
+
ResultCodeConstraintViolation => "Constraint Violation",
|
422
|
+
ResultCodeAttributeOrValueExists => "Attribute or Value Exists",
|
423
|
+
ResultCodeInvalidAttributeSyntax => "Invalide Attribute Syntax",
|
424
|
+
ResultCodeNoSuchObject => "No Such Object",
|
425
|
+
ResultCodeAliasProblem => "Alias Problem",
|
426
|
+
ResultCodeInvalidDNSyntax => "Invalid DN Syntax",
|
427
|
+
ResultCodeAliasDereferencingProblem => "Alias Dereferencing Problem",
|
428
|
+
ResultCodeInappropriateAuthentication => "Inappropriate Authentication",
|
429
|
+
ResultCodeInvalidCredentials => "Invalid Credentials",
|
430
|
+
ResultCodeInsufficientAccessRights => "Insufficient Access Rights",
|
431
|
+
ResultCodeBusy => "Busy",
|
432
|
+
ResultCodeUnavailable => "Unavailable",
|
433
|
+
ResultCodeUnwillingToPerform => "Unwilling to perform",
|
434
|
+
ResultCodeNamingViolation => "Naming Violation",
|
435
|
+
ResultCodeObjectClassViolation => "Object Class Violation",
|
436
|
+
ResultCodeNotAllowedOnNonLeaf => "Not Allowed On Non-Leaf",
|
437
|
+
ResultCodeNotAllowedOnRDN => "Not Allowed On RDN",
|
438
|
+
ResultCodeEntryAlreadyExists => "Entry Already Exists",
|
439
|
+
ResultCodeObjectClassModsProhibited => "ObjectClass Modifications Prohibited",
|
440
|
+
ResultCodeAffectsMultipleDSAs => "Affects Multiple DSAs",
|
441
|
+
ResultCodeOther => "Other",
|
336
442
|
}
|
337
443
|
|
338
|
-
module
|
339
|
-
|
444
|
+
module LDAPControls
|
445
|
+
PAGED_RESULTS = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
|
446
|
+
SORT_REQUEST = "1.2.840.113556.1.4.473"
|
447
|
+
SORT_RESPONSE = "1.2.840.113556.1.4.474"
|
448
|
+
DELETE_TREE = "1.2.840.113556.1.4.805"
|
340
449
|
end
|
341
450
|
|
342
451
|
def self.result2string(code) #:nodoc:
|
@@ -345,6 +454,7 @@ class Net::LDAP
|
|
345
454
|
|
346
455
|
attr_accessor :host
|
347
456
|
attr_accessor :port
|
457
|
+
attr_accessor :hosts
|
348
458
|
attr_accessor :base
|
349
459
|
|
350
460
|
# Instantiate an object of type Net::LDAP to perform directory operations.
|
@@ -353,6 +463,8 @@ class Net::LDAP
|
|
353
463
|
# described below. The following arguments are supported:
|
354
464
|
# * :host => the LDAP server's IP-address (default 127.0.0.1)
|
355
465
|
# * :port => the LDAP server's TCP port (default 389)
|
466
|
+
# * :hosts => an enumerable of pairs of hosts and corresponding ports with
|
467
|
+
# which to attempt opening connections (default [[host, port]])
|
356
468
|
# * :auth => a Hash containing authorization parameters. Currently
|
357
469
|
# supported values include: {:method => :anonymous} and {:method =>
|
358
470
|
# :simple, :username => your_user_name, :password => your_password }
|
@@ -364,28 +476,90 @@ class Net::LDAP
|
|
364
476
|
# specify a treebase. If you give a treebase value in any particular
|
365
477
|
# call to #search, that value will override any treebase value you give
|
366
478
|
# here.
|
479
|
+
# * :force_no_page => Set to true to prevent paged results even if your
|
480
|
+
# server says it supports them. This is a fix for MS Active Directory
|
481
|
+
# * :instrumentation_service => An object responsible for instrumenting
|
482
|
+
# operations, compatible with ActiveSupport::Notifications' public API.
|
367
483
|
# * :encryption => specifies the encryption to be used in communicating
|
368
|
-
# with the LDAP server. The value
|
369
|
-
# parameters,
|
370
|
-
#
|
371
|
-
#
|
372
|
-
#
|
484
|
+
# with the LDAP server. The value must be a Hash containing additional
|
485
|
+
# parameters, which consists of two keys:
|
486
|
+
# method: - :simple_tls or :start_tls
|
487
|
+
# tls_options: - Hash of options for that method
|
488
|
+
# The :simple_tls encryption method encrypts <i>all</i> communications
|
489
|
+
# with the LDAP server. It completely establishes SSL/TLS encryption with
|
490
|
+
# the LDAP server before any LDAP-protocol data is exchanged. There is no
|
491
|
+
# plaintext negotiation and no special encryption-request controls are
|
492
|
+
# sent to the server. <i>The :simple_tls option is the simplest, easiest
|
493
|
+
# way to encrypt communications between Net::LDAP and LDAP servers.</i>
|
494
|
+
# If you get communications or protocol errors when using this option,
|
495
|
+
# check with your LDAP server administrator. Pay particular attention
|
496
|
+
# to the TCP port you are connecting to. It's impossible for an LDAP
|
497
|
+
# server to support plaintext LDAP communications and <i>simple TLS</i>
|
498
|
+
# connections on the same port. The standard TCP port for unencrypted
|
499
|
+
# LDAP connections is 389, but the standard port for simple-TLS
|
500
|
+
# encrypted connections is 636. Be sure you are using the correct port.
|
501
|
+
# The :start_tls like the :simple_tls encryption method also encrypts all
|
502
|
+
# communcations with the LDAP server. With the exception that it operates
|
503
|
+
# over the standard TCP port.
|
504
|
+
#
|
505
|
+
# To validate the LDAP server's certificate (a security must if you're
|
506
|
+
# talking over the public internet), you need to set :tls_options
|
507
|
+
# something like this...
|
508
|
+
#
|
509
|
+
# Net::LDAP.new(
|
510
|
+
# # ... set host, bind dn, etc ...
|
511
|
+
# encryption: {
|
512
|
+
# method: :simple_tls,
|
513
|
+
# tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
|
514
|
+
# }
|
515
|
+
# )
|
516
|
+
#
|
517
|
+
# The above will use the operating system-provided store of CA
|
518
|
+
# certificates to validate your LDAP server's cert.
|
519
|
+
# If cert validation fails, it'll happen during the #bind
|
520
|
+
# whenever you first try to open a connection to the server.
|
521
|
+
# Those methods will throw Net::LDAP::ConnectionError with
|
522
|
+
# a message about certificate verify failing. If your
|
523
|
+
# LDAP server's certificate is signed by DigiCert, Comodo, etc.,
|
524
|
+
# you're probably good. If you've got a self-signed cert but it's
|
525
|
+
# been added to the host's OS-maintained CA store (e.g. on Debian
|
526
|
+
# add foobar.crt to /usr/local/share/ca-certificates/ and run
|
527
|
+
# `update-ca-certificates`), then the cert should pass validation.
|
528
|
+
# To ignore the OS's CA store, put your CA in a PEM-encoded file and...
|
529
|
+
#
|
530
|
+
# encryption: {
|
531
|
+
# method: :simple_tls,
|
532
|
+
# tls_options: { ca_file: '/path/to/my-little-ca.pem',
|
533
|
+
# ssl_version: 'TLSv1_1' },
|
534
|
+
# }
|
535
|
+
#
|
536
|
+
# As you might guess, the above example also fails the connection
|
537
|
+
# if the client can't negotiate TLS v1.1.
|
538
|
+
# tls_options is ultimately passed to OpenSSL::SSL::SSLContext#set_params
|
539
|
+
# For more details, see
|
540
|
+
# http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
|
373
541
|
#
|
374
542
|
# Instantiating a Net::LDAP object does <i>not</i> result in network
|
375
543
|
# traffic to the LDAP server. It simply stores the connection and binding
|
376
|
-
# parameters in the object.
|
544
|
+
# parameters in the object. That's why Net::LDAP.new doesn't throw
|
545
|
+
# cert validation errors itself; #bind does instead.
|
377
546
|
def initialize(args = {})
|
378
547
|
@host = args[:host] || DefaultHost
|
379
548
|
@port = args[:port] || DefaultPort
|
549
|
+
@hosts = args[:hosts]
|
380
550
|
@verbose = false # Make this configurable with a switch on the class.
|
381
551
|
@auth = args[:auth] || DefaultAuth
|
382
552
|
@base = args[:base] || DefaultTreebase
|
383
|
-
|
553
|
+
@force_no_page = args[:force_no_page] || DefaultForceNoPage
|
554
|
+
@encryption = normalize_encryption(args[:encryption]) # may be nil
|
555
|
+
@connect_timeout = args[:connect_timeout]
|
384
556
|
|
385
557
|
if pr = @auth[:password] and pr.respond_to?(:call)
|
386
558
|
@auth[:password] = pr.call
|
387
559
|
end
|
388
560
|
|
561
|
+
@instrumentation_service = args[:instrumentation_service]
|
562
|
+
|
389
563
|
# This variable is only set when we are created with LDAP::open. All of
|
390
564
|
# our internal methods will connect using it, or else they will create
|
391
565
|
# their own.
|
@@ -429,7 +603,7 @@ class Net::LDAP
|
|
429
603
|
@auth = {
|
430
604
|
:method => :simple,
|
431
605
|
:username => username,
|
432
|
-
:password => password
|
606
|
+
:password => password,
|
433
607
|
}
|
434
608
|
end
|
435
609
|
alias_method :auth, :authenticate
|
@@ -442,38 +616,12 @@ class Net::LDAP
|
|
442
616
|
# additional capabilities are added, more configuration values will be
|
443
617
|
# added here.
|
444
618
|
#
|
445
|
-
#
|
446
|
-
#
|
447
|
-
# without enclosing it in a Hash.)
|
448
|
-
#
|
449
|
-
# The :simple_tls encryption method encrypts <i>all</i> communications
|
450
|
-
# with the LDAP server. It completely establishes SSL/TLS encryption with
|
451
|
-
# the LDAP server before any LDAP-protocol data is exchanged. There is no
|
452
|
-
# plaintext negotiation and no special encryption-request controls are
|
453
|
-
# sent to the server. <i>The :simple_tls option is the simplest, easiest
|
454
|
-
# way to encrypt communications between Net::LDAP and LDAP servers.</i>
|
455
|
-
# It's intended for cases where you have an implicit level of trust in the
|
456
|
-
# authenticity of the LDAP server. No validation of the LDAP server's SSL
|
457
|
-
# certificate is performed. This means that :simple_tls will not produce
|
458
|
-
# errors if the LDAP server's encryption certificate is not signed by a
|
459
|
-
# well-known Certification Authority. If you get communications or
|
460
|
-
# protocol errors when using this option, check with your LDAP server
|
461
|
-
# administrator. Pay particular attention to the TCP port you are
|
462
|
-
# connecting to. It's impossible for an LDAP server to support plaintext
|
463
|
-
# LDAP communications and <i>simple TLS</i> connections on the same port.
|
464
|
-
# The standard TCP port for unencrypted LDAP connections is 389, but the
|
465
|
-
# standard port for simple-TLS encrypted connections is 636. Be sure you
|
466
|
-
# are using the correct port.
|
467
|
-
#
|
468
|
-
# <i>[Note: a future version of Net::LDAP will support the STARTTLS LDAP
|
469
|
-
# control, which will enable encrypted communications on the same TCP port
|
470
|
-
# used for unencrypted connections.]</i>
|
619
|
+
# This method is deprecated.
|
620
|
+
#
|
471
621
|
def encryption(args)
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
end
|
476
|
-
@encryption = args
|
622
|
+
warn "Deprecation warning: please give :encryption option as a Hash to Net::LDAP.new"
|
623
|
+
return if args.nil?
|
624
|
+
@encryption = normalize_encryption(args)
|
477
625
|
end
|
478
626
|
|
479
627
|
# #open takes the same parameters as #new. #open makes a network
|
@@ -516,17 +664,22 @@ class Net::LDAP
|
|
516
664
|
# response codes instead of a simple numeric code.
|
517
665
|
#++
|
518
666
|
def get_operation_result
|
667
|
+
result = @result
|
519
668
|
os = OpenStruct.new
|
520
|
-
if
|
669
|
+
if result.is_a?(Net::LDAP::PDU)
|
670
|
+
os.extended_response = result.extended_response
|
671
|
+
result = result.result
|
672
|
+
end
|
673
|
+
if result.is_a?(Hash)
|
521
674
|
# We might get a hash of LDAP response codes instead of a simple
|
522
675
|
# numeric code.
|
523
|
-
os.code = (
|
524
|
-
os.error_message =
|
525
|
-
os.matched_dn =
|
526
|
-
elsif
|
527
|
-
os.code =
|
676
|
+
os.code = (result[:resultCode] || "").to_i
|
677
|
+
os.error_message = result[:errorMessage]
|
678
|
+
os.matched_dn = result[:matchedDN]
|
679
|
+
elsif result
|
680
|
+
os.code = result
|
528
681
|
else
|
529
|
-
os.code =
|
682
|
+
os.code = Net::LDAP::ResultCodeSuccess
|
530
683
|
end
|
531
684
|
os.message = Net::LDAP.result2string(os.code)
|
532
685
|
os
|
@@ -553,18 +706,18 @@ class Net::LDAP
|
|
553
706
|
# anything with the bind results. We then pass self to the caller's
|
554
707
|
# block, where he will execute his LDAP operations. Of course they will
|
555
708
|
# all generate auth failures if the bind was unsuccessful.
|
556
|
-
raise Net::LDAP::
|
709
|
+
raise Net::LDAP::AlreadyOpenedError, "Open already in progress" if @open_connection
|
557
710
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
711
|
+
instrument "open.net_ldap" do |payload|
|
712
|
+
begin
|
713
|
+
@open_connection = new_connection
|
714
|
+
payload[:connection] = @open_connection
|
715
|
+
payload[:bind] = @result = @open_connection.bind(@auth)
|
716
|
+
yield self
|
717
|
+
ensure
|
718
|
+
@open_connection.close if @open_connection
|
719
|
+
@open_connection = nil
|
720
|
+
end
|
568
721
|
end
|
569
722
|
end
|
570
723
|
|
@@ -582,6 +735,9 @@ class Net::LDAP
|
|
582
735
|
# Net::LDAP::SearchScope_WholeSubtree. Default is WholeSubtree.)
|
583
736
|
# * :size (an integer indicating the maximum number of search entries to
|
584
737
|
# return. Default is zero, which signifies no limit.)
|
738
|
+
# * :time (an integer restricting the maximum time in seconds allowed for a search. Default is zero, no time limit RFC 4511 4.5.1.5)
|
739
|
+
# * :deref (one of: Net::LDAP::DerefAliases_Never, Net::LDAP::DerefAliases_Search,
|
740
|
+
# Net::LDAP::DerefAliases_Find, Net::LDAP::DerefAliases_Always. Default is Never.)
|
585
741
|
#
|
586
742
|
# #search queries the LDAP server and passes <i>each entry</i> to the
|
587
743
|
# caller-supplied block, as an object of type Net::LDAP::Entry. If the
|
@@ -623,31 +779,23 @@ class Net::LDAP
|
|
623
779
|
return_result_set = args[:return_result] != false
|
624
780
|
result_set = return_result_set ? [] : nil
|
625
781
|
|
626
|
-
|
627
|
-
@result =
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
else
|
632
|
-
@result = 0
|
633
|
-
begin
|
634
|
-
conn = Net::LDAP::Connection.new(:host => @host, :port => @port,
|
635
|
-
:encryption => @encryption)
|
636
|
-
if (@result = conn.bind(args[:auth] || @auth)) == 0
|
637
|
-
@result = conn.search(args) { |entry|
|
638
|
-
result_set << entry if result_set
|
639
|
-
yield entry if block_given?
|
640
|
-
}
|
782
|
+
instrument "search.net_ldap", args do |payload|
|
783
|
+
@result = use_connection(args) do |conn|
|
784
|
+
conn.search(args) do |entry|
|
785
|
+
result_set << entry if result_set
|
786
|
+
yield entry if block_given?
|
641
787
|
end
|
642
|
-
ensure
|
643
|
-
conn.close if conn
|
644
788
|
end
|
645
|
-
end
|
646
789
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
790
|
+
if return_result_set
|
791
|
+
unless @result.nil?
|
792
|
+
if ResultCodesSearchSuccess.include?(@result.result_code)
|
793
|
+
result_set
|
794
|
+
end
|
795
|
+
end
|
796
|
+
else
|
797
|
+
@result.success?
|
798
|
+
end
|
651
799
|
end
|
652
800
|
end
|
653
801
|
|
@@ -709,19 +857,22 @@ class Net::LDAP
|
|
709
857
|
# the documentation for #auth, the password parameter can be a Ruby Proc
|
710
858
|
# instead of a String.
|
711
859
|
def bind(auth = @auth)
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
860
|
+
instrument "bind.net_ldap" do |payload|
|
861
|
+
if @open_connection
|
862
|
+
payload[:connection] = @open_connection
|
863
|
+
payload[:bind] = @result = @open_connection.bind(auth)
|
864
|
+
else
|
865
|
+
begin
|
866
|
+
conn = new_connection
|
867
|
+
payload[:connection] = conn
|
868
|
+
payload[:bind] = @result = conn.bind(auth)
|
869
|
+
ensure
|
870
|
+
conn.close if conn
|
871
|
+
end
|
721
872
|
end
|
722
|
-
end
|
723
873
|
|
724
|
-
|
874
|
+
@result.success?
|
875
|
+
end
|
725
876
|
end
|
726
877
|
|
727
878
|
# #bind_as is for testing authentication credentials.
|
@@ -772,7 +923,7 @@ class Net::LDAP
|
|
772
923
|
# end
|
773
924
|
def bind_as(args = {})
|
774
925
|
result = false
|
775
|
-
open
|
926
|
+
open do |me|
|
776
927
|
rs = search args
|
777
928
|
if rs and rs.first and dn = rs.first.dn
|
778
929
|
password = args[:password]
|
@@ -780,7 +931,7 @@ class Net::LDAP
|
|
780
931
|
result = rs if bind(:method => :simple, :username => dn,
|
781
932
|
:password => password)
|
782
933
|
end
|
783
|
-
|
934
|
+
end
|
784
935
|
result
|
785
936
|
end
|
786
937
|
|
@@ -809,21 +960,12 @@ class Net::LDAP
|
|
809
960
|
# ldap.add(:dn => dn, :attributes => attr)
|
810
961
|
# end
|
811
962
|
def add(args)
|
812
|
-
|
813
|
-
@result =
|
814
|
-
|
815
|
-
@result = 0
|
816
|
-
begin
|
817
|
-
conn = Connection.new(:host => @host, :port => @port,
|
818
|
-
:encryption => @encryption)
|
819
|
-
if (@result = conn.bind(args[:auth] || @auth)) == 0
|
820
|
-
@result = conn.add(args)
|
821
|
-
end
|
822
|
-
ensure
|
823
|
-
conn.close if conn
|
963
|
+
instrument "add.net_ldap", args do |payload|
|
964
|
+
@result = use_connection(args) do |conn|
|
965
|
+
conn.add(args)
|
824
966
|
end
|
967
|
+
@result.success?
|
825
968
|
end
|
826
|
-
@result == 0
|
827
969
|
end
|
828
970
|
|
829
971
|
# Modifies the attribute values of a particular entry on the LDAP
|
@@ -841,7 +983,7 @@ class Net::LDAP
|
|
841
983
|
# The LDAP protocol provides a full and well thought-out set of operations
|
842
984
|
# for changing the values of attributes, but they are necessarily somewhat
|
843
985
|
# complex and not always intuitive. If these instructions are confusing or
|
844
|
-
# incomplete, please send us email or create
|
986
|
+
# incomplete, please send us email or create an issue on GitHub.
|
845
987
|
#
|
846
988
|
# The :operations parameter to #modify takes an array of
|
847
989
|
# operation-descriptors. Each individual operation is specified in one
|
@@ -849,9 +991,10 @@ class Net::LDAP
|
|
849
991
|
# operations in order.
|
850
992
|
#
|
851
993
|
# Each of the operations appearing in the Array must itself be an Array
|
852
|
-
# with exactly three elements:
|
853
|
-
#
|
854
|
-
#
|
994
|
+
# with exactly three elements:
|
995
|
+
# an operator :: must be :add, :replace, or :delete
|
996
|
+
# an attribute name :: the attribute name (string or symbol) to modify
|
997
|
+
# a value :: either a string or an array of strings.
|
855
998
|
#
|
856
999
|
# The :add operator will, unsurprisingly, add the specified values to the
|
857
1000
|
# specified attribute. If the attribute does not already exist, :add will
|
@@ -894,34 +1037,63 @@ class Net::LDAP
|
|
894
1037
|
# may not get extended information that will tell you which one failed.
|
895
1038
|
# #modify has no notion of an atomic transaction. If you specify a chain
|
896
1039
|
# of modifications in one call to #modify, and one of them fails, the
|
897
|
-
# preceding ones will usually not be "rolled back,
|
1040
|
+
# preceding ones will usually not be "rolled back", resulting in a
|
898
1041
|
# partial update. This is a limitation of the LDAP protocol, not of
|
899
1042
|
# Net::LDAP.
|
900
1043
|
#
|
901
1044
|
# The lack of transactional atomicity in LDAP means that you're usually
|
902
1045
|
# better off using the convenience methods #add_attribute,
|
903
|
-
# #replace_attribute, and #delete_attribute, which are
|
1046
|
+
# #replace_attribute, and #delete_attribute, which are wrappers over
|
904
1047
|
# #modify. However, certain LDAP servers may provide concurrency
|
905
1048
|
# semantics, in which the several operations contained in a single #modify
|
906
1049
|
# call are not interleaved with other modification-requests received
|
907
1050
|
# simultaneously by the server. It bears repeating that this concurrency
|
908
1051
|
# does _not_ imply transactional atomicity, which LDAP does not provide.
|
909
1052
|
def modify(args)
|
910
|
-
|
911
|
-
@result =
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
1053
|
+
instrument "modify.net_ldap", args do |payload|
|
1054
|
+
@result = use_connection(args) do |conn|
|
1055
|
+
conn.modify(args)
|
1056
|
+
end
|
1057
|
+
@result.success?
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
# Password Modify
|
1062
|
+
#
|
1063
|
+
# Change existing password:
|
1064
|
+
#
|
1065
|
+
# dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
|
1066
|
+
# auth = {
|
1067
|
+
# method: :simple,
|
1068
|
+
# username: dn,
|
1069
|
+
# password: 'passworD1'
|
1070
|
+
# }
|
1071
|
+
# ldap.password_modify(dn: dn,
|
1072
|
+
# auth: auth,
|
1073
|
+
# old_password: 'passworD1',
|
1074
|
+
# new_password: 'passworD2')
|
1075
|
+
#
|
1076
|
+
# Or get the LDAP server to generate a password for you:
|
1077
|
+
#
|
1078
|
+
# dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
|
1079
|
+
# auth = {
|
1080
|
+
# method: :simple,
|
1081
|
+
# username: dn,
|
1082
|
+
# password: 'passworD1'
|
1083
|
+
# }
|
1084
|
+
# ldap.password_modify(dn: dn,
|
1085
|
+
# auth: auth,
|
1086
|
+
# old_password: 'passworD1')
|
1087
|
+
#
|
1088
|
+
# ldap.get_operation_result.extended_response[0][0] #=> 'VtcgGf/G'
|
1089
|
+
#
|
1090
|
+
def password_modify(args)
|
1091
|
+
instrument "modify_password.net_ldap", args do |payload|
|
1092
|
+
@result = use_connection(args) do |conn|
|
1093
|
+
conn.password_modify(args)
|
922
1094
|
end
|
1095
|
+
@result.success?
|
923
1096
|
end
|
924
|
-
@result == 0
|
925
1097
|
end
|
926
1098
|
|
927
1099
|
# Add a value to an attribute. Takes the full DN of the entry to modify,
|
@@ -978,21 +1150,12 @@ class Net::LDAP
|
|
978
1150
|
#
|
979
1151
|
# _Documentation_ _stub_
|
980
1152
|
def rename(args)
|
981
|
-
|
982
|
-
@result =
|
983
|
-
|
984
|
-
@result = 0
|
985
|
-
begin
|
986
|
-
conn = Connection.new(:host => @host, :port => @port,
|
987
|
-
:encryption => @encryption)
|
988
|
-
if (@result = conn.bind(args[:auth] || @auth)) == 0
|
989
|
-
@result = conn.rename(args)
|
990
|
-
end
|
991
|
-
ensure
|
992
|
-
conn.close if conn
|
1153
|
+
instrument "rename.net_ldap", args do |payload|
|
1154
|
+
@result = use_connection(args) do |conn|
|
1155
|
+
conn.rename(args)
|
993
1156
|
end
|
1157
|
+
@result.success?
|
994
1158
|
end
|
995
|
-
@result == 0
|
996
1159
|
end
|
997
1160
|
alias_method :modify_rdn, :rename
|
998
1161
|
|
@@ -1006,21 +1169,33 @@ class Net::LDAP
|
|
1006
1169
|
# dn = "mail=deleteme@example.com, ou=people, dc=example, dc=com"
|
1007
1170
|
# ldap.delete :dn => dn
|
1008
1171
|
def delete(args)
|
1009
|
-
|
1010
|
-
@result =
|
1011
|
-
|
1012
|
-
@result = 0
|
1013
|
-
begin
|
1014
|
-
conn = Connection.new(:host => @host, :port => @port,
|
1015
|
-
:encryption => @encryption)
|
1016
|
-
if (@result = conn.bind(args[:auth] || @auth)) == 0
|
1017
|
-
@result = conn.delete(args)
|
1018
|
-
end
|
1019
|
-
ensure
|
1020
|
-
conn.close
|
1172
|
+
instrument "delete.net_ldap", args do |payload|
|
1173
|
+
@result = use_connection(args) do |conn|
|
1174
|
+
conn.delete(args)
|
1021
1175
|
end
|
1176
|
+
@result.success?
|
1177
|
+
end
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
# Delete an entry from the LDAP directory along with all subordinate entries.
|
1181
|
+
# the regular delete method will fail to delete an entry if it has subordinate
|
1182
|
+
# entries. This method sends an extra control code to tell the LDAP server
|
1183
|
+
# to do a tree delete. ('1.2.840.113556.1.4.805')
|
1184
|
+
#
|
1185
|
+
# If the LDAP server does not support the DELETE_TREE control code, subordinate
|
1186
|
+
# entries are deleted recursively instead.
|
1187
|
+
#
|
1188
|
+
# Returns True or False to indicate whether the delete succeeded. Extended
|
1189
|
+
# status information is available by calling #get_operation_result.
|
1190
|
+
#
|
1191
|
+
# dn = "mail=deleteme@example.com, ou=people, dc=example, dc=com"
|
1192
|
+
# ldap.delete_tree :dn => dn
|
1193
|
+
def delete_tree(args)
|
1194
|
+
if search_root_dse[:supportedcontrol].include? Net::LDAP::LDAPControls::DELETE_TREE
|
1195
|
+
delete(args.merge(:control_codes => [[Net::LDAP::LDAPControls::DELETE_TREE, true]]))
|
1196
|
+
else
|
1197
|
+
recursive_delete(args)
|
1022
1198
|
end
|
1023
|
-
@result == 0
|
1024
1199
|
end
|
1025
1200
|
|
1026
1201
|
# This method is experimental and subject to change. Return the rootDSE
|
@@ -1039,9 +1214,16 @@ class Net::LDAP
|
|
1039
1214
|
def search_root_dse
|
1040
1215
|
rs = search(:ignore_server_caps => true, :base => "",
|
1041
1216
|
:scope => SearchScope_BaseObject,
|
1042
|
-
:attributes => [
|
1043
|
-
:altServer,
|
1044
|
-
:
|
1217
|
+
:attributes => [
|
1218
|
+
:altServer,
|
1219
|
+
:namingContexts,
|
1220
|
+
:supportedCapabilities,
|
1221
|
+
:supportedControl,
|
1222
|
+
:supportedExtension,
|
1223
|
+
:supportedFeatures,
|
1224
|
+
:supportedLdapVersion,
|
1225
|
+
:supportedSASLMechanisms,
|
1226
|
+
])
|
1045
1227
|
(rs and rs.first) or Net::LDAP::Entry.new
|
1046
1228
|
end
|
1047
1229
|
|
@@ -1092,473 +1274,93 @@ class Net::LDAP
|
|
1092
1274
|
# MUST refactor the root_dse call out.
|
1093
1275
|
#++
|
1094
1276
|
def paged_searches_supported?
|
1277
|
+
# active directory returns that it supports paged results. However
|
1278
|
+
# it returns binary data in the rfc2696_cookie which throws an
|
1279
|
+
# encoding exception breaking searching.
|
1280
|
+
return false if @force_no_page
|
1095
1281
|
@server_caps ||= search_root_dse
|
1096
|
-
@server_caps[:supportedcontrol].include?(Net::LDAP::
|
1282
|
+
@server_caps[:supportedcontrol].include?(Net::LDAP::LDAPControls::PAGED_RESULTS)
|
1097
1283
|
end
|
1098
|
-
end # class LDAP
|
1099
|
-
|
1100
|
-
# This is a private class used internally by the library. It should not
|
1101
|
-
# be called by user code.
|
1102
|
-
class Net::LDAP::Connection #:nodoc:
|
1103
|
-
LdapVersion = 3
|
1104
|
-
MaxSaslChallenges = 10
|
1105
1284
|
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
rescue Errno::ECONNREFUSED
|
1112
|
-
raise Net::LDAP::LdapError, "Server #{server[:host]} refused connection on port #{server[:port]}."
|
1113
|
-
end
|
1114
|
-
|
1115
|
-
if server[:encryption]
|
1116
|
-
setup_encryption server[:encryption]
|
1117
|
-
end
|
1118
|
-
|
1119
|
-
yield self if block_given?
|
1285
|
+
# Mask auth password
|
1286
|
+
def inspect
|
1287
|
+
inspected = super
|
1288
|
+
inspected.gsub! @auth[:password], "*******" if @auth[:password]
|
1289
|
+
inspected
|
1120
1290
|
end
|
1121
1291
|
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
end
|
1292
|
+
# Internal: Set @open_connection for testing
|
1293
|
+
def connection=(connection)
|
1294
|
+
@open_connection = connection
|
1126
1295
|
end
|
1127
1296
|
|
1128
|
-
|
1129
|
-
raise Net::LDAP::LdapError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL
|
1130
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
1131
|
-
conn = OpenSSL::SSL::SSLSocket.new(io, ctx)
|
1132
|
-
conn.connect
|
1133
|
-
conn.sync_close = true
|
1297
|
+
private
|
1134
1298
|
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
# successfully-opened @conn instance variable, which is a TCP connection.
|
1143
|
-
# Depending on the received arguments, we establish SSL, potentially
|
1144
|
-
# replacing the value of @conn accordingly. Don't generate any errors here
|
1145
|
-
# if no encryption is requested. DO raise Net::LDAP::LdapError objects if encryption
|
1146
|
-
# is requested and we have trouble setting it up. That includes if OpenSSL
|
1147
|
-
# is not set up on the machine. (Question: how does the Ruby OpenSSL
|
1148
|
-
# wrapper react in that case?) DO NOT filter exceptions raised by the
|
1149
|
-
# OpenSSL library. Let them pass back to the user. That should make it
|
1150
|
-
# easier for us to debug the problem reports. Presumably (hopefully?) that
|
1151
|
-
# will also produce recognizable errors if someone tries to use this on a
|
1152
|
-
# machine without OpenSSL.
|
1153
|
-
#
|
1154
|
-
# The simple_tls method is intended as the simplest, stupidest, easiest
|
1155
|
-
# solution for people who want nothing more than encrypted comms with the
|
1156
|
-
# LDAP server. It doesn't do any server-cert validation and requires
|
1157
|
-
# nothing in the way of key files and root-cert files, etc etc. OBSERVE:
|
1158
|
-
# WE REPLACE the value of @conn, which is presumed to be a connected
|
1159
|
-
# TCPSocket object.
|
1160
|
-
#
|
1161
|
-
# The start_tls method is supported by many servers over the standard LDAP
|
1162
|
-
# port. It does not require an alternative port for encrypted
|
1163
|
-
# communications, as with simple_tls. Thanks for Kouhei Sutou for
|
1164
|
-
# generously contributing the :start_tls path.
|
1165
|
-
#++
|
1166
|
-
def setup_encryption(args)
|
1167
|
-
case args[:method]
|
1168
|
-
when :simple_tls
|
1169
|
-
@conn = self.class.wrap_with_ssl(@conn)
|
1170
|
-
# additional branches requiring server validation and peer certs, etc.
|
1171
|
-
# go here.
|
1172
|
-
when :start_tls
|
1173
|
-
msgid = next_msgid.to_ber
|
1174
|
-
request = [Net::LDAP::StartTlsOid.to_ber].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
|
1175
|
-
request_pkt = [msgid, request].to_ber_sequence
|
1176
|
-
@conn.write request_pkt
|
1177
|
-
be = @conn.read_ber(Net::LDAP::AsnSyntax)
|
1178
|
-
raise Net::LDAP::LdapError, "no start_tls result" if be.nil?
|
1179
|
-
pdu = Net::LDAP::PDU.new(be)
|
1180
|
-
raise Net::LDAP::LdapError, "no start_tls result" if pdu.nil?
|
1181
|
-
if pdu.result_code.zero?
|
1182
|
-
@conn = self.class.wrap_with_ssl(@conn)
|
1183
|
-
else
|
1184
|
-
raise Net::LDAP::LdapError, "start_tls failed: #{pdu.result_code}"
|
1185
|
-
end
|
1186
|
-
else
|
1187
|
-
raise Net::LDAP::LdapError, "unsupported encryption method #{args[:method]}"
|
1188
|
-
end
|
1189
|
-
end
|
1190
|
-
|
1191
|
-
#--
|
1192
|
-
# This is provided as a convenience method to make sure a connection
|
1193
|
-
# object gets closed without waiting for a GC to happen. Clients shouldn't
|
1194
|
-
# have to call it, but perhaps it will come in handy someday.
|
1195
|
-
#++
|
1196
|
-
def close
|
1197
|
-
@conn.close
|
1198
|
-
@conn = nil
|
1199
|
-
end
|
1200
|
-
|
1201
|
-
def next_msgid
|
1202
|
-
@msgid ||= 0
|
1203
|
-
@msgid += 1
|
1204
|
-
end
|
1205
|
-
|
1206
|
-
def bind(auth)
|
1207
|
-
meth = auth[:method]
|
1208
|
-
if [:simple, :anonymous, :anon].include?(meth)
|
1209
|
-
bind_simple auth
|
1210
|
-
elsif meth == :sasl
|
1211
|
-
bind_sasl(auth)
|
1212
|
-
elsif meth == :gss_spnego
|
1213
|
-
bind_gss_spnego(auth)
|
1299
|
+
# Yields an open connection if there is one, otherwise establishes a new
|
1300
|
+
# connection, binds, and yields it. If binding fails, it will return the
|
1301
|
+
# result from that, and :use_connection: will not yield at all. If not
|
1302
|
+
# the return value is whatever is returned from the block.
|
1303
|
+
def use_connection(args)
|
1304
|
+
if @open_connection
|
1305
|
+
yield @open_connection
|
1214
1306
|
else
|
1215
|
-
|
1307
|
+
begin
|
1308
|
+
conn = new_connection
|
1309
|
+
result = conn.bind(args[:auth] || @auth)
|
1310
|
+
return result unless result.result_code == Net::LDAP::ResultCodeSuccess
|
1311
|
+
yield conn
|
1312
|
+
ensure
|
1313
|
+
conn.close if conn
|
1314
|
+
end
|
1216
1315
|
end
|
1217
1316
|
end
|
1218
1317
|
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
@conn.write request_pkt
|
1237
|
-
|
1238
|
-
(be = @conn.read_ber(Net::LDAP::AsnSyntax) and pdu = Net::LDAP::PDU.new(be)) or raise Net::LDAP::LdapError, "no bind result"
|
1239
|
-
|
1240
|
-
pdu.result_code
|
1241
|
-
end
|
1242
|
-
|
1243
|
-
#--
|
1244
|
-
# Required parameters: :mechanism, :initial_credential and
|
1245
|
-
# :challenge_response
|
1246
|
-
#
|
1247
|
-
# Mechanism is a string value that will be passed in the SASL-packet's
|
1248
|
-
# "mechanism" field.
|
1249
|
-
#
|
1250
|
-
# Initial credential is most likely a string. It's passed in the initial
|
1251
|
-
# BindRequest that goes to the server. In some protocols, it may be empty.
|
1252
|
-
#
|
1253
|
-
# Challenge-response is a Ruby proc that takes a single parameter and
|
1254
|
-
# returns an object that will typically be a string. The
|
1255
|
-
# challenge-response block is called when the server returns a
|
1256
|
-
# BindResponse with a result code of 14 (saslBindInProgress). The
|
1257
|
-
# challenge-response block receives a parameter containing the data
|
1258
|
-
# returned by the server in the saslServerCreds field of the LDAP
|
1259
|
-
# BindResponse packet. The challenge-response block may be called multiple
|
1260
|
-
# times during the course of a SASL authentication, and each time it must
|
1261
|
-
# return a value that will be passed back to the server as the credential
|
1262
|
-
# data in the next BindRequest packet.
|
1263
|
-
#++
|
1264
|
-
def bind_sasl(auth)
|
1265
|
-
mech, cred, chall = auth[:mechanism], auth[:initial_credential],
|
1266
|
-
auth[:challenge_response]
|
1267
|
-
raise Net::LDAP::LdapError, "Invalid binding information" unless (mech && cred && chall)
|
1268
|
-
|
1269
|
-
n = 0
|
1270
|
-
loop {
|
1271
|
-
msgid = next_msgid.to_ber
|
1272
|
-
sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
|
1273
|
-
request = [LdapVersion.to_ber, "".to_ber, sasl].to_ber_appsequence(0)
|
1274
|
-
request_pkt = [msgid, request].to_ber_sequence
|
1275
|
-
@conn.write request_pkt
|
1276
|
-
|
1277
|
-
(be = @conn.read_ber(Net::LDAP::AsnSyntax) and pdu = Net::LDAP::PDU.new(be)) or raise Net::LDAP::LdapError, "no bind result"
|
1278
|
-
return pdu.result_code unless pdu.result_code == 14 # saslBindInProgress
|
1279
|
-
raise Net::LDAP::LdapError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
|
1280
|
-
|
1281
|
-
cred = chall.call(pdu.result_server_sasl_creds)
|
1318
|
+
# Establish a new connection to the LDAP server
|
1319
|
+
def new_connection
|
1320
|
+
connection = Net::LDAP::Connection.new \
|
1321
|
+
:host => @host,
|
1322
|
+
:port => @port,
|
1323
|
+
:hosts => @hosts,
|
1324
|
+
:encryption => @encryption,
|
1325
|
+
:instrumentation_service => @instrumentation_service,
|
1326
|
+
:connect_timeout => @connect_timeout
|
1327
|
+
|
1328
|
+
# Force connect to see if there's a connection error
|
1329
|
+
connection.socket
|
1330
|
+
connection
|
1331
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
1332
|
+
@result = {
|
1333
|
+
:resultCode => 52,
|
1334
|
+
:errorMessage => ResultStrings[ResultCodeUnavailable],
|
1282
1335
|
}
|
1283
|
-
|
1284
|
-
raise Net::LDAP::LdapError, "why are we here?"
|
1285
|
-
end
|
1286
|
-
private :bind_sasl
|
1287
|
-
|
1288
|
-
#--
|
1289
|
-
# PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
|
1290
|
-
# Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
|
1291
|
-
# integrate it without introducing an external dependency.
|
1292
|
-
#
|
1293
|
-
# This authentication method is accessed by calling #bind with a :method
|
1294
|
-
# parameter of :gss_spnego. It requires :username and :password
|
1295
|
-
# attributes, just like the :simple authentication method. It performs a
|
1296
|
-
# GSS-SPNEGO authentication with the server, which is presumed to be a
|
1297
|
-
# Microsoft Active Directory.
|
1298
|
-
#++
|
1299
|
-
def bind_gss_spnego(auth)
|
1300
|
-
require 'ntlm'
|
1301
|
-
|
1302
|
-
user, psw = [auth[:username] || auth[:dn], auth[:password]]
|
1303
|
-
raise Net::LDAP::LdapError, "Invalid binding information" unless (user && psw)
|
1304
|
-
|
1305
|
-
nego = proc { |challenge|
|
1306
|
-
t2_msg = NTLM::Message.parse(challenge)
|
1307
|
-
t3_msg = t2_msg.response({ :user => user, :password => psw },
|
1308
|
-
{ :ntlmv2 => true })
|
1309
|
-
t3_msg.serialize
|
1310
|
-
}
|
1311
|
-
|
1312
|
-
bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO",
|
1313
|
-
:initial_credential => NTLM::Message::Type1.new.serialize,
|
1314
|
-
:challenge_response => nego)
|
1315
|
-
end
|
1316
|
-
private :bind_gss_spnego
|
1317
|
-
|
1318
|
-
#--
|
1319
|
-
# Alternate implementation, this yields each search entry to the caller as
|
1320
|
-
# it are received.
|
1321
|
-
#
|
1322
|
-
# TODO: certain search parameters are hardcoded.
|
1323
|
-
# TODO: if we mis-parse the server results or the results are wrong, we
|
1324
|
-
# can block forever. That's because we keep reading results until we get a
|
1325
|
-
# type-5 packet, which might never come. We need to support the time-limit
|
1326
|
-
# in the protocol.
|
1327
|
-
#++
|
1328
|
-
def search(args = {})
|
1329
|
-
search_filter = (args && args[:filter]) ||
|
1330
|
-
Net::LDAP::Filter.eq("objectclass", "*")
|
1331
|
-
search_filter = Net::LDAP::Filter.construct(search_filter) if search_filter.is_a?(String)
|
1332
|
-
search_base = (args && args[:base]) || "dc=example, dc=com"
|
1333
|
-
search_attributes = ((args && args[:attributes]) || []).map { |attr| attr.to_s.to_ber}
|
1334
|
-
return_referrals = args && args[:return_referrals] == true
|
1335
|
-
sizelimit = (args && args[:size].to_i) || 0
|
1336
|
-
raise Net::LDAP::LdapError, "invalid search-size" unless sizelimit >= 0
|
1337
|
-
paged_searches_supported = (args && args[:paged_searches_supported])
|
1338
|
-
|
1339
|
-
attributes_only = (args and args[:attributes_only] == true)
|
1340
|
-
scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
|
1341
|
-
raise Net::LDAP::LdapError, "invalid search scope" unless Net::LDAP::SearchScopes.include?(scope)
|
1342
|
-
|
1343
|
-
# An interesting value for the size limit would be close to A/D's
|
1344
|
-
# built-in page limit of 1000 records, but openLDAP newer than version
|
1345
|
-
# 2.2.0 chokes on anything bigger than 126. You get a silent error that
|
1346
|
-
# is easily visible by running slapd in debug mode. Go figure.
|
1347
|
-
#
|
1348
|
-
# Changed this around 06Sep06 to support a caller-specified search-size
|
1349
|
-
# limit. Because we ALWAYS do paged searches, we have to work around the
|
1350
|
-
# problem that it's not legal to specify a "normal" sizelimit (in the
|
1351
|
-
# body of the search request) that is larger than the page size we're
|
1352
|
-
# requesting. Unfortunately, I have the feeling that this will break
|
1353
|
-
# with LDAP servers that don't support paged searches!!!
|
1354
|
-
#
|
1355
|
-
# (Because we pass zero as the sizelimit on search rounds when the
|
1356
|
-
# remaining limit is larger than our max page size of 126. In these
|
1357
|
-
# cases, I think the caller's search limit will be ignored!)
|
1358
|
-
#
|
1359
|
-
# CONFIRMED: This code doesn't work on LDAPs that don't support paged
|
1360
|
-
# searches when the size limit is larger than 126. We're going to have
|
1361
|
-
# to do a root-DSE record search and not do a paged search if the LDAP
|
1362
|
-
# doesn't support it. Yuck.
|
1363
|
-
rfc2696_cookie = [126, ""]
|
1364
|
-
result_code = 0
|
1365
|
-
n_results = 0
|
1366
|
-
|
1367
|
-
loop {
|
1368
|
-
# should collect this into a private helper to clarify the structure
|
1369
|
-
query_limit = 0
|
1370
|
-
if sizelimit > 0
|
1371
|
-
if paged_searches_supported
|
1372
|
-
query_limit = (((sizelimit - n_results) < 126) ? (sizelimit -
|
1373
|
-
n_results) : 0)
|
1374
|
-
else
|
1375
|
-
query_limit = sizelimit
|
1376
|
-
end
|
1377
|
-
end
|
1378
|
-
|
1379
|
-
request = [
|
1380
|
-
search_base.to_ber,
|
1381
|
-
scope.to_ber_enumerated,
|
1382
|
-
0.to_ber_enumerated,
|
1383
|
-
query_limit.to_ber, # size limit
|
1384
|
-
0.to_ber,
|
1385
|
-
attributes_only.to_ber,
|
1386
|
-
search_filter.to_ber,
|
1387
|
-
search_attributes.to_ber_sequence
|
1388
|
-
].to_ber_appsequence(3)
|
1389
|
-
|
1390
|
-
controls = []
|
1391
|
-
controls <<
|
1392
|
-
[
|
1393
|
-
Net::LDAP::LdapControls::PagedResults.to_ber,
|
1394
|
-
# Criticality MUST be false to interoperate with normal LDAPs.
|
1395
|
-
false.to_ber,
|
1396
|
-
rfc2696_cookie.map{ |v| v.to_ber}.to_ber_sequence.to_s.to_ber
|
1397
|
-
].to_ber_sequence if paged_searches_supported
|
1398
|
-
controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
|
1399
|
-
|
1400
|
-
pkt = [next_msgid.to_ber, request, controls].compact.to_ber_sequence
|
1401
|
-
@conn.write pkt
|
1402
|
-
|
1403
|
-
result_code = 0
|
1404
|
-
controls = []
|
1405
|
-
|
1406
|
-
while (be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be))
|
1407
|
-
case pdu.app_tag
|
1408
|
-
when 4 # search-data
|
1409
|
-
n_results += 1
|
1410
|
-
yield pdu.search_entry if block_given?
|
1411
|
-
when 19 # search-referral
|
1412
|
-
if return_referrals
|
1413
|
-
if block_given?
|
1414
|
-
se = Net::LDAP::Entry.new
|
1415
|
-
se[:search_referrals] = (pdu.search_referrals || [])
|
1416
|
-
yield se
|
1417
|
-
end
|
1418
|
-
end
|
1419
|
-
when 5 # search-result
|
1420
|
-
result_code = pdu.result_code
|
1421
|
-
controls = pdu.result_controls
|
1422
|
-
if return_referrals && result_code == 10
|
1423
|
-
if block_given?
|
1424
|
-
se = Net::LDAP::Entry.new
|
1425
|
-
se[:search_referrals] = (pdu.search_referrals || [])
|
1426
|
-
yield se
|
1427
|
-
end
|
1428
|
-
end
|
1429
|
-
break
|
1430
|
-
else
|
1431
|
-
raise Net::LDAP::LdapError, "invalid response-type in search: #{pdu.app_tag}"
|
1432
|
-
end
|
1433
|
-
end
|
1434
|
-
|
1435
|
-
# When we get here, we have seen a type-5 response. If there is no
|
1436
|
-
# error AND there is an RFC-2696 cookie, then query again for the next
|
1437
|
-
# page of results. If not, we're done. Don't screw this up or we'll
|
1438
|
-
# break every search we do.
|
1439
|
-
#
|
1440
|
-
# Noticed 02Sep06, look at the read_ber call in this loop, shouldn't
|
1441
|
-
# that have a parameter of AsnSyntax? Does this just accidentally
|
1442
|
-
# work? According to RFC-2696, the value expected in this position is
|
1443
|
-
# of type OCTET STRING, covered in the default syntax supported by
|
1444
|
-
# read_ber, so I guess we're ok.
|
1445
|
-
more_pages = false
|
1446
|
-
if result_code == 0 and controls
|
1447
|
-
controls.each do |c|
|
1448
|
-
if c.oid == Net::LDAP::LdapControls::PagedResults
|
1449
|
-
# just in case some bogus server sends us more than 1 of these.
|
1450
|
-
more_pages = false
|
1451
|
-
if c.value and c.value.length > 0
|
1452
|
-
cookie = c.value.read_ber[1]
|
1453
|
-
if cookie and cookie.length > 0
|
1454
|
-
rfc2696_cookie[1] = cookie
|
1455
|
-
more_pages = true
|
1456
|
-
end
|
1457
|
-
end
|
1458
|
-
end
|
1459
|
-
end
|
1460
|
-
end
|
1461
|
-
|
1462
|
-
break unless more_pages
|
1463
|
-
} # loop
|
1464
|
-
|
1465
|
-
result_code
|
1336
|
+
raise e
|
1466
1337
|
end
|
1467
1338
|
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1339
|
+
# Normalize encryption parameter the constructor accepts, expands a few
|
1340
|
+
# convenience symbols into recognizable hashes
|
1341
|
+
def normalize_encryption(args)
|
1342
|
+
return if args.nil?
|
1343
|
+
return args if args.is_a? Hash
|
1473
1344
|
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
operations.each { |op, attrib, values|
|
1478
|
-
# TODO, fix the following line, which gives a bogus error if the
|
1479
|
-
# opcode is invalid.
|
1480
|
-
op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated
|
1481
|
-
values = [ values ].flatten.map { |v| v.to_ber if v }.to_ber_set
|
1482
|
-
values = [ attrib.to_s.to_ber, values ].to_ber_sequence
|
1483
|
-
ops << [ op_ber, values ].to_ber
|
1484
|
-
}
|
1345
|
+
case method = args.to_sym
|
1346
|
+
when :simple_tls, :start_tls
|
1347
|
+
{ :method => method, :tls_options => {} }
|
1485
1348
|
end
|
1486
|
-
ops
|
1487
1349
|
end
|
1488
1350
|
|
1489
|
-
|
1490
|
-
#
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
@conn.write pkt
|
1503
|
-
|
1504
|
-
(be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == 7) or raise Net::LDAP::LdapError, "response missing or invalid"
|
1505
|
-
pdu.result_code
|
1506
|
-
end
|
1507
|
-
|
1508
|
-
#--
|
1509
|
-
# TODO: need to support a time limit, in case the server fails to respond.
|
1510
|
-
# Unlike other operation-methods in this class, we return a result hash
|
1511
|
-
# rather than a simple result number. This is experimental, and eventually
|
1512
|
-
# we'll want to do this with all the others. The point is to have access
|
1513
|
-
# to the error message and the matched-DN returned by the server.
|
1514
|
-
#++
|
1515
|
-
def add(args)
|
1516
|
-
add_dn = args[:dn] or raise Net::LDAP::LdapError, "Unable to add empty DN"
|
1517
|
-
add_attrs = []
|
1518
|
-
a = args[:attributes] and a.each { |k, v|
|
1519
|
-
add_attrs << [ k.to_s.to_ber, Array(v).map { |m| m.to_ber}.to_ber_set ].to_ber_sequence
|
1520
|
-
}
|
1521
|
-
|
1522
|
-
request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8)
|
1523
|
-
pkt = [next_msgid.to_ber, request].to_ber_sequence
|
1524
|
-
@conn.write pkt
|
1525
|
-
|
1526
|
-
(be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == 9) or raise Net::LDAP::LdapError, "response missing or invalid"
|
1527
|
-
pdu.result_code
|
1528
|
-
end
|
1529
|
-
|
1530
|
-
#--
|
1531
|
-
# TODO: need to support a time limit, in case the server fails to respond.
|
1532
|
-
#++
|
1533
|
-
def rename args
|
1534
|
-
old_dn = args[:olddn] or raise "Unable to rename empty DN"
|
1535
|
-
new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN"
|
1536
|
-
delete_attrs = args[:delete_attributes] ? true : false
|
1537
|
-
new_superior = args[:new_superior]
|
1538
|
-
|
1539
|
-
request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber]
|
1540
|
-
request << new_superior.to_ber unless new_superior == nil
|
1541
|
-
|
1542
|
-
pkt = [next_msgid.to_ber, request.to_ber_appsequence(12)].to_ber_sequence
|
1543
|
-
@conn.write pkt
|
1544
|
-
|
1545
|
-
(be = @conn.read_ber(Net::LDAP::AsnSyntax)) &&
|
1546
|
-
(pdu = Net::LDAP::PDU.new( be )) && (pdu.app_tag == 13) or
|
1547
|
-
raise Net::LDAP::LdapError.new( "response missing or invalid" )
|
1548
|
-
pdu.result_code
|
1351
|
+
# Recursively delete a dn and it's subordinate children.
|
1352
|
+
# This is useful when a server does not support the DELETE_TREE control code.
|
1353
|
+
def recursive_delete(args)
|
1354
|
+
raise EmptyDNError unless args.is_a?(Hash) && args.key?(:dn)
|
1355
|
+
# Delete Children
|
1356
|
+
search(base: args[:dn], scope: Net::LDAP::SearchScope_SingleLevel) do |entry|
|
1357
|
+
recursive_delete(dn: entry.dn)
|
1358
|
+
end
|
1359
|
+
# Delete Self
|
1360
|
+
unless delete(dn: args[:dn])
|
1361
|
+
raise Net::LDAP::Error, get_operation_result[:error_message].to_s
|
1362
|
+
end
|
1363
|
+
true
|
1549
1364
|
end
|
1550
1365
|
|
1551
|
-
|
1552
|
-
# TODO, need to support a time limit, in case the server fails to respond.
|
1553
|
-
#++
|
1554
|
-
def delete(args)
|
1555
|
-
dn = args[:dn] or raise "Unable to delete empty DN"
|
1556
|
-
|
1557
|
-
request = dn.to_s.to_ber_application_string(10)
|
1558
|
-
pkt = [next_msgid.to_ber, request].to_ber_sequence
|
1559
|
-
@conn.write pkt
|
1560
|
-
|
1561
|
-
(be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == 11) or raise Net::LDAP::LdapError, "response missing or invalid"
|
1562
|
-
pdu.result_code
|
1563
|
-
end
|
1564
|
-
end # class Connection
|
1366
|
+
end # class LDAP
|