net-ldap 0.11 → 0.16.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.

Potentially problematic release.


This version of net-ldap might be problematic. Click here for more details.

Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -0
  3. data/.rubocop_todo.yml +471 -180
  4. data/.travis.yml +10 -5
  5. data/Contributors.rdoc +1 -0
  6. data/History.rdoc +60 -0
  7. data/README.rdoc +18 -11
  8. data/Rakefile +0 -1
  9. data/lib/net/ber/ber_parser.rb +4 -4
  10. data/lib/net/ber/core_ext/array.rb +1 -1
  11. data/lib/net/ber/core_ext/integer.rb +1 -1
  12. data/lib/net/ber/core_ext/string.rb +1 -1
  13. data/lib/net/ber.rb +37 -5
  14. data/lib/net/ldap/auth_adapter/gss_spnego.rb +41 -0
  15. data/lib/net/ldap/auth_adapter/sasl.rb +62 -0
  16. data/lib/net/ldap/auth_adapter/simple.rb +34 -0
  17. data/lib/net/ldap/auth_adapter.rb +29 -0
  18. data/lib/net/ldap/connection.rb +197 -187
  19. data/lib/net/ldap/dataset.rb +2 -2
  20. data/lib/net/ldap/dn.rb +4 -5
  21. data/lib/net/ldap/entry.rb +4 -5
  22. data/lib/net/ldap/error.rb +36 -1
  23. data/lib/net/ldap/filter.rb +6 -6
  24. data/lib/net/ldap/pdu.rb +26 -2
  25. data/lib/net/ldap/version.rb +1 -1
  26. data/lib/net/ldap.rb +189 -75
  27. data/lib/net/snmp.rb +18 -18
  28. data/net-ldap.gemspec +4 -2
  29. data/script/changelog +47 -0
  30. data/script/generate-fixture-ca +48 -0
  31. data/script/install-openldap +67 -44
  32. data/test/ber/core_ext/test_array.rb +1 -1
  33. data/test/ber/test_ber.rb +11 -3
  34. data/test/fixtures/ca/ca.info +4 -0
  35. data/test/fixtures/ca/cacert.pem +24 -0
  36. data/test/fixtures/ca/cakey.pem +190 -0
  37. data/test/fixtures/openldap/slapd.conf.ldif +1 -1
  38. data/test/integration/test_add.rb +1 -1
  39. data/test/integration/test_ber.rb +1 -1
  40. data/test/integration/test_bind.rb +220 -10
  41. data/test/integration/test_delete.rb +1 -1
  42. data/test/integration/test_open.rb +1 -1
  43. data/test/integration/test_password_modify.rb +80 -0
  44. data/test/integration/test_search.rb +1 -1
  45. data/test/support/vm/openldap/README.md +35 -3
  46. data/test/support/vm/openldap/Vagrantfile +1 -0
  47. data/test/test_auth_adapter.rb +15 -0
  48. data/test/test_dn.rb +3 -3
  49. data/test/test_filter.rb +4 -4
  50. data/test/test_filter_parser.rb +4 -0
  51. data/test/test_helper.rb +10 -2
  52. data/test/test_ldap.rb +64 -10
  53. data/test/test_ldap_connection.rb +115 -28
  54. data/test/test_ldif.rb +11 -11
  55. data/test/test_search.rb +2 -2
  56. data/test/test_snmp.rb +4 -4
  57. data/testserver/ldapserver.rb +11 -12
  58. metadata +50 -8
  59. data/test/fixtures/cacert.pem +0 -20
@@ -3,29 +3,73 @@
3
3
  class Net::LDAP::Connection #:nodoc:
4
4
  include Net::LDAP::Instrumentation
5
5
 
6
+ # Seconds before failing for socket connect timeout
7
+ DefaultConnectTimeout = 5
8
+
6
9
  LdapVersion = 3
7
- MaxSaslChallenges = 10
8
10
 
9
- def initialize(server)
11
+ # Initialize a connection to an LDAP server
12
+ #
13
+ # :server
14
+ # :hosts Array of tuples specifying host, port
15
+ # :host host
16
+ # :port port
17
+ # :socket prepared socket
18
+ #
19
+ def initialize(server = {})
20
+ @server = server
10
21
  @instrumentation_service = server[:instrumentation_service]
