net-ldap 0.0.5 → 0.1.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.

@@ -0,0 +1,112 @@
1
+ require 'stringio'
2
+
3
+ module Net
4
+ module BER
5
+ module BERParser
6
+ VERSION = '0.1.0'
7
+
8
+ # The order of these follows the class-codes in BER.
9
+ # Maybe this should have been a hash.
10
+ TagClasses = [:universal, :application, :context_specific, :private]
11
+
12
+ BuiltinSyntax = Net::BER.compile_syntax( {
13
+ :universal => {
14
+ :primitive => {
15
+ 1 => :boolean,
16
+ 2 => :integer,
17
+ 4 => :string,
18
+ 5 => :null,
19
+ 6 => :oid,
20
+ 10 => :integer,
21
+ 13 => :string # (relative OID)
22
+ },
23
+ :constructed => {
24
+ 16 => :array,
25
+ 17 => :array
26
+ }
27
+ },
28
+ :context_specific => {
29
+ :primitive => {
30
+ 10 => :integer
31
+ }
32
+ }
33
+ })
34
+
35
+ def read_ber syntax=nil
36
+ # TODO: clean this up so it works properly with partial
37
+ # packets coming from streams that don't block when
38
+ # we ask for more data (like StringIOs). At it is,
39
+ # this can throw TypeErrors and other nasties.
40
+
41
+ id = getbyte or return nil # don't trash this value, we'll use it later
42
+
43
+ n = getbyte
44
+ lengthlength,contentlength = if n <= 127
45
+ [1,n]
46
+ else
47
+ # Replaced the inject because it profiles hot.
48
+ # j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
49
+ j = 0
50
+ read( n & 127 ).each_byte {|n1| j = (j << 8) + n1}
51
+ [1 + (n & 127), j]
52
+ end
53
+
54
+ newobj = read contentlength
55
+
56
+ # This exceptionally clever and clear bit of code is verrrry slow.
57
+ objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
58
+
59
+ # == is expensive so sort this if/else so the common cases are at the top.
60
+ obj = if objtype == :string
61
+ #(newobj || "").dup
62
+ s = BerIdentifiedString.new( newobj || "" )
63
+ s.ber_identifier = id
64
+ s
65
+ elsif objtype == :integer
66
+ j = 0
67
+ newobj.each_byte {|b| j = (j << 8) + b}
68
+ j
69
+ elsif objtype == :oid
70
+ # cf X.690 pgh 8.19 for an explanation of this algorithm.
71
+ # Potentially not good enough. We may need a BerIdentifiedOid
72
+ # as a subclass of BerIdentifiedArray, to get the ber identifier
73
+ # and also a to_s method that produces the familiar dotted notation.
74
+ oid = newobj.unpack("w*")
75
+ f = oid.shift
76
+ g = if f < 40
77
+ [0, f]
78
+ elsif f < 80
79
+ [1, f-40]
80
+ else
81
+ [2, f-80] # f-80 can easily be > 80. What a weird optimization.
82
+ end
83
+ oid.unshift g.last
84
+ oid.unshift g.first
85
+ oid
86
+ elsif objtype == :array
87
+ #seq = []
88
+ seq = BerIdentifiedArray.new
89
+ seq.ber_identifier = id
90
+ sio = StringIO.new( newobj || "" )
91
+ # Interpret the subobject, but note how the loop
92
+ # is built: nil ends the loop, but false (a valid
93
+ # BER value) does not!
94
+ while (e = sio.read_ber(syntax)) != nil
95
+ seq << e
96
+ end
97
+ seq
98
+ elsif objtype == :boolean
99
+ newobj != "\000"
100
+ elsif objtype == :null
101
+ n = BerIdentifiedNull.new
102
+ n.ber_identifier = id
103
+ n
104
+ else
105
+ raise BerError.new( "unsupported object type: id=#{id}" )
106
+ end
107
+
108
+ obj
109
+ end
110
+ end
111
+ end
112
+ end
data/lib/net/ldap.rb CHANGED
@@ -1,41 +1,15 @@
1
- # $Id$
2
- #
3
- # Net::LDAP for Ruby
4
- #
5
- #
6
- # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
7
- #
8
- # Written and maintained by Francis Cianfrocca, gmail: garbagecat10.
9
- #
10
- # This program is free software.
11
- # You may re-distribute and/or modify this program under the same terms
12
- # as Ruby itself: Ruby Distribution License or GNU General Public License.
13
- #
14
- #
15
- # See Net::LDAP for documentation and usage samples.
16
- #
17
-
18
-
19
- require 'socket'
1
+ require 'openssl'
20
2
  require 'ostruct'
21
3
 
22
- begin
23
- require 'openssl'
24
- $net_ldap_openssl_available = true
25
- rescue LoadError
26
- end
27
-
28
4
  require 'net/ber'
29
5
  require 'net/ldap/pdu'
30
6
  require 'net/ldap/filter'
31
7
  require 'net/ldap/dataset'
32
8
  require 'net/ldap/psw'
33
9
  require 'net/ldap/entry'
34
-
10
+ require 'net/ldap/core_ext/all'
35
11
 
36
12
  module Net
37
-
38
-
39
13
  # == Net::LDAP
40
14
  #
41
15
  # This library provides a pure-Ruby implementation of the
@@ -257,46 +231,42 @@ module Net
257
231
  # and then disconnect from the server. The exception is Net::LDAP#open, which makes a connection
258
232
  # to the server and then keeps it open while it executes a user-supplied block. Net::LDAP#open
259
233
  # closes the connection on completion of the block.
260
- #
261
-
262
234
  class LDAP
263
-
264
235
  class LdapError < StandardError; end
265
236
 
266
- VERSION = "0.0.5"
267
-
237
+ VERSION = "0.1.0"
268
238
 
269
239
  SearchScope_BaseObject = 0
270
240
  SearchScope_SingleLevel = 1
271
241
  SearchScope_WholeSubtree = 2
272
242
  SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel, SearchScope_WholeSubtree]
273
243
 
