net-ldap 0.11 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.

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.