11
22
 
12
- begin
13
- @conn = server[:socket] || TCPSocket.new(server[:host], server[:port])
14
- rescue SocketError
15
- raise Net::LDAP::Error, "No such address or other socket error."
16
- rescue Errno::ECONNREFUSED
17
- raise Net::LDAP::Error, "Server #{server[:host]} refused connection on port #{server[:port]}."
18
- rescue Errno::EHOSTUNREACH => error
19
- raise Net::LDAP::Error, "Host #{server[:host]} was unreachable (#{error.message})"
20
- rescue Errno::ETIMEDOUT
21
- raise Net::LDAP::Error, "Connection to #{server[:host]} timed out."
22
- end
23
+ # Allows tests to parameterize what socket class to use
24
+ @socket_class = server.fetch(:socket_class, DefaultSocket)
25
+
26
+ yield self if block_given?
27
+ end
28
+
29
+ def socket_class=(socket_class)
30
+ @socket_class = socket_class
31
+ end
32
+
33
+ def prepare_socket(server, timeout=nil)
34
+ socket = server[:socket]
35
+ encryption = server[:encryption]
36
+
37
+ @conn = socket
38
+ setup_encryption(encryption, timeout) if encryption
39
+ end
40
+
41
+ def open_connection(server)
42
+ hosts = server[:hosts]
43
+ encryption = server[:encryption]
44
+
45
+ timeout = server[:connect_timeout] || DefaultConnectTimeout
46
+ socket_opts = {
47
+ connect_timeout: timeout,
48
+ }
23
49
 
24
- if server[:encryption]
25
- setup_encryption server[:encryption]
50
+ errors = []
51
+ hosts.each do |host, port|
52
+ begin
53
+ prepare_socket(server.merge(socket: @socket_class.new(host, port, socket_opts)), timeout)
54
+ if encryption
55
+ if encryption[:tls_options] &&
56
+ encryption[:tls_options][:verify_mode] &&
57
+ encryption[:tls_options][:verify_mode] == OpenSSL::SSL::VERIFY_NONE
58
+ warn "not verifying SSL hostname of LDAPS server '#{host}:#{port}'"
59
+ else
60
+ @conn.post_connection_check(host)
61
+ end
62
+ end
63
+ return
64
+ rescue Net::LDAP::Error, SocketError, SystemCallError,
65
+ OpenSSL::SSL::SSLError => e
66
+ # Ensure the connection is closed in the event a setup failure.
67
+ close
68
+ errors << [e, host, port]
69
+ end
26
70
  end
27
71
 
28
- yield self if block_given?
72
+ raise Net::LDAP::ConnectionError.new(errors)
29
73
  end
30
74
 
31
75
  module GetbyteForSSLSocket
@@ -41,7 +85,7 @@ class Net::LDAP::Connection #:nodoc:
41
85
  end
42
86
  end
43
87
 
44
- def self.wrap_with_ssl(io, tls_options = {})
88
+ def self.wrap_with_ssl(io, tls_options = {}, timeout=nil)
45
89
  raise Net::LDAP::NoOpenSSLError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL
46
90
 
47
91
  ctx = OpenSSL::SSL::SSLContext.new
@@ -51,7 +95,22 @@ class Net::LDAP::Connection #:nodoc:
51
95
  ctx.set_params(tls_options) unless tls_options.empty?
52
96
 
53
97
  conn = OpenSSL::SSL::SSLSocket.new(io, ctx)
54
- conn.connect
98
+
99
+ begin
100
+ if timeout
101
+ conn.connect_nonblock
102
+ else
103
+ conn.connect
104
+ end
105
+ rescue IO::WaitReadable
106
+ raise Errno::ETIMEDOUT, "OpenSSL connection read timeout" unless
107
+ IO.select([conn], nil, nil, timeout)
108
+ retry
109
+ rescue IO::WaitWritable
110
+ raise Errno::ETIMEDOUT, "OpenSSL connection write timeout" unless
111
+ IO.select(nil, [conn], nil, timeout)
112
+ retry
113
+ end
55
114
 