274
- AsnSyntax = BER.compile_syntax({
244
+ AsnSyntax = Net::BER.compile_syntax({
275
245
  :application => {
276
- :primitive => {
277
- 2 => :null # UnbindRequest body
278
- },
279
- :constructed => {
280
- 0 => :array, # BindRequest
281
- 1 => :array, # BindResponse
282
- 2 => :array, # UnbindRequest
283
- 3 => :array, # SearchRequest
284
- 4 => :array, # SearchData
285
- 5 => :array, # SearchResult
286
- 6 => :array, # ModifyRequest
287
- 7 => :array, # ModifyResponse
288
- 8 => :array, # AddRequest
289
- 9 => :array, # AddResponse
290
- 10 => :array, # DelRequest
291
- 11 => :array, # DelResponse
292
- 12 => :array, # ModifyRdnRequest
293
- 13 => :array, # ModifyRdnResponse
294
- 14 => :array, # CompareRequest
295
- 15 => :array, # CompareResponse
296
- 16 => :array, # AbandonRequest
297
- 19 => :array, # SearchResultReferral
298
- 24 => :array, # Unsolicited Notification
299
- }
246
+ :primitive => {
247
+ 2 => :null # UnbindRequest body
248
+ },
249
+ :constructed => {
250
+ 0 => :array, # BindRequest
251
+ 1 => :array, # BindResponse
252
+ 2 => :array, # UnbindRequest
253
+ 3 => :array, # SearchRequest
254
+ 4 => :array, # SearchData
255
+ 5 => :array, # SearchResult
256
+ 6 => :array, # ModifyRequest
257
+ 7 => :array, # ModifyResponse
258
+ 8 => :array, # AddRequest
259
+ 9 => :array, # AddResponse
260
+ 10 => :array, # DelRequest
261
+ 11 => :array, # DelResponse
262
+ 12 => :array, # ModifyRdnRequest
263
+ 13 => :array, # ModifyRdnResponse
264
+ 14 => :array, # CompareRequest
265
+ 15 => :array, # CompareResponse
266
+ 16 => :array, # AbandonRequest
267
+ 19 => :array, # SearchResultReferral
268
+ 24 => :array, # Unsolicited Notification
269
+ }
300
270
  },
301
271
  :context_specific => {
302
272
  :primitive => {
@@ -348,23 +318,17 @@ module Net
348
318
  68 => "Entry Already Exists"
349
319
  }
350
320
 
351
-
352
321
  module LdapControls
353
322
  PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
354
323
  end
355
324
 
356
-
357
- #
358
325
  # LDAP::result2string
359
- #
360
326
  def LDAP::result2string code # :nodoc:
361
327
  ResultStrings[code] || "unknown result (#{code})"
362
328
  end
363
329
 
364
-
365
330
  attr_accessor :host, :port, :base
366
331
 
367
-
368
332
  # Instantiate an object of type Net::LDAP to perform directory operations.
369
333
  # This constructor takes a Hash containing arguments, all of which are either optional or may be specified later with other methods as described below. The following arguments
370
334
  # are supported:
@@ -478,12 +442,11 @@ module Net
478
442
  def encryption args
479
443
  case args
480
444
  when :simple_tls, :start_tls
481
- args = {:method => args}
445
+ args = {:method => args}
482
446
  end
483
447
  @encryption = args
484
448
  end
485
449
 
486
-
487
450
  # #open takes the same parameters as #new. #open makes a network connection to the
488
451
  # LDAP server and then passes a newly-created Net::LDAP object to the caller-supplied block.
489
452
  # Within the block, you can call any of the instance methods of Net::LDAP to
@@ -504,30 +467,31 @@ module Net
504
467
  ldap1.open {|ldap| yield ldap }
505
468
  end
506
469
 
507
- # Returns a meaningful result any time after
508
- # a protocol operation (#bind, #search, #add, #modify, #rename, #delete)
509
- # has completed.
510
- # It returns an #OpenStruct containing an LDAP result code (0 means success),
511
- # and a human-readable string.
470
+ # Returns a meaningful result any time after a protocol operation
471
+ # (#bind, #search, #add, #modify, #rename, #delete) has completed.
472
+ # It returns an #OpenStruct containing an LDAP result code (0 means
473
+ # success), and a human-readable string.
474
+ #
512
475
  # unless ldap.bind
513
476
  # puts "Result: #{ldap.get_operation_result.code}"
514
477
  # puts "Message: #{ldap.get_operation_result.message}"
515
478
  # end
516
479
  #
517
- # Certain operations return additional information, accessible through members
518
- # of the object returned from #get_operation_result. Check #get_operation_result.error_message
519
- # and #get_operation_result.matched_dn.
480
+ # Certain operations return additional information, accessible through
481
+ # members of the object returned from #get_operation_result. Check
482
+ # #get_operation_result.error_message and
483
+ # #get_operation_result.matched_dn.
520
484
  #
521
485
  #--
522
- # Modified the implementation, 20Mar07. We might get a hash of LDAP response codes
523
- # instead of a simple numeric code.
524
- #
486
+ # Modified the implementation, 20Mar07. We might get a hash of LDAP
487
+ # response codes instead of a simple numeric code.
488
+ #++
525
489
  def get_operation_result
526
490
  os = OpenStruct.new
527
491
  if @result.is_a?(Hash)
528
- os.code = (@result[:resultCode] || "").to_i
529
- os.error_message = @result[:errorMessage]
530
- os.matched_dn = @result[:matchedDN]
492
+ os.code = (@result[:resultCode] || "").to_i
493
+ os.error_message = @result[:errorMessage]
494
+ os.matched_dn = @result[:matchedDN]
531
495
  elsif @result
532
496
  os.code = @result
533
497
  else
@@ -537,7 +501,6 @@ module Net
537
501
  os
538
502
  end
539
503
 
540
-
541
504
  # Opens a network connection to the server and then
542
505
  # passes <tt>self</tt> to the caller-supplied block. The connection is
543
506
  # closed when the block completes. Used for executing multiple
@@ -561,6 +524,7 @@ module Net
561
524
  # We then pass self to the caller's block, where he will execute
562
525
  # his LDAP operations. Of course they will all generate auth failures
563
526
  # if the bind was unsuccessful.
527
+ #++
564
528
  def open
565
529
  raise LdapError.new( "open already in progress" ) if @open_connection
566
530
  begin
@@ -573,7 +537,6 @@ module Net
573
537
  end
574
538
  end
575
539
 
576
-
577
540
  # Searches the LDAP directory for directory entries.
578
541
  # Takes a hash argument with parameters. Supported parameters include:
579
542
  # * :base (a string specifying the tree-base for the search);
@@ -651,7 +614,7 @@ module Net
651
614
  # handle DNs. Change it to a plain array. Eventually we may
652
615
  # want to return a Dataset object that delegates to an internal
653
616
  # array, so we can provide sort methods and what-not.
654
- #
617
+ #++
655
618
  def search args = {}
656
619
  unless args[:ignore_server_caps]
657
620
  args[:paged_searches_supported] = paged_searches_supported?
@@ -744,8 +707,8 @@ module Net
744
707
  # If there is an @open_connection, then perform the bind
745
708
  # on it. Otherwise, connect, bind, and disconnect.
746
709
  # The latter operation is obviously useful only as an auth check.
747
- #
748
- def bind auth=@auth
710
+ #++
711
+ def bind(auth=@auth)
749
712
  if @open_connection
750
713
  @result = @open_connection.bind auth
751
714
  else
@@ -760,7 +723,6 @@ module Net
760
723
  @result == 0
761
724
  end
762
725
 
763
-
764
726
  #
765
727
  # #bind_as is for testing authentication credentials.
766
728
  #
@@ -820,7 +782,6 @@ module Net
820
782
  result
821
783
  end
822
784
 
823
-
824
785
  # Adds a new entry to the remote LDAP server.
825
786
  # Supported arguments:
826
787
  # :dn :: Full DN of the new entry
@@ -847,7 +808,7 @@ module Net
847
808
  #--
848
809
  # Provisional modification: Connection#add returns a full hash with LDAP status values,
849
810
  # instead of the simple result number we're used to getting.
850
- #
811
+ #++
851
812
  def add args
852
813
  if @open_connection
853
814
  @result = @open_connection.add( args )
@@ -865,7 +826,6 @@ module Net
865
826
  @result == 0
866
827
  end
867
828
 
868
-
869
829
  # Modifies the attribute values of a particular entry on the LDAP directory.
870
830
  # Takes a hash with arguments. Supported arguments are:
871
831
  # :dn :: (the full DN of the entry whose attributes are to be modified)
@@ -963,7 +923,6 @@ module Net
963
923
  @result == 0
964
924
  end
965
925
 
966
-
967
926
  # Add a value to an attribute.
968
927
  # Takes the full DN of the entry to modify,
969
928
  # the name (Symbol or String) of the attribute, and the value (String or
@@ -1017,7 +976,6 @@ module Net
1017
976
  modify :dn => dn, :operations => [[:delete, attribute, nil]]
1018
977
  end
1019
978
 
1020
-
1021
979
  # Rename an entry on the remote DIS by changing the last RDN of its DN.
1022
980
  # _Documentation_ _stub_
1023
981
  #
@@ -1071,7 +1029,6 @@ module Net
1071
1029
  @result == 0
1072
1030
  end
1073
1031
 
1074
-
1075
1032
  # (Experimental, subject to change).
1076
1033
  # Return the rootDSE record from the LDAP server as a Net::LDAP::Entry, or an
1077
1034
  # empty Entry if the server doesn't return the record.
@@ -1084,7 +1041,7 @@ module Net
1084
1041
  # We may be called by #search itself, which may need to determine things like paged
1085
1042
  # search capabilities. So to avoid an infinite regress, set :ignore_server_caps,
1086
1043
  # which prevents us getting called recursively.
1087
- #
1044
+ #++
1088
1045
  def search_root_dse
1089
1046
  rs = search(
1090
1047
  :ignore_server_caps=>true,
@@ -1095,519 +1052,508 @@ module Net
1095
1052
  (rs and rs.first) or Entry.new
1096
1053
  end
1097
1054
 
1055
+ # Return the root Subschema record from the LDAP server as a Net::LDAP::Entry,
1056
+ # or an empty Entry if the server doesn't return the record. On success, the
1057
+ # Net::LDAP::Entry returned from this call will have the attributes :dn,
1058
+ # :objectclasses, and :attributetypes. If there is an error, call #get_operation_result
1059
+ # for more information.
1060
+ #
1061
+ # ldap = Net::LDAP.new
1062
+ # ldap.host = "your.ldap.host"
1063
+ # ldap.auth "your-user-dn", "your-psw"
1064
+ # subschema_entry = ldap.search_subschema_entry
1065
+ #
1066
+ # subschema_entry.attributetypes.each do |attrtype|
1067
+ # # your code
1068
+ # end
1069
+ #
1070
+ # subschema_entry.objectclasses.each do |attrtype|
1071
+ # # your code
1072
+ # end
1073
+ #--
1074
+ # cf. RFC4512 section 4, particulary graff 4.4.
1075
+ # The :dn attribute in the returned Entry is the subschema name as returned from
1076
+ # the server.
1077
+ # Set :ignore_server_caps, see the notes in search_root_dse.
1078
+ #++
1079
+ def search_subschema_entry
1080
+ rs = search(
1081
+ :ignore_server_caps=>true,
1082
+ :base=>"",
1083
+ :scope=>SearchScope_BaseObject,
1084
+ :attributes=>[:subschemaSubentry]
1085
+ )
1086
+ return Entry.new unless (rs and rs.first)
1087
+ subschema_name = rs.first.subschemasubentry
1088
+ return Entry.new unless (subschema_name and subschema_name.first)
1098
1089
 
1099
- # Return the root Subschema record from the LDAP server as a Net::LDAP::Entry,
1100
- # or an empty Entry if the server doesn't return the record. On success, the
1101
- # Net::LDAP::Entry returned from this call will have the attributes :dn,
1102
- # :objectclasses, and :attributetypes. If there is an error, call #get_operation_result
1103
- # for more information.
1104
- #
1105
- # ldap = Net::LDAP.new
1106
- # ldap.host = "your.ldap.host"
1107
- # ldap.auth "your-user-dn", "your-psw"
1108
- # subschema_entry = ldap.search_subschema_entry
1109
- #
1110
- # subschema_entry.attributetypes.each do |attrtype|
1111
- # # your code
1112
- # end
1113
- #
1114
- # subschema_entry.objectclasses.each do |attrtype|
1115
- # # your code
1116
- # end
1117
- #--
1118
- # cf. RFC4512 section 4, particulary graff 4.4.
1119
- # The :dn attribute in the returned Entry is the subschema name as returned from
1120
- # the server.
1121
- # Set :ignore_server_caps, see the notes in search_root_dse.
1122
- #
1123
- def search_subschema_entry
1124
- rs = search(
1125
- :ignore_server_caps=>true,
1126
- :base=>"",
1127
- :scope=>SearchScope_BaseObject,
1128
- :attributes=>[:subschemaSubentry]
1129
- )
1130
- return Entry.new unless (rs and rs.first)
1131
- subschema_name = rs.first.subschemasubentry
1132
- return Entry.new unless (subschema_name and subschema_name.first)
1133
-
1134
- rs = search(
1135
- :ignore_server_caps=>true,
1136
- :base=>subschema_name.first,
1137
- :scope=>SearchScope_BaseObject,
1138
- :filter=>"objectclass=subschema",
1139
- :attributes=>[:objectclasses, :attributetypes]
1140
- )
1141
-
1142
- (rs and rs.first) or Entry.new
1143
- end
1090
+ rs = search(
1091
+ :ignore_server_caps=>true,
1092
+ :base=>subschema_name.first,
1093
+ :scope=>SearchScope_BaseObject,
1094
+ :filter=>"objectclass=subschema",
1095
+ :attributes=>[:objectclasses, :attributetypes]
1096
+ )
1144
1097
 
1098
+ (rs and rs.first) or Entry.new
1099
+ end
1145
1100
 
1146
1101
  #--
1147
1102
  # Convenience method to query server capabilities.
1148
1103
  # Only do this once per Net::LDAP object.
1149
1104
  # Note, we call a search, and we might be called from inside a search!
1150
1105
  # MUST refactor the root_dse call out.
1106
+ #++
1151
1107
  def paged_searches_supported?
1152
1108
  @server_caps ||= search_root_dse
1153
1109
  @server_caps[:supportedcontrol].include?(LdapControls::PagedResults)
1154
1110
  end
1155
-
1156
1111
  end # class LDAP
1157
1112
 
1158
-
1159
-
1160
1113
  class LDAP
1161
- # This is a private class used internally by the library. It should not be called by user code.
1162
- class Connection # :nodoc:
1114
+ # This is a private class used internally by the library. It should not
1115
+ # be called by user code.
1116
+ class Connection # :nodoc:
1117
+ LdapVersion = 3
1118
+ MaxSaslChallenges = 10
1163
1119
 
1164
- LdapVersion = 3
1165
- MaxSaslChallenges = 10
1120
+ def initialize server
1121
+ begin
1122
+ @conn = TCPSocket.new( server[:host], server[:port] )
1123
+ rescue
1124
+ raise LdapError.new( "no connection to server" )
1125
+ end
1166
1126
 
1127
+ if server[:encryption]
1128
+ setup_encryption server[:encryption]
1129
+ end
1167
1130
 
1168
- #--
1169
- # initialize
1170
- #
1171
- def initialize server
1172
- begin
1173
- @conn = TCPSocket.new( server[:host], server[:port] )
1174
- rescue
1175
- raise LdapError.new( "no connection to server" )
1131
+ yield self if block_given?
1176
1132
  end
1177
1133
 
1178
- if server[:encryption]
1179
- setup_encryption server[:encryption]
1134
+ module GetbyteForSSLSocket
1135
+ def getbyte
1136
+ getc.ord
1137
+ end
1180
1138
  end
1181
1139
 
1182
- yield self if block_given?
1183
- end
1184
-
1185
-
1186
- #--
1187
- # Helper method called only from new, and only after we have a successfully-opened
1188
- # @conn instance variable, which is a TCP connection.
1189
- # Depending on the received arguments, we establish SSL, potentially replacing
1190
- # the value of @conn accordingly.
1191
- # Don't generate any errors here if no encryption is requested.
1192
- # DO raise LdapError objects if encryption is requested and we have trouble setting
1193
- # it up. That includes if OpenSSL is not set up on the machine. (Question:
1194
- # how does the Ruby OpenSSL wrapper react in that case?)
1195
- # DO NOT filter exceptions raised by the OpenSSL library. Let them pass back
1196
- # to the user. That should make it easier for us to debug the problem reports.
1197
- # Presumably (hopefully?) that will also produce recognizable errors if someone
1198
- # tries to use this on a machine without OpenSSL.
1199
- #
1200
- # The simple_tls method is intended as the simplest, stupidest, easiest solution
1201
- # for people who want nothing more than encrypted comms with the LDAP server.
1202
- # It doesn't do any server-cert validation and requires nothing in the way
1203
- # of key files and root-cert files, etc etc.
1204
- # OBSERVE: WE REPLACE the value of @conn, which is presumed to be a connected
1205
- # TCPSocket object.
1206
- #
1207
- # The start_tls method is supported by many servers over the standard LDAP port.
1208
- # It does not require an alternative port for encrypted communications, as with
1209
- # simple_tls.
1210
- # Thanks for Kouhei Sutou for generously contributing the :start_tls path.
1211
- #
1212
- def setup_encryption args
1213
- case args[:method]
1214
- when :simple_tls
1215
- raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available
1140
+ def self.wrap_with_ssl(io)
1216
1141
  ctx = OpenSSL::SSL::SSLContext.new
1217
- @conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx)
1218
- @conn.connect
1219
- @conn.sync_close = true
1220
- # additional branches requiring server validation and peer certs, etc. go here.
1221
- when :start_tls
1222
- raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available
1223
- msgid = next_msgid.to_ber
1224
- request = [StartTlsOid.to_ber].to_ber_appsequence( Net::LdapPdu::ExtendedRequest )
1225
- request_pkt = [msgid, request].to_ber_sequence
1226
- @conn.write request_pkt
1227
- be = @conn.read_ber(AsnSyntax)
1228
- raise LdapError.new("no start_tls result") if be.nil?
1229
- pdu = Net::LdapPdu.new(be)
1230
- raise LdapError.new("no start_tls result") if pdu.nil?
1231
- if pdu.result_code.zero?
1232
- ctx = OpenSSL::SSL::SSLContext.new
1233
- @conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx)
1234
- @conn.connect
1235
- @conn.sync_close = true
1236
- else
1237
- raise LdapError.new("start_tls failed: #{pdu.result_code}")
1238
- end
1239
- else
1240
- raise LdapError.new( "unsupported encryption method #{args[:method]}" )
1142
+ conn = OpenSSL::SSL::SSLSocket.new(io, ctx)
1143
+ conn.connect
1144
+ conn.sync_close = true
1145
+
1146
+ conn.extend(GetbyteForSSLSocket) unless conn.respond_to?(:getbyte)
1147
+
1148
+ conn
1241
1149
  end
1242
- end
1243
-
1244
- #--
1245
- # close
1246
- # This is provided as a convenience method to make
1247
- # sure a connection object gets closed without waiting
1248
- # for a GC to happen. Clients shouldn't have to call it,
1249
- # but perhaps it will come in handy someday.
1250
- def close
1251
- @conn.close
1252
- @conn = nil
1253
- end
1254
-
1255
- #--
1256
- # next_msgid
1257
- #
1258
- def next_msgid
1259
- @msgid ||= 0
1260
- @msgid += 1
1261
- end
1262
1150
 
1151
+ #--
1152
+ # Helper method called only from new, and only after we have a successfully-opened
1153
+ # @conn instance variable, which is a TCP connection.
1154
+ # Depending on the received arguments, we establish SSL, potentially replacing
1155
+ # the value of @conn accordingly.
1156
+ # Don't generate any errors here if no encryption is requested.
1157
+ # DO raise LdapError objects if encryption is requested and we have trouble setting
1158
+ # it up. That includes if OpenSSL is not set up on the machine. (Question:
1159
+ # how does the Ruby OpenSSL wrapper react in that case?)
1160
+ # DO NOT filter exceptions raised by the OpenSSL library. Let them pass back
1161
+ # to the user. That should make it easier for us to debug the problem reports.
1162
+ # Presumably (hopefully?) that will also produce recognizable errors if someone
1163
+ # tries to use this on a machine without OpenSSL.
1164
+ #
1165
+ # The simple_tls method is intended as the simplest, stupidest, easiest solution
1166
+ # for people who want nothing more than encrypted comms with the LDAP server.
1167
+ # It doesn't do any server-cert validation and requires nothing in the way
1168
+ # of key files and root-cert files, etc etc.
1169
+ # OBSERVE: WE REPLACE the value of @conn, which is presumed to be a connected
1170
+ # TCPSocket object.
1171
+ #
1172
+ # The start_tls method is supported by many servers over the standard LDAP port.
1173
+ # It does not require an alternative port for encrypted communications, as with
1174
+ # simple_tls.
1175
+ # Thanks for Kouhei Sutou for generously contributing the :start_tls path.
1176
+ #++
1177
+ def setup_encryption args
1178
+ case args[:method]
1179
+ when :simple_tls
1180
+ @conn = self.class.wrap_with_ssl(@conn)
1181
+ # additional branches requiring server validation and peer certs, etc. go here.
1182
+ when :start_tls
1183
+ msgid = next_msgid.to_ber
1184
+ request = [StartTlsOid.to_ber].to_ber_appsequence( Net::LdapPdu::ExtendedRequest )
1185
+ request_pkt = [msgid, request].to_ber_sequence
1186
+ @conn.write request_pkt
1187
+ be = @conn.read_ber(AsnSyntax)
1188
+ raise LdapError.new("no start_tls result") if be.nil?
1189
+ pdu = Net::LdapPdu.new(be)
1190
+ raise LdapError.new("no start_tls result") if pdu.nil?
1191
+ if pdu.result_code.zero?
1192
+ @conn = self.class.wrap_with_ssl(@conn)
1193
+ else
1194
+ raise LdapError.new("start_tls failed: #{pdu.result_code}")
1195
+ end
1196
+ else
1197
+ raise LdapError.new( "unsupported encryption method #{args[:method]}" )
1198
+ end
1199
+ end
1263
1200
 
1264
- #--
1265
- # bind
1266
- #
1267
- def bind auth
1268
- meth = auth[:method]
1269
- if [:simple, :anonymous, :anon].include?( meth )
1270
- bind_simple auth
1271
- elsif meth == :sasl
1272
- bind_sasl( auth )
1273
- elsif meth == :gss_spnego
1274
- bind_gss_spnego( auth )
1275
- else
1276
- raise LdapError.new( "unsupported auth method (#{meth})" )
1201
+ #--
1202
+ # close
1203
+ # This is provided as a convenience method to make
1204
+ # sure a connection object gets closed without waiting
1205
+ # for a GC to happen. Clients shouldn't have to call it,
1206
+ # but perhaps it will come in handy someday.
1207
+ #++
1208
+ def close
1209
+ @conn.close
1210
+ @conn = nil
1277
1211
  end
1278
- end
1279
1212
 
1280
- #--
1281
- # bind_simple
1282
- # Implements a simple user/psw authentication.
1283
- # Accessed by calling #bind with a method of :simple or :anonymous.
1284
- #
1285
- def bind_simple auth
1286
- user,psw = if auth[:method] == :simple
1287
- [auth[:username] || auth[:dn], auth[:password]]
1288
- else
1289
- ["",""]
1213
+ #--
1214
+ # next_msgid
1215
+ #++
1216
+ def next_msgid
1217
+ @msgid ||= 0
1218
+ @msgid += 1
1290
1219
  end
1291
1220
 
1292
- raise LdapError.new( "invalid binding information" ) unless (user && psw)
1221
+ #--
1222
+ # bind
1223
+ #++
1224
+ def bind auth
1225
+ meth = auth[:method]
1226
+ if [:simple, :anonymous, :anon].include?( meth )
1227
+ bind_simple auth
1228
+ elsif meth == :sasl
1229
+ bind_sasl( auth )
1230
+ elsif meth == :gss_spnego
1231
+ bind_gss_spnego( auth )
1232
+ else
1233
+ raise LdapError.new( "unsupported auth method (#{meth})" )
1234
+ end
1235
+ end
1293
1236
 
1294
- msgid = next_msgid.to_ber
1295
- request = [LdapVersion.to_ber, user.to_ber, psw.to_ber_contextspecific(0)].to_ber_appsequence(0)
1296
- request_pkt = [msgid, request].to_ber_sequence
1297
- @conn.write request_pkt
1237
+ #--
1238
+ # bind_simple
1239
+ # Implements a simple user/psw authentication.
1240
+ # Accessed by calling #bind with a method of :simple or :anonymous.
1241
+ #++
1242
+ def bind_simple auth
1243
+ user,psw = if auth[:method] == :simple
1244
+ [auth[:username] || auth[:dn], auth[:password]]
1245
+ else
1246
+ ["",""]
1247
+ end
1298
1248
 
1299
- (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" )
1300
- pdu.result_code
1301
- end
1249
+ raise LdapError.new( "invalid binding information" ) unless (user && psw)
1302
1250
 
1303
- #--
1304
- # bind_sasl
1305
- # Required parameters: :mechanism, :initial_credential and :challenge_response
1306
- # Mechanism is a string value that will be passed in the SASL-packet's "mechanism" field.
1307
- # Initial credential is most likely a string. It's passed in the initial BindRequest
1308
- # that goes to the server. In some protocols, it may be empty.
1309
- # Challenge-response is a Ruby proc that takes a single parameter and returns an object
1310
- # that will typically be a string. The challenge-response block is called when the server
1311
- # returns a BindResponse with a result code of 14 (saslBindInProgress). The challenge-response
1312
- # block receives a parameter containing the data returned by the server in the saslServerCreds
1313
- # field of the LDAP BindResponse packet. The challenge-response block may be called multiple
1314
- # times during the course of a SASL authentication, and each time it must return a value
1315
- # that will be passed back to the server as the credential data in the next BindRequest packet.
1316
- #
1317
- def bind_sasl auth
1318
- mech,cred,chall = auth[:mechanism],auth[:initial_credential],auth[:challenge_response]
1319
- raise LdapError.new( "invalid binding information" ) unless (mech && cred && chall)
1320
-
1321
- n = 0
1322
- loop {
1323
1251
  msgid = next_msgid.to_ber
1324
- sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
1325
- request = [LdapVersion.to_ber, "".to_ber, sasl].to_ber_appsequence(0)
1252
+ request = [LdapVersion.to_ber, user.to_ber, psw.to_ber_contextspecific(0)].to_ber_appsequence(0)
1326
1253
  request_pkt = [msgid, request].to_ber_sequence
1327
1254
  @conn.write request_pkt
1328
1255
 
1329
1256
  (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" )
1330
- return pdu.result_code unless pdu.result_code == 14 # saslBindInProgress
1331
- raise LdapError.new("sasl-challenge overflow") if ((n += 1) > MaxSaslChallenges)
1257
+ pdu.result_code
1258
+ end
1332
1259
 
1333
- cred = chall.call( pdu.result_server_sasl_creds )
1334
- }
1260
+ #--
1261
+ # bind_sasl
1262
+ # Required parameters: :mechanism, :initial_credential and :challenge_response
1263
+ # Mechanism is a string value that will be passed in the SASL-packet's "mechanism" field.
1264
+ # Initial credential is most likely a string. It's passed in the initial BindRequest
1265
+ # that goes to the server. In some protocols, it may be empty.
1266
+ # Challenge-response is a Ruby proc that takes a single parameter and returns an object
1267
+ # that will typically be a string. The challenge-response block is called when the server
1268
+ # returns a BindResponse with a result code of 14 (saslBindInProgress). The challenge-response
1269
+ # block receives a parameter containing the data returned by the server in the saslServerCreds
1270
+ # field of the LDAP BindResponse packet. The challenge-response block may be called multiple
1271
+ # times during the course of a SASL authentication, and each time it must return a value
1272
+ # that will be passed back to the server as the credential data in the next BindRequest packet.
1273
+ #++
1274
+ def bind_sasl auth
1275
+ mech,cred,chall = auth[:mechanism],auth[:initial_credential],auth[:challenge_response]
1276
+ raise LdapError.new( "invalid binding information" ) unless (mech && cred && chall)
1277
+
1278
+ n = 0
1279
+ loop {
1280
+ msgid = next_msgid.to_ber
1281
+ sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
1282
+ request = [LdapVersion.to_ber, "".to_ber, sasl].to_ber_appsequence(0)
1283
+ request_pkt = [msgid, request].to_ber_sequence
1284
+ @conn.write request_pkt
1285
+
1286
+ (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" )
1287
+ return pdu.result_code unless pdu.result_code == 14 # saslBindInProgress
1288
+ raise LdapError.new("sasl-challenge overflow") if ((n += 1) > MaxSaslChallenges)
1289
+
1290
+ cred = chall.call( pdu.result_server_sasl_creds )
1291
+ }
1335
1292
 
1336
- raise LdapError.new( "why are we here?")
1337
- end
1338
- private :bind_sasl
1293
+ raise LdapError.new( "why are we here?")
1294
+ end
1295
+ private :bind_sasl
1296
+
1297
+ #--
1298
+ # bind_gss_spnego
1299
+ # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
1300
+ # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to integrate it without
1301
+ # introducing an external dependency.
1302
+ # This authentication method is accessed by calling #bind with a :method parameter of
1303
+ # :gss_spnego. It requires :username and :password attributes, just like the :simple
1304
+ # authentication method. It performs a GSS-SPNEGO authentication with the server, which
1305
+ # is presumed to be a Microsoft Active Directory.
1306
+ #++
1307
+ def bind_gss_spnego auth
1308
+ require 'ntlm.rb'
1309
+
1310
+ user,psw = [auth[:username] || auth[:dn], auth[:password]]
1311
+ raise LdapError.new( "invalid binding information" ) unless (user && psw)
1312
+
1313
+ nego = proc {|challenge|
1314
+ t2_msg = NTLM::Message.parse( challenge )
1315
+ t3_msg = t2_msg.response( {:user => user, :password => psw}, {:ntlmv2 => true} )
1316
+ t3_msg.serialize
1317
+ }
1339
1318
 
1340
- #--
1341
- # bind_gss_spnego
1342
- # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
1343
- # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to integrate it without
1344
- # introducing an external dependency.
1345
- # This authentication method is accessed by calling #bind with a :method parameter of
1346
- # :gss_spnego. It requires :username and :password attributes, just like the :simple
1347
- # authentication method. It performs a GSS-SPNEGO authentication with the server, which
1348
- # is presumed to be a Microsoft Active Directory.
1349
- #
1350
- def bind_gss_spnego auth
1351
- require 'ntlm.rb'
1352
-
1353
- user,psw = [auth[:username] || auth[:dn], auth[:password]]
1354
- raise LdapError.new( "invalid binding information" ) unless (user && psw)
1355
-
1356
- nego = proc {|challenge|
1357
- t2_msg = NTLM::Message.parse( challenge )
1358
- t3_msg = t2_msg.response( {:user => user, :password => psw}, {:ntlmv2 => true} )
1359
- t3_msg.serialize
1360
- }
1319
+ bind_sasl( {
1320
+ :method => :sasl,
1321
+ :mechanism => "GSS-SPNEGO",
1322
+ :initial_credential => NTLM::Message::Type1.new.serialize,
1323
+ :challenge_response => nego
1324
+ })
1325
+ end
1326
+ private :bind_gss_spnego
1327
+
1328
+ #--
1329
+ # search
1330
+ # Alternate implementation, this yields each search entry to the caller
1331
+ # as it are received.
1332
+ # TODO, certain search parameters are hardcoded.
1333
+ # TODO, if we mis-parse the server results or the results are wrong, we can block
1334
+ # forever. That's because we keep reading results until we get a type-5 packet,
1335
+ # which might never come. We need to support the time-limit in the protocol.
1336
+ #--
1337
+ # WARNING: this code substantially recapitulates the searchx method.
1338
+ #
1339
+ # 02May06: Well, I added support for RFC-2696-style paged searches.
1340
+ # This is used on all queries because the extension is marked non-critical.
1341
+ # As far as I know, only A/D uses this, but it's required for A/D. Otherwise
1342
+ # you won't get more than 1000 results back from a query.
1343
+ # This implementation is kindof clunky and should probably be refactored.
1344
+ # Also, is it my imagination, or are A/Ds the slowest directory servers ever???
1345
+ # OpenLDAP newer than version 2.2.0 supports paged searches.
1346
+ #++
1347
+ def search args = {}
1348
+ search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
1349
+ search_filter = Filter.construct(search_filter) if search_filter.is_a?(String)
1350
+ search_base = (args && args[:base]) || "dc=example,dc=com"
1351
+ search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
1352
+ return_referrals = args && args[:return_referrals] == true
1353
+ sizelimit = (args && args[:size].to_i) || 0
1354
+ raise LdapError.new( "invalid search-size" ) unless sizelimit >= 0
1355
+ paged_searches_supported = (args && args[:paged_searches_supported])
1356
+
1357
+ attributes_only = (args and args[:attributes_only] == true)
1358
+ scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
1359
+ raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope)
1360
+
1361
+ # An interesting value for the size limit would be close to A/D's built-in
1362
+ # page limit of 1000 records, but openLDAP newer than version 2.2.0 chokes
1363
+ # on anything bigger than 126. You get a silent error that is easily visible
1364
+ # by running slapd in debug mode. Go figure.
1365
+ #
1366
+ # Changed this around 06Sep06 to support a caller-specified search-size limit.
1367
+ # Because we ALWAYS do paged searches, we have to work around the problem that
1368
+ # it's not legal to specify a "normal" sizelimit (in the body of the search request)
1369
+ # that is larger than the page size we're requesting. Unfortunately, I have the
1370
+ # feeling that this will break with LDAP servers that don't support paged searches!!!
1371
+ # (Because we pass zero as the sizelimit on search rounds when the remaining limit
1372
+ # is larger than our max page size of 126. In these cases, I think the caller's
1373
+ # search limit will be ignored!)
1374
+ # CONFIRMED: This code doesn't work on LDAPs that don't support paged searches
1375
+ # when the size limit is larger than 126. We're going to have to do a root-DSE record
1376
+ # search and not do a paged search if the LDAP doesn't support it. Yuck.
1377
+ #
1378
+ rfc2696_cookie = [126, ""]
1379
+ result_code = 0
1380
+ n_results = 0
1361
1381
 
1362
- bind_sasl( {
1363
- :method => :sasl,
1364
- :mechanism => "GSS-SPNEGO",
1365
- :initial_credential => NTLM::Message::Type1.new.serialize,
1366
- :challenge_response => nego
1367
- })
1368
- end
1369
- private :bind_gss_spnego
1382
+ loop {
1383
+ # should collect this into a private helper to clarify the structure
1370
1384
 
1371
- #--
1372
- # search
1373
- # Alternate implementation, this yields each search entry to the caller
1374
- # as it are received.
1375
- # TODO, certain search parameters are hardcoded.
1376
- # TODO, if we mis-parse the server results or the results are wrong, we can block
1377
- # forever. That's because we keep reading results until we get a type-5 packet,
1378
- # which might never come. We need to support the time-limit in the protocol.
1379
- #--
1380
- # WARNING: this code substantially recapitulates the searchx method.
1381
- #
1382
- # 02May06: Well, I added support for RFC-2696-style paged searches.
1383
- # This is used on all queries because the extension is marked non-critical.
1384
- # As far as I know, only A/D uses this, but it's required for A/D. Otherwise
1385
- # you won't get more than 1000 results back from a query.
1386
- # This implementation is kindof clunky and should probably be refactored.
1387
- # Also, is it my imagination, or are A/Ds the slowest directory servers ever???
1388
- # OpenLDAP newer than version 2.2.0 supports paged searches.
1389
- #
1390
- def search args = {}
1391
- search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
1392
- search_filter = Filter.construct(search_filter) if search_filter.is_a?(String)
1393
- search_base = (args && args[:base]) || "dc=example,dc=com"
1394
- search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
1395
- return_referrals = args && args[:return_referrals] == true
1396
- sizelimit = (args && args[:size].to_i) || 0
1397
- raise LdapError.new( "invalid search-size" ) unless sizelimit >= 0
1398
- paged_searches_supported = (args && args[:paged_searches_supported])
1399
-
1400
- attributes_only = (args and args[:attributes_only] == true)
1401
- scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
1402
- raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope)
1403
-
1404
- # An interesting value for the size limit would be close to A/D's built-in
1405
- # page limit of 1000 records, but openLDAP newer than version 2.2.0 chokes
1406
- # on anything bigger than 126. You get a silent error that is easily visible
1407
- # by running slapd in debug mode. Go figure.
1408
- #
1409
- # Changed this around 06Sep06 to support a caller-specified search-size limit.
1410
- # Because we ALWAYS do paged searches, we have to work around the problem that
1411
- # it's not legal to specify a "normal" sizelimit (in the body of the search request)
1412
- # that is larger than the page size we're requesting. Unfortunately, I have the
1413
- # feeling that this will break with LDAP servers that don't support paged searches!!!
1414
- # (Because we pass zero as the sizelimit on search rounds when the remaining limit
1415
- # is larger than our max page size of 126. In these cases, I think the caller's
1416
- # search limit will be ignored!)
1417
- # CONFIRMED: This code doesn't work on LDAPs that don't support paged searches
1418
- # when the size limit is larger than 126. We're going to have to do a root-DSE record
1419
- # search and not do a paged search if the LDAP doesn't support it. Yuck.
1420
- #
1421
- rfc2696_cookie = [126, ""]
1422
- result_code = 0
1423
- n_results = 0
1424
-
1425
- loop {
1426
- # should collect this into a private helper to clarify the structure
1427
-
1428
- query_limit = 0
1429
- if sizelimit > 0
1430
- if paged_searches_supported
1431
- query_limit = (((sizelimit - n_results) < 126) ? (sizelimit - n_results) : 0)
1432
- else
1433
- query_limit = sizelimit
1385
+ query_limit = 0
1386
+ if sizelimit > 0
1387
+ if paged_searches_supported
1388
+ query_limit = (((sizelimit - n_results) < 126) ? (sizelimit - n_results) : 0)
1389
+ else
1390
+ query_limit = sizelimit
1391
+ end
1434
1392
  end
1435
- end
1436
1393
 
1437
- request = [
1438
- search_base.to_ber,
1439
- scope.to_ber_enumerated,
1440
- 0.to_ber_enumerated,
1441
- query_limit.to_ber, # size limit
1442
- 0.to_ber,
1443
- attributes_only.to_ber,
1444
- search_filter.to_ber,
1445
- search_attributes.to_ber_sequence
1446
- ].to_ber_appsequence(3)
1394
+ request = [
1395
+ search_base.to_ber,
1396
+ scope.to_ber_enumerated,
1397
+ 0.to_ber_enumerated,
1398
+ query_limit.to_ber, # size limit
1399
+ 0.to_ber,
1400
+ attributes_only.to_ber,
1401
+ search_filter.to_ber,
1402
+ search_attributes.to_ber_sequence
1403
+ ].to_ber_appsequence(3)
1447
1404
 
1448
- controls = [
1449
- [
1450
- LdapControls::PagedResults.to_ber,
1451
- false.to_ber, # criticality MUST be false to interoperate with normal LDAPs.
1452
- rfc2696_cookie.map{|v| v.to_ber}.to_ber_sequence.to_s.to_ber
1453
- ].to_ber_sequence
1454
- ].to_ber_contextspecific(0)
1455
-
1456
- pkt = [next_msgid.to_ber, request, controls].to_ber_sequence
1457
- @conn.write pkt
1458
-
1459
- result_code = 0
1460
- controls = []
1461
-
1462
- while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
1463
- case pdu.app_tag
1464
- when 4 # search-data
1465
- n_results += 1
1466
- yield( pdu.search_entry ) if block_given?
1467
- when 19 # search-referral
1468
- if return_referrals
1469
- if block_given?
1470
- se = Net::LDAP::Entry.new
1471
- se[:search_referrals] = (pdu.search_referrals || [])
1472
- yield se
1405
+ controls = [
1406
+ [
1407
+ LdapControls::PagedResults.to_ber,
1408
+ false.to_ber, # criticality MUST be false to interoperate with normal LDAPs.
1409
+ rfc2696_cookie.map{|v| v.to_ber}.to_ber_sequence.to_s.to_ber
1410
+ ].to_ber_sequence
1411
+ ].to_ber_contextspecific(0)
1412
+
1413
+ pkt = [next_msgid.to_ber, request, controls].to_ber_sequence
1414
+ @conn.write pkt
1415
+
1416
+ result_code = 0
1417
+ controls = []
1418
+
1419
+ while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
1420
+ case pdu.app_tag
1421
+ when 4 # search-data
1422
+ n_results += 1
1423
+ yield( pdu.search_entry ) if block_given?
1424
+ when 19 # search-referral
1425
+ if return_referrals
1426
+ if block_given?
1427
+ se = Net::LDAP::Entry.new
1428
+ se[:search_referrals] = (pdu.search_referrals || [])
1429
+ yield se
1430
+ end
1473
1431
  end
1432
+ #p pdu.referrals
1433
+ when 5 # search-result
1434
+ result_code = pdu.result_code
1435
+ controls = pdu.result_controls
1436
+ break
1437
+ else
1438
+ raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
1474
1439
  end
1475
- #p pdu.referrals
1476
- when 5 # search-result
1477
- result_code = pdu.result_code
1478
- controls = pdu.result_controls
1479
- break
1480
- else
1481
- raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
1482
1440
  end
1483
- end
1484
1441
 
1485
- # When we get here, we have seen a type-5 response.
1486
- # If there is no error AND there is an RFC-2696 cookie,
1487
- # then query again for the next page of results.
1488
- # If not, we're done.
1489
- # Don't screw this up or we'll break every search we do.
1490
- #
1491
- # Noticed 02Sep06, look at the read_ber call in this loop,
1492
- # shouldn't that have a parameter of AsnSyntax? Does this
1493
- # just accidentally work? According to RFC-2696, the value
1494
- # expected in this position is of type OCTET STRING, covered
1495
- # in the default syntax supported by read_ber, so I guess
1496
- # we're ok.
1497
- #
1498
- more_pages = false
1499
- if result_code == 0 and controls
1500
- controls.each do |c|
1501
- if c.oid == LdapControls::PagedResults
1502
- more_pages = false # just in case some bogus server sends us >1 of these.
1503
- if c.value and c.value.length > 0
1504
- cookie = c.value.read_ber[1]
1505
- if cookie and cookie.length > 0
1506
- rfc2696_cookie[1] = cookie
1507
- more_pages = true
1442
+ # When we get here, we have seen a type-5 response.
1443
+ # If there is no error AND there is an RFC-2696 cookie,
1444
+ # then query again for the next page of results.
1445
+ # If not, we're done.
1446
+ # Don't screw this up or we'll break every search we do.
1447
+ #
1448
+ # Noticed 02Sep06, look at the read_ber call in this loop,
1449
+ # shouldn't that have a parameter of AsnSyntax? Does this
1450
+ # just accidentally work? According to RFC-2696, the value
1451
+ # expected in this position is of type OCTET STRING, covered
1452
+ # in the default syntax supported by read_ber, so I guess
1453
+ # we're ok.
1454
+ #
1455
+ more_pages = false
1456
+ if result_code == 0 and controls
1457
+ controls.each do |c|
1458
+ if c.oid == LdapControls::PagedResults
1459
+ more_pages = false # just in case some bogus server sends us >1 of these.
1460
+ if c.value and c.value.length > 0
1461
+ cookie = c.value.read_ber[1]
1462
+ if cookie and cookie.length > 0
1463
+ rfc2696_cookie[1] = cookie
1464
+ more_pages = true
1465
+ end
1508
1466
  end
1509
1467
  end
1510
1468
  end
1511
1469
  end
1512
- end
1513
1470
 
1514
- break unless more_pages
1515
- } # loop
1471
+ break unless more_pages
1472
+ } # loop
1516
1473
 
1517
- result_code
1518
- end
1519
-
1520
-
1521
-
1522
- #--
1523
- # modify
1524
- # TODO, need to support a time limit, in case the server fails to respond.
1525
- # TODO!!! We're throwing an exception here on empty DN.
1526
- # Should return a proper error instead, probaby from farther up the chain.
1527
- # TODO!!! If the user specifies a bogus opcode, we'll throw a
1528
- # confusing error here ("to_ber_enumerated is not defined on nil").
1529
- #
1530
- def modify args
1531
- modify_dn = args[:dn] or raise "Unable to modify empty DN"
1532
- modify_ops = []
1533
- a = args[:operations] and a.each {|op, attr, values|
1534
- # TODO, fix the following line, which gives a bogus error
1535
- # if the opcode is invalid.
1536
- op_1 = {:add => 0, :delete => 1, :replace => 2} [op.to_sym].to_ber_enumerated
1537
- modify_ops << [op_1, [attr.to_s.to_ber, values.to_a.map {|v| v.to_ber}.to_ber_set].to_ber_sequence].to_ber_sequence
1538
- }
1539
-
1540
- request = [modify_dn.to_ber, modify_ops.to_ber_sequence].to_ber_appsequence(6)
1541
- pkt = [next_msgid.to_ber, request].to_ber_sequence
1542
- @conn.write pkt
1543
-
1544
- (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 7) or raise LdapError.new( "response missing or invalid" )
1545
- pdu.result
1546
- end
1547
-
1548
-
1549
- #--
1550
- # add
1551
- # TODO, need to support a time limit, in case the server fails to respond.
1552
- # Unlike other operation-methods in this class, we return a result hash rather
1553
- # than a simple result number. This is experimental, and eventually we'll want
1554
- # to do this with all the others. The point is to have access to the error message
1555
- # and the matched-DN returned by the server.
1556
- #
1557
- def add args
1558
- add_dn = args[:dn] or raise LdapError.new("Unable to add empty DN")
1559
- add_attrs = []
1560
- a = args[:attributes] and a.each {|k,v|
1561
- add_attrs << [ k.to_s.to_ber, v.to_a.map {|m| m.to_ber}.to_ber_set ].to_ber_sequence
1562
- }
1563
-
1564
- request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8)
1565
- pkt = [next_msgid.to_ber, request].to_ber_sequence
1566
- @conn.write pkt
1474
+ result_code
1475
+ end
1567
1476
 
1568
- (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 9) or raise LdapError.new( "response missing or invalid" )
1569
- pdu.result
1570
- end
1477
+ #--
1478
+ # modify
1479
+ # TODO, need to support a time limit, in case the server fails to respond.
1480
+ # TODO!!! We're throwing an exception here on empty DN.
1481
+ # Should return a proper error instead, probaby from farther up the chain.
1482
+ # TODO!!! If the user specifies a bogus opcode, we'll throw a
1483
+ # confusing error here ("to_ber_enumerated is not defined on nil").
1484
+ #++
1485
+ def modify args
1486
+ modify_dn = args[:dn] or raise "Unable to modify empty DN"
1487
+ modify_ops = []
1488
+ a = args[:operations] and a.each {|op, attr, values|
1489
+ # TODO, fix the following line, which gives a bogus error
1490
+ # if the opcode is invalid.
1491
+ op_1 = {:add => 0, :delete => 1, :replace => 2} [op.to_sym].to_ber_enumerated
1492
+ modify_ops << [op_1, [attr.to_s.to_ber, values.to_a.map {|v| v.to_ber}.to_ber_set].to_ber_sequence].to_ber_sequence
1493
+ }
1571
1494
 
1495
+ request = [modify_dn.to_ber, modify_ops.to_ber_sequence].to_ber_appsequence(6)
1496
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
1497
+ @conn.write pkt
1572
1498
 
1573
- #--
1574
- # rename
1575
- # TODO, need to support a time limit, in case the server fails to respond.
1576
- #
1577
- def rename args
1578
- old_dn = args[:olddn] or raise "Unable to rename empty DN"
1579
- new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN"
1580
- delete_attrs = args[:delete_attributes] ? true : false
1499
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 7) or raise LdapError.new( "response missing or invalid" )
1500
+ pdu.result
1501
+ end
1581
1502
 
1582
- request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber].to_ber_appsequence(12)
1583
- pkt = [next_msgid.to_ber, request].to_ber_sequence
1584
- @conn.write pkt
1503
+ #--
1504
+ # add
1505
+ # TODO, need to support a time limit, in case the server fails to respond.
1506
+ # Unlike other operation-methods in this class, we return a result hash rather
1507
+ # than a simple result number. This is experimental, and eventually we'll want
1508
+ # to do this with all the others. The point is to have access to the error message
1509
+ # and the matched-DN returned by the server.
1510
+ #++
1511
+ def add args
1512
+ add_dn = args[:dn] or raise LdapError.new("Unable to add empty DN")
1513
+ add_attrs = []
1514
+ a = args[:attributes] and a.each {|k,v|
1515
+ add_attrs << [ k.to_s.to_ber, v.to_a.map {|m| m.to_ber}.to_ber_set ].to_ber_sequence
1516
+ }
1585
1517
 
1586
- (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 13) or raise LdapError.new( "response missing or invalid" )
1587
- pdu.result_code
1588
- end
1518
+ request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8)
1519
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
1520
+ @conn.write pkt
1589
1521
 
1522
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 9) or raise LdapError.new( "response missing or invalid" )
1523
+ pdu.result
1524
+ end
1590
1525
 