56
115
  # Doesn't work:
57
116
  # conn.sync_close = true
@@ -63,18 +122,18 @@ class Net::LDAP::Connection #:nodoc:
63
122
  end
64
123
 
65
124
  #--
66
- # Helper method called only from new, and only after we have a
67
- # successfully-opened @conn instance variable, which is a TCP connection.
68
- # Depending on the received arguments, we establish SSL, potentially
69
- # replacing the value of @conn accordingly. Don't generate any errors here
70
- # if no encryption is requested. DO raise Net::LDAP::Error objects if encryption
71
- # is requested and we have trouble setting it up. That includes if OpenSSL
72
- # is not set up on the machine. (Question: how does the Ruby OpenSSL
73
- # wrapper react in that case?) DO NOT filter exceptions raised by the
74
- # OpenSSL library. Let them pass back to the user. That should make it
75
- # easier for us to debug the problem reports. Presumably (hopefully?) that
76
- # will also produce recognizable errors if someone tries to use this on a
77
- # machine without OpenSSL.
125
+ # Helper method called only from prepare_socket or open_connection, and only
126
+ # after we have a successfully-opened @conn instance variable, which is a TCP
127
+ # connection. Depending on the received arguments, we establish SSL,
128
+ # potentially replacing the value of @conn accordingly. Don't generate any
129
+ # errors here if no encryption is requested. DO raise Net::LDAP::Error objects
130
+ # if encryption is requested and we have trouble setting it up. That includes
131
+ # if OpenSSL is not set up on the machine. (Question: how does the Ruby
132
+ # OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the
133
+ # OpenSSL library. Let them pass back to the user. That should make it easier
134
+ # for us to debug the problem reports. Presumably (hopefully?) that will also
135
+ # produce recognizable errors if someone tries to use this on a machine
136
+ # without OpenSSL.
78
137
  #
79
138
  # The simple_tls method is intended as the simplest, stupidest, easiest
80
139
  # solution for people who want nothing more than encrypted comms with the
@@ -88,17 +147,17 @@ class Net::LDAP::Connection #:nodoc:
88
147
  # communications, as with simple_tls. Thanks for Kouhei Sutou for
89
148
  # generously contributing the :start_tls path.
90
149
  #++
91
- def setup_encryption(args)
150
+ def setup_encryption(args, timeout=nil)
92
151
  args[:tls_options] ||= {}
93
152
  case args[:method]
94
153
  when :simple_tls
95
- @conn = self.class.wrap_with_ssl(@conn, args[:tls_options])
154
+ @conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout)
96
155
  # additional branches requiring server validation and peer certs, etc.
97
156
  # go here.
98
157
  when :start_tls
99
158
  message_id = next_msgid
100
159
  request = [
101
- Net::LDAP::StartTlsOid.to_ber_contextspecific(0)
160
+ Net::LDAP::StartTlsOid.to_ber_contextspecific(0),
102
161
  ].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
103
162
 
104
163
  write(request, nil, message_id)
@@ -108,11 +167,9 @@ class Net::LDAP::Connection #:nodoc:
108
167
  raise Net::LDAP::NoStartTLSResultError, "no start_tls result"
109
168
  end
110
169
 
111
- if pdu.result_code.zero?
112
- @conn = self.class.wrap_with_ssl(@conn, args[:tls_options])
113
- else
114
- raise Net::LDAP::StartTlSError, "start_tls failed: #{pdu.result_code}"
115
- end
170
+ raise Net::LDAP::StartTLSError,
171
+ "start_tls failed: #{pdu.result_code}" unless pdu.result_code.zero?
172
+ @conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout)
116
173
  else
117
174
  raise Net::LDAP::EncMethodUnsupportedError, "unsupported encryption method #{args[:method]}"
118
175
  end
@@ -124,6 +181,7 @@ class Net::LDAP::Connection #:nodoc:
124
181
  # have to call it, but perhaps it will come in handy someday.
125
182
  #++
126
183
  def close
184
+ return if @conn.nil?
127
185
  @conn.close
128
186
  @conn = nil
129
187
  end
@@ -141,12 +199,10 @@ class Net::LDAP::Connection #:nodoc:
141
199
 
142
200
  # read messages until we have a match for the given message_id
143
201
  while pdu = read
144
- if pdu.message_id == message_id
145
- return pdu
146
- else
147
- message_queue[pdu.message_id].push pdu
148
- next
149
- end
202
+ return pdu if pdu.message_id == message_id
203
+
204
+ message_queue[pdu.message_id].push pdu
205
+ next
150
206
  end
151
207
 
152
208
  pdu
@@ -175,7 +231,7 @@ class Net::LDAP::Connection #:nodoc:
175
231
  def read(syntax = Net::LDAP::AsnSyntax)
176
232
  ber_object =
177
233
  instrument "read.net_ldap_connection", :syntax => syntax do |payload|
178
- @conn.read_ber(syntax) do |id, content_length|
234
+ socket.read_ber(syntax) do |id, content_length|
179
235
  payload[:object_type_id] = id
180
236
  payload[:content_length] = content_length
181
237
  end
@@ -205,7 +261,7 @@ class Net::LDAP::Connection #:nodoc:
205
261
  def write(request, controls = nil, message_id = next_msgid)
206
262
  instrument "write.net_ldap_connection" do |payload|
207
263
  packet = [message_id.to_ber, request, controls].compact.to_ber_sequence
208
- payload[:content_length] = @conn.write(packet)
264
+ payload[:content_length] = socket.write(packet)
209
265
  end
210
266
  end
211
267
  private :write
@@ -218,130 +274,11 @@ class Net::LDAP::Connection #:nodoc:
218
274
  def bind(auth)
219
275
  instrument "bind.net_ldap_connection" do |payload|
220
276
  payload[:method] = meth = auth[:method]
221
- if [:simple, :anonymous, :anon].include?(meth)
222
- bind_simple auth
223
- elsif meth == :sasl
224
- bind_sasl(auth)
225
- elsif meth == :gss_spnego
226
- bind_gss_spnego(auth)
227
- else
228
- raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{meth})"
229
- end
277
+ adapter = Net::LDAP::AuthAdapter[meth]
278
+ adapter.new(self).bind(auth)
230
279
  end
231
280
  end
232
281
 
233
- #--
234
- # Implements a simple user/psw authentication. Accessed by calling #bind
235
- # with a method of :simple or :anonymous.
236
- #++
237
- def bind_simple(auth)
238
- user, psw = if auth[:method] == :simple
239
- [auth[:username] || auth[:dn], auth[:password]]
240
- else
241
- ["", ""]
242
- end
243
-
244
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
245
-
246
- message_id = next_msgid
247
- request = [
248
- LdapVersion.to_ber, user.to_ber,
249
- psw.to_ber_contextspecific(0)
250
- ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
251
-
252
- write(request, nil, message_id)
253
- pdu = queued_read(message_id)
254
-
255
- if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
256
- raise Net::LDAP::NoBindResultError, "no bind result"
257
- end
258
-
259
- pdu
260
- end
261
-
262
- #--
263
- # Required parameters: :mechanism, :initial_credential and
264
- # :challenge_response
265
- #
266
- # Mechanism is a string value that will be passed in the SASL-packet's
267
- # "mechanism" field.
268
- #
269
- # Initial credential is most likely a string. It's passed in the initial
270
- # BindRequest that goes to the server. In some protocols, it may be empty.
271
- #
272
- # Challenge-response is a Ruby proc that takes a single parameter and
273
- # returns an object that will typically be a string. The
274
- # challenge-response block is called when the server returns a
275
- # BindResponse with a result code of 14 (saslBindInProgress). The
276
- # challenge-response block receives a parameter containing the data
277
- # returned by the server in the saslServerCreds field of the LDAP
278
- # BindResponse packet. The challenge-response block may be called multiple
279
- # times during the course of a SASL authentication, and each time it must
280
- # return a value that will be passed back to the server as the credential
281
- # data in the next BindRequest packet.
282
- #++
283
- def bind_sasl(auth)
284
- mech, cred, chall = auth[:mechanism], auth[:initial_credential],
285
- auth[:challenge_response]
286
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall)
287
-
288
- message_id = next_msgid
289
-
290
- n = 0
291
- loop {
292
- sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
293
- request = [
294
- LdapVersion.to_ber, "".to_ber, sasl
295
- ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
296
-
297
- write(request, nil, message_id)
298
- pdu = queued_read(message_id)
299
-
300
- if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
301
- raise Net::LDAP::NoBindResultError, "no bind result"
302
- end
303
-
304
- return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress
305
- raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
306
-
307
- cred = chall.call(pdu.result_server_sasl_creds)
308
- }
309
-
310
- raise Net::LDAP::SASLChallengeOverflowError, "why are we here?"
311
- end
312
- private :bind_sasl
313
-
314
- #--
315
- # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
316
- # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
317
- # integrate it without introducing an external dependency.
318
- #
319
- # This authentication method is accessed by calling #bind with a :method
320
- # parameter of :gss_spnego. It requires :username and :password
321
- # attributes, just like the :simple authentication method. It performs a
322
- # GSS-SPNEGO authentication with the server, which is presumed to be a
323
- # Microsoft Active Directory.
324
- #++
325
- def bind_gss_spnego(auth)
326
- require 'ntlm'
327
-
328
- user, psw = [auth[:username] || auth[:dn], auth[:password]]
329
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
330
-
331
- nego = proc { |challenge|
332
- t2_msg = NTLM::Message.parse(challenge)
333
- t3_msg = t2_msg.response({ :user => user, :password => psw },
334
- { :ntlmv2 => true })
335
- t3_msg.serialize
336
- }
337
-
338
- bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO",
339
- :initial_credential => NTLM::Message::Type1.new.serialize,
340
- :challenge_response => nego)
341
- end
342
- private :bind_gss_spnego
343
-
344
-
345
282
  #--