1591
- #--
1592
- # delete
1593
- # TODO, need to support a time limit, in case the server fails to respond.
1594
- #
1595
- def delete args
1596
- dn = args[:dn] or raise "Unable to delete empty DN"
1526
+ #--
1527
+ # rename
1528
+ # TODO, need to support a time limit, in case the server fails to respond.
1529
+ #++
1530
+ def rename args
1531
+ old_dn = args[:olddn] or raise "Unable to rename empty DN"
1532
+ new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN"
1533
+ delete_attrs = args[:delete_attributes] ? true : false
1534
+
1535
+ request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber].to_ber_appsequence(12)
1536
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
1537
+ @conn.write pkt
1597
1538
 
1598
- request = dn.to_s.to_ber_application_string(10)
1599
- pkt = [next_msgid.to_ber, request].to_ber_sequence
1600
- @conn.write pkt
1539
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 13) or raise LdapError.new( "response missing or invalid" )
1540
+ pdu.result_code
1541
+ end
1601
1542
 
1602
- (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 11) or raise LdapError.new( "response missing or invalid" )
1603
- pdu.result_code
1604
- end
1543
+ #--
1544
+ # delete
1545
+ # TODO, need to support a time limit, in case the server fails to respond.
1546
+ #++
1547
+ def delete args
1548
+ dn = args[:dn] or raise "Unable to delete empty DN"
1605
1549
 
1550
+ request = dn.to_s.to_ber_application_string(10)
1551
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
1552
+ @conn.write pkt
1606
1553
 
1607
- end # class Connection
1554
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 11) or raise LdapError.new( "response missing or invalid" )
1555
+ pdu.result_code
1556
+ end
1557
+ end # class Connection
1608
1558
  end # class LDAP
1609
-
1610
-
1611
1559
  end # module Net
1612
-
1613
-