346
283
  # Allow the caller to specify a sort control
347
284
  #
@@ -366,7 +303,7 @@ class Net::LDAP::Connection #:nodoc:
366
303
  sort_control = [
367
304
  Net::LDAP::LDAPControls::SORT_REQUEST.to_ber,
368
305
  false.to_ber,
369
- sort_control_values.to_ber_sequence.to_s.to_ber
306
+ sort_control_values.to_ber_sequence.to_s.to_ber,
370
307
  ].to_ber_sequence
371
308
  end
372
309
 
@@ -463,12 +400,11 @@ class Net::LDAP::Connection #:nodoc:
463
400
  # should collect this into a private helper to clarify the structure
464
401
  query_limit = 0
465
402
  if size > 0
466
- if paged
467
- query_limit = (((size - n_results) < 126) ? (size -
468
- n_results) : 0)
469
- else
470
- query_limit = size
471
- end
403
+ query_limit = if paged
404
+ (((size - n_results) < 126) ? (size - n_results) : 0)
405
+ else
406
+ size
407
+ end
472
408
  end
473
409
 
474
410
  request = [
@@ -479,7 +415,7 @@ class Net::LDAP::Connection #:nodoc:
479
415
  time.to_ber,
480
416
  attrs_only.to_ber,
481
417
  filter.to_ber,
482
- ber_attrs.to_ber_sequence
418
+ ber_attrs.to_ber_sequence,
483
419
  ].to_ber_appsequence(Net::LDAP::PDU::SearchRequest)
484
420
 
485
421
  # rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
@@ -492,7 +428,7 @@ class Net::LDAP::Connection #:nodoc:
492
428
  Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber,
493
429
  # Criticality MUST be false to interoperate with normal LDAPs.
494
430
  false.to_ber,
495
- rfc2696_cookie.map{ |v| v.to_ber}.to_ber_sequence.to_s.to_ber
431
+ rfc2696_cookie.map(&:to_ber).to_ber_sequence.to_s.to_ber,
496
432
  ].to_ber_sequence if paged
497
433
  controls << ber_sort if ber_sort
498
434
  controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
@@ -586,20 +522,20 @@ class Net::LDAP::Connection #:nodoc:
586
522
  MODIFY_OPERATIONS = { #:nodoc:
587
523
  :add => 0,
588
524
  :delete => 1,
589
- :replace => 2
525
+ :replace => 2,
590
526
  }
591
527
 
592
528
  def self.modify_ops(operations)
593
529
  ops = []
594
530
  if operations
595
- operations.each { |op, attrib, values|
531
+ operations.each do |op, attrib, values|
596
532
  # TODO, fix the following line, which gives a bogus error if the
597
533
  # opcode is invalid.
598
534
  op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated
599
- values = [ values ].flatten.map { |v| v.to_ber if v }.to_ber_set
600
- values = [ attrib.to_s.to_ber, values ].to_ber_sequence
601
- ops << [ op_ber, values ].to_ber
602
- }
535
+ values = [values].flatten.map { |v| v.to_ber if v }.to_ber_set
536
+ values = [attrib.to_s.to_ber, values].to_ber_sequence
537
+ ops << [op_ber, values].to_ber
538
+ end
603
539
  end
604
540
  ops
605
541
  end
@@ -618,7 +554,7 @@ class Net::LDAP::Connection #:nodoc:
618
554
  message_id = next_msgid
619
555
  request = [
620
556
  modify_dn.to_ber,
621
- ops.to_ber_sequence
557
+ ops.to_ber_sequence,
622
558
  ].to_ber_appsequence(Net::LDAP::PDU::ModifyRequest)
623
559
 
624
560
  write(request, nil, message_id)
@@ -631,6 +567,51 @@ class Net::LDAP::Connection #:nodoc:
631
567
  pdu
632
568
  end
633
569
 
570
+ ##
571
+ # Password Modify
572
+ #
573
+ # http://tools.ietf.org/html/rfc3062
574
+ #
575
+ # passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1
576
+ #
577
+ # PasswdModifyRequestValue ::= SEQUENCE {
578
+ # userIdentity [0] OCTET STRING OPTIONAL
579
+ # oldPasswd [1] OCTET STRING OPTIONAL
580
+ # newPasswd [2] OCTET STRING OPTIONAL }
581
+ #
582
+ # PasswdModifyResponseValue ::= SEQUENCE {
583
+ # genPasswd [0] OCTET STRING OPTIONAL }
584
+ #
585
+ # Encoded request:
586
+ #
587
+ # 00\x02\x01\x02w+\x80\x171.3.6.1.4.1.4203.1.11.1\x81\x100\x0E\x81\x05old\x82\x05new
588
+ #
589
+ def password_modify(args)
590
+ dn = args[:dn]
591
+ raise ArgumentError, 'DN is required' if !dn || dn.empty?
592
+
593
+ ext_seq = [Net::LDAP::PasswdModifyOid.to_ber_contextspecific(0)]
594
+
595
+ unless args[:old_password].nil?
596
+ pwd_seq = [args[:old_password].to_ber(0x81)]
597
+ pwd_seq << args[:new_password].to_ber(0x82) unless args[:new_password].nil?
598
+ ext_seq << pwd_seq.to_ber_sequence.to_ber(0x81)
599
+ end
600
+
601
+ request = ext_seq.to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
602
+
603
+ message_id = next_msgid
604
+
605
+ write(request, nil, message_id)
606
+ pdu = queued_read(message_id)
607
+
608
+ if !pdu || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse
609
+ raise Net::LDAP::ResponseMissingError, "response missing or invalid"
610
+ end
611
+
612
+ pdu
613
+ end
614
+
634
615
  #--
635
616
  # TODO: need to support a time limit, in case the server fails to respond.
636
617
  # Unlike other operation-methods in this class, we return a result hash
@@ -641,9 +622,9 @@ class Net::LDAP::Connection #:nodoc:
641
622
  def add(args)
642
623
  add_dn = args[:dn] or raise Net::LDAP::EmptyDNError, "Unable to add empty DN"
643
624
  add_attrs = []
644
- a = args[:attributes] and a.each { |k, v|
645
- add_attrs << [ k.to_s.to_ber, Array(v).map { |m| m.to_ber}.to_ber_set ].to_ber_sequence
646
- }
625
+ a = args[:attributes] and a.each do |k, v|
626
+ add_attrs << [k.to_s.to_ber, Array(v).map(&:to_ber).to_ber_set].to_ber_sequence
627
+ end
647
628
 
648
629
  message_id = next_msgid
649
630
  request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(Net::LDAP::PDU::AddRequest)
@@ -652,7 +633,7 @@ class Net::LDAP::Connection #:nodoc:
652
633
  pdu = queued_read(message_id)
653
634
 
654
635
  if !pdu || pdu.app_tag != Net::LDAP::PDU::AddResponse
655
- raise Net::LDAP::ResponseMissingError, "response missing or invalid"
636
+ raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid"
656
637
  end
657
638
 
658
639
  pdu
@@ -699,4 +680,33 @@ class Net::LDAP::Connection #:nodoc:
699
680
 
700
681
  pdu
701
682
  end
683
+
684
+ # Internal: Returns a Socket like object used internally to communicate with
685
+ # LDAP server.
686
+ #
687
+ # Typically a TCPSocket, but can be a OpenSSL::SSL::SSLSocket
688
+ def socket
689
+ return @conn if defined? @conn
690
+
691
+ # First refactoring uses the existing methods open_connection and
692
+ # prepare_socket to set @conn. Next cleanup would centralize connection
693
+ # handling here.
694
+ if @server[:socket]
695
+ prepare_socket(@server)
696
+ else
697
+ @server[:hosts] = [[@server[:host], @server[:port]]] if @server[:hosts].nil?
698
+ open_connection(@server)
699
+ end
700
+
701
+ @conn
702
+ end
703
+
704
+ private
705
+
706
+ # Wrap around Socket.tcp to normalize with other Socket initializers
707
+ class DefaultSocket
708
+ def self.new(host, port, socket_opts = {})
709
+ Socket.tcp(host, port, socket_opts)
710
+ end
711
+ end
702
712
  end # class Connection
@@ -29,7 +29,7 @@ class Net::LDAP::Dataset < Hash
29
29
  keys.sort.each do |dn|
30
30
  ary << "dn: #{dn}"
31
31
 
32
- attributes = self[dn].keys.map { |attr| attr.to_s }.sort
32
+ attributes = self[dn].keys.map(&:to_s).sort
33
33
  attributes.each do |attr|
34
34
  self[dn][attr.to_sym].each do |value|
35
35
  if attr == "userpassword" or value_is_binary?(value)
@@ -141,7 +141,7 @@ class Net::LDAP::Dataset < Hash
141
141
  # $' is the dn-value
142
142
  # Avoid the Base64 class because not all Ruby versions have it.
143
143
  dn = ($1 == ":") ? $'.unpack('m').shift : $'
144
- ds[dn] = Hash.new { |k,v| k[v] = [] }
144
+ ds[dn] = Hash.new { |k, v| k[v] = [] }
145
145
  yield :dn, dn if block_given?
146
146
  elsif line.empty?
147
147
  dn = nil
data/lib/net/ldap/dn.rb CHANGED
@@ -169,11 +169,10 @@ class Net::LDAP::DN
169
169
  end
170
170
 
171
171
  # Last pair
172
- if [:value, :value_normal, :value_hexstring, :value_end].include? state
173
- yield key.string.strip, value.string.rstrip
174
- else
175
- raise "DN badly formed"
176
- end
172
+ raise "DN badly formed" unless
173
+ [:value, :value_normal, :value_hexstring, :value_end].include? state
174
+
175
+ yield key.string.strip, value.string.rstrip
177
176
  end
178
177
 
179
178
  ##
@@ -140,11 +140,10 @@ class Net::LDAP::Entry
140
140
  # arguments to the block: a Symbol giving the name of the attribute, and a
141
141
  # (possibly empty) \Array of data values.
142
142
  def each # :yields: attribute-name, data-values-array
143
- if block_given?
144
- attribute_names.each {|a|
145
- attr_name,values = a,self[a]
146
- yield attr_name, values
147
- }
143
+ return unless block_given?
144
+ attribute_names.each do|a|
145
+ attr_name, values = a, self[a]
146
+ yield attr_name, values
148
147
  end
149
148
  end
150
149
  alias_method :each_attribute, :each
@@ -9,7 +9,42 @@ class Net::LDAP
9
9
 
10
10
  class AlreadyOpenedError < Error; end
11
11
  class SocketError < Error; end
12
- class ConnectionRefusedError < Error; end
12
+ class ConnectionRefusedError < Error;
13
+ def initialize(*args)
14
+ warn_deprecation_message
15
+ super
16
+ end
17
+
18
+ def message
19
+ warn_deprecation_message
20
+ super
21
+ end
22
+
23
+ private
24
+
25
+ def warn_deprecation_message
26
+ warn "Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead."
27
+ end
28
+ end
29
+ class ConnectionError < Error
30
+ def self.new(errors)
31
+ error = errors.first.first
32
+ if errors.size == 1
33
+ if error.kind_of? Errno::ECONNREFUSED
34
+ return Net::LDAP::ConnectionRefusedError.new(error.message)
35
+ end
36
+
37
+ return Net::LDAP::Error.new(error.message)
38
+ end
39
+
40
+ super
41
+ end
42
+
43
+ def initialize(errors)
44
+ message = "Unable to connect to any given server: \n #{errors.map { |e, h, p| "#{e.class}: #{e.message} (#{h}:#{p})" }.join("\n ")}"
45
+ super(message)
46
+ end
47
+ end
13
48
  class NoOpenSSLError < Error; end
14
49
  class NoStartTLSResultError < Error; end
15
50
  class NoSearchBaseError < Error; end
@@ -23,7 +23,7 @@
23
23
  class Net::LDAP::Filter
24
24
  ##
25
25
  # Known filter types.
26
- FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq ]
26
+ FilterTypes = [:ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq]
27
27
 
28
28
  def initialize(op, left, right) #:nodoc:
29
29
  unless FilterTypes.include?(op)
@@ -287,7 +287,7 @@ class Net::LDAP::Filter
287
287
  when 0xa4 # context-specific constructed 4, "substring"
288
288
  str = ""
289
289
  final = false
290
- ber.last.each { |b|
290
+ ber.last.each do |b|
291
291
  case b.ber_identifier
292
292
  when 0x80 # context-specific primitive 0, SubstringFilter "initial"
293
293
  raise Net::LDAP::SubstringFilterError, "Unrecognized substring filter; bad initial value." if str.length > 0
@@ -298,7 +298,7 @@ class Net::LDAP::Filter
298
298
  str += "*#{escape(b)}"
299
299
  final = true
300
300
  end
301
- }
301
+ end
302
302
  str += "*" unless final
303
303
  eq(ber.first.to_s, str)
304
304
  when 0xa5 # context-specific constructed 5, "greaterOrEqual"
@@ -550,10 +550,10 @@ class Net::LDAP::Filter
550
550
  [self.class.eq(@left, @right).to_ber].to_ber_contextspecific(2)
551
551
  when :and
552
552
  ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
553
- ary.map {|a| a.to_ber}.to_ber_contextspecific(0)
553
+ ary.map(&:to_ber).to_ber_contextspecific(0)
554
554
  when :or
555
555
  ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
556
- ary.map {|a| a.to_ber}.to_ber_contextspecific(1)
556
+ ary.map(&:to_ber).to_ber_contextspecific(1)
557
557
  when :not
558
558
  [@left.to_ber].to_ber_contextspecific(2)
559
559
  end
@@ -752,7 +752,7 @@ class Net::LDAP::Filter
752
752
  scanner.scan(/\s*/)
753
753
  if op = scanner.scan(/<=|>=|!=|:=|=/)
754
754
  scanner.scan(/\s*/)
755
- if value = scanner.scan(/(?:[-\[\]{}\w*.+:@=,#\$%&!'^~\s\xC3\x80-\xCA\xAF]|[^\x00-\x7F]|\\[a-fA-F\d]{2})+/u)
755
+ if value = scanner.scan(/(?:[-\[\]{}\w*.+\/:@=,#\$%&!'^~\s\xC3\x80-\xCA\xAF]|[^\x00-\x7F]|\\[a-fA-F\d]{2})+/u)
756
756
  # 20100313 AZ: Assumes that "(uid=george*)" is the same as
757
757
  # "(uid=george* )". The standard doesn't specify, but I can find
758
758
  # no examples that suggest otherwise.