datacom-net-ldap 0.5.0.datacom

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +11 -0
  3. data/.rspec +2 -0
  4. data/Contributors.rdoc +22 -0
  5. data/Hacking.rdoc +68 -0
  6. data/History.rdoc +198 -0
  7. data/License.rdoc +29 -0
  8. data/Manifest.txt +50 -0
  9. data/README.rdoc +49 -0
  10. data/Rakefile +74 -0
  11. data/autotest/discover.rb +1 -0
  12. data/lib/net-ldap.rb +2 -0
  13. data/lib/net/ber.rb +320 -0
  14. data/lib/net/ber/ber_parser.rb +168 -0
  15. data/lib/net/ber/core_ext.rb +62 -0
  16. data/lib/net/ber/core_ext/array.rb +96 -0
  17. data/lib/net/ber/core_ext/bignum.rb +22 -0
  18. data/lib/net/ber/core_ext/false_class.rb +10 -0
  19. data/lib/net/ber/core_ext/fixnum.rb +66 -0
  20. data/lib/net/ber/core_ext/string.rb +78 -0
  21. data/lib/net/ber/core_ext/true_class.rb +12 -0
  22. data/lib/net/ldap.rb +1646 -0
  23. data/lib/net/ldap/dataset.rb +154 -0
  24. data/lib/net/ldap/dn.rb +225 -0
  25. data/lib/net/ldap/entry.rb +185 -0
  26. data/lib/net/ldap/filter.rb +781 -0
  27. data/lib/net/ldap/password.rb +37 -0
  28. data/lib/net/ldap/pdu.rb +273 -0
  29. data/lib/net/ldap/version.rb +5 -0
  30. data/lib/net/snmp.rb +270 -0
  31. data/net-ldap.gemspec +61 -0
  32. data/spec/integration/ssl_ber_spec.rb +36 -0
  33. data/spec/spec.opts +2 -0
  34. data/spec/spec_helper.rb +5 -0
  35. data/spec/unit/ber/ber_spec.rb +141 -0
  36. data/spec/unit/ber/core_ext/string_spec.rb +51 -0
  37. data/spec/unit/ldap/dn_spec.rb +80 -0
  38. data/spec/unit/ldap/entry_spec.rb +51 -0
  39. data/spec/unit/ldap/filter_spec.rb +115 -0
  40. data/spec/unit/ldap_spec.rb +78 -0
  41. data/test/common.rb +3 -0
  42. data/test/test_entry.rb +59 -0
  43. data/test/test_filter.rb +122 -0
  44. data/test/test_ldap_connection.rb +24 -0
  45. data/test/test_ldif.rb +79 -0
  46. data/test/test_password.rb +17 -0
  47. data/test/test_rename.rb +77 -0
  48. data/test/test_snmp.rb +114 -0
  49. data/test/testdata.ldif +101 -0
  50. data/testserver/ldapserver.rb +210 -0
  51. data/testserver/testdata.ldif +101 -0
  52. metadata +213 -0
@@ -0,0 +1,37 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+ require 'digest/sha1'
3
+ require 'digest/md5'
4
+ require 'base64'
5
+
6
+ class Net::LDAP::Password
7
+ class << self
8
+ # Generate a password-hash suitable for inclusion in an LDAP attribute.
9
+ # Pass a hash type as a symbol (:md5, :sha, :ssha) and a plaintext
10
+ # password. This function will return a hashed representation.
11
+ #
12
+ #--
13
+ # STUB: This is here to fulfill the requirements of an RFC, which
14
+ # one?
15
+ #
16
+ # TODO:
17
+ # * maybe salted-md5
18
+ # * Should we provide sha1 as a synonym for sha1? I vote no because then
19
+ # should you also provide ssha1 for symmetry?
20
+ #
21
+ attribute_value = ""
22
+ def generate(type, str)
23
+ case type
24
+ when :md5
25
+ attribute_value = '{MD5}' + Base64.encode64(Digest::MD5.digest(str)).chomp!
26
+ when :sha
27
+ attribute_value = '{SHA}' + Base64.encode64(Digest::SHA1.digest(str)).chomp!
28
+ when :ssha
29
+ srand; salt = (rand * 1000).to_i.to_s
30
+ attribute_value = '{SSHA}' + Base64.encode64(Digest::SHA1.digest(str + salt) + salt).chomp!
31
+ else
32
+ raise Net::LDAP::LdapError, "Unsupported password-hash type (#{type})"
33
+ end
34
+ return attribute_value
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,273 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+ require 'ostruct'
3
+
4
+ ##
5
+ # Defines the Protocol Data Unit (PDU) for LDAP. An LDAP PDU always looks
6
+ # like a BER SEQUENCE with at least two elements: an INTEGER message ID
7
+ # number and an application-specific SEQUENCE. Some LDAPv3 packets also
8
+ # include an optional third element, a sequence of "controls" (see RFC 2251
9
+ # section 4.1.12 for more information).
10
+ #
11
+ # The application-specific tag in the sequence tells us what kind of packet
12
+ # it is, and each kind has its own format, defined in RFC-1777.
13
+ #
14
+ # Observe that many clients (such as ldapsearch) do not necessarily enforce
15
+ # the expected application tags on received protocol packets. This
16
+ # implementation does interpret the RFC strictly in this regard, and it
17
+ # remains to be seen whether there are servers out there that will not work
18
+ # well with our approach.
19
+ #
20
+ # Currently, we only support controls on SearchResult.
21
+ class Net::LDAP::PDU
22
+ class Error < RuntimeError; end
23
+
24
+ ##
25
+ # This message packet is a bind request.
26
+ BindRequest = 0
27
+ BindResult = 1
28
+ UnbindRequest = 2
29
+ SearchRequest = 3
30
+ SearchReturnedData = 4
31
+ SearchResult = 5
32
+ ModifyResponse = 7
33
+ AddResponse = 9
34
+ DeleteResponse = 11
35
+ ModifyRDNResponse = 13
36
+ SearchResultReferral = 19
37
+ ExtendedRequest = 23
38
+ ExtendedResponse = 24
39
+
40
+ ##
41
+ # The LDAP packet message ID.
42
+ attr_reader :message_id
43
+ alias_method :msg_id, :message_id
44
+
45
+ ##
46
+ # The application protocol format tag.
47
+ attr_reader :app_tag
48
+
49
+ attr_reader :search_entry
50
+ attr_reader :search_referrals
51
+ attr_reader :search_parameters
52
+ attr_reader :bind_parameters
53
+
54
+ ##
55
+ # Returns RFC-2251 Controls if any.
56
+ attr_reader :ldap_controls
57
+ alias_method :result_controls, :ldap_controls
58
+ # Messy. Does this functionality belong somewhere else?
59
+
60
+ def initialize(ber_object)
61
+ begin
62
+ @message_id = ber_object[0].to_i
63
+ # Grab the bottom five bits of the identifier so we know which type of
64
+ # PDU this is.
65
+ #
66
+ # This is safe enough in LDAP-land, but it is recommended that other
67
+ # approaches be taken for other protocols in the case that there's an
68
+ # app-specific tag that has both primitive and constructed forms.
69
+ @app_tag = ber_object[1].ber_identifier & 0x1f
70
+ @ldap_controls = []
71
+ rescue Exception => ex
72
+ raise Net::LDAP::PDU::Error, "LDAP PDU Format Error: #{ex.message}"
73
+ end
74
+
75
+ case @app_tag
76
+ when BindResult
77
+ parse_bind_response(ber_object[1])
78
+ when SearchReturnedData
79
+ parse_search_return(ber_object[1])
80
+ when SearchResultReferral
81
+ parse_search_referral(ber_object[1])
82
+ when SearchResult
83
+ parse_ldap_result(ber_object[1])
84
+ when ModifyResponse
85
+ parse_ldap_result(ber_object[1])
86
+ when AddResponse
87
+ parse_ldap_result(ber_object[1])
88
+ when DeleteResponse
89
+ parse_ldap_result(ber_object[1])
90
+ when ModifyRDNResponse
91
+ parse_ldap_result(ber_object[1])
92
+ when SearchRequest
93
+ parse_ldap_search_request(ber_object[1])
94
+ when BindRequest
95
+ parse_bind_request(ber_object[1])
96
+ when UnbindRequest
97
+ parse_unbind_request(ber_object[1])
98
+ when ExtendedResponse
99
+ parse_ldap_result(ber_object[1])
100
+ else
101
+ raise LdapPduError.new("unknown pdu-type: #{@app_tag}")
102
+ end
103
+
104
+ parse_controls(ber_object[2]) if ber_object[2]
105
+ end
106
+
107
+ ##
108
+ # Returns a hash which (usually) defines the members :resultCode,
109
+ # :errorMessage, and :matchedDN. These values come directly from an LDAP
110
+ # response packet returned by the remote peer. Also see #result_code.
111
+ def result
112
+ @ldap_result || {}
113
+ end
114
+
115
+ def error_message
116
+ result[:errorMessage] || ""
117
+ end
118
+
119
+ ##
120
+ # This returns an LDAP result code taken from the PDU, but it will be nil
121
+ # if there wasn't a result code. That can easily happen depending on the
122
+ # type of packet.
123
+ def result_code(code = :resultCode)
124
+ @ldap_result and @ldap_result[code]
125
+ end
126
+
127
+ def status
128
+ result_code == 0 ? :success : :failure
129
+ end
130
+
131
+ def success?
132
+ status == :success
133
+ end
134
+
135
+ def failure?
136
+ !success?
137
+ end
138
+
139
+ ##
140
+ # Return serverSaslCreds, which are only present in BindResponse packets.
141
+ #--
142
+ # Messy. Does this functionality belong somewhere else? We ought to
143
+ # refactor the accessors of this class before they get any kludgier.
144
+ def result_server_sasl_creds
145
+ @ldap_result && @ldap_result[:serverSaslCreds]
146
+ end
147
+
148
+ def parse_ldap_result(sequence)
149
+ sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
150
+ @ldap_result = {
151
+ :resultCode => sequence[0],
152
+ :matchedDN => sequence[1],
153
+ :errorMessage => sequence[2]
154
+ }
155
+ parse_search_referral(sequence[3]) if @ldap_result[:resultCode] == 10
156
+ end
157
+ private :parse_ldap_result
158
+
159
+ ##
160
+ # A Bind Response may have an additional field, ID [7], serverSaslCreds,
161
+ # per RFC 2251 pgh 4.2.3.
162
+ def parse_bind_response(sequence)
163
+ sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP Bind Response length."
164
+ parse_ldap_result(sequence)
165
+ @ldap_result[:serverSaslCreds] = sequence[3] if sequence.length >= 4
166
+ @ldap_result
167
+ end
168
+ private :parse_bind_response
169
+
170
+ # Definition from RFC 1777 (we're handling application-4 here).
171
+ #
172
+ # Search Response ::=
173
+ # CHOICE {
174
+ # entry [APPLICATION 4] SEQUENCE {
175
+ # objectName LDAPDN,
176
+ # attributes SEQUENCE OF SEQUENCE {
177
+ # AttributeType,
178
+ # SET OF AttributeValue
179
+ # }
180
+ # },
181
+ # resultCode [APPLICATION 5] LDAPResult
182
+ # }
183
+ #
184
+ # We concoct a search response that is a hash of the returned attribute
185
+ # values.
186
+ #
187
+ # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
188
+ #
189
+ # This is to make them more predictable for user programs, but it may not
190
+ # be a good idea. Maybe this should be configurable.
191
+ def parse_search_return(sequence)
192
+ sequence.length >= 2 or raise Net::LDAP::PDU::Error, "Invalid Search Response length."
193
+ @search_entry = Net::LDAP::Entry.new(sequence[0])
194
+ sequence[1].each { |seq| @search_entry[seq[0]] = seq[1] }
195
+ end
196
+ private :parse_search_return
197
+
198
+ ##
199
+ # A search referral is a sequence of one or more LDAP URIs. Any number of
200
+ # search-referral replies can be returned by the server, interspersed with
201
+ # normal replies in any order.
202
+ #--
203
+ # Until I can think of a better way to do this, we'll return the referrals
204
+ # as an array. It'll be up to higher-level handlers to expose something
205
+ # reasonable to the client.
206
+ def parse_search_referral(uris)
207
+ @search_referrals = uris
208
+ end
209
+ private :parse_search_referral
210
+
211
+ ##
212
+ # Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
213
+ # of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
214
+ # Octet String. If only two fields are given, the second one may be either
215
+ # criticality or data, since criticality has a default value. Someday we
216
+ # may want to come back here and add support for some of more-widely used
217
+ # controls. RFC-2696 is a good example.
218
+ def parse_controls(sequence)
219
+ @ldap_controls = sequence.map do |control|
220
+ o = OpenStruct.new
221
+ o.oid, o.criticality, o.value = control[0], control[1], control[2]
222
+ if o.criticality and o.criticality.is_a?(String)
223
+ o.value = o.criticality
224
+ o.criticality = false
225
+ end
226
+ o
227
+ end
228
+ end
229
+ private :parse_controls
230
+
231
+ # (provisional, must document)
232
+ def parse_ldap_search_request(sequence)
233
+ s = OpenStruct.new
234
+ s.base_object, s.scope, s.deref_aliases, s.size_limit, s.time_limit,
235
+ s.types_only, s.filter, s.attributes = sequence
236
+ @search_parameters = s
237
+ end
238
+ private :parse_ldap_search_request
239
+
240
+ # (provisional, must document)
241
+ def parse_bind_request sequence
242
+ s = OpenStruct.new
243
+ s.version, s.name, s.authentication = sequence
244
+ @bind_parameters = s
245
+ end
246
+ private :parse_bind_request
247
+
248
+ # (provisional, must document)
249
+ # UnbindRequest has no content so this is a no-op.
250
+ def parse_unbind_request(sequence)
251
+ nil
252
+ end
253
+ private :parse_unbind_request
254
+ end
255
+
256
+ module Net
257
+ ##
258
+ # Handle renamed constants Net::LdapPdu (Net::LDAP::PDU) and
259
+ # Net::LdapPduError (Net::LDAP::PDU::Error).
260
+ def self.const_missing(name) #:nodoc:
261
+ case name.to_s
262
+ when "LdapPdu"
263
+ warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU instead."
264
+ Net::LDAP::PDU
265
+ when "LdapPduError"
266
+ warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU::Error instead."
267
+ Net::LDAP::PDU::Error
268
+ when 'LDAP'
269
+ else
270
+ super
271
+ end
272
+ end
273
+ end # module Net
@@ -0,0 +1,5 @@
1
+ module Net
2
+ class LDAP
3
+ VERSION = "0.5.0.datacom"
4
+ end
5
+ end
@@ -0,0 +1,270 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+ require 'net/ldap/version'
3
+
4
+ # :stopdoc:
5
+ module Net
6
+ class SNMP
7
+ VERSION = Net::LDAP::VERSION
8
+
9
+ AsnSyntax = Net::BER.compile_syntax({
10
+ :application => {
11
+ :primitive => {
12
+ 1 => :integer, # Counter32, (RFC2578 sec 2)
13
+ 2 => :integer, # Gauge32 or Unsigned32, (RFC2578 sec 2)
14
+ 3 => :integer # TimeTicks32, (RFC2578 sec 2)
15
+ },
16
+ :constructed => {
17
+ }
18
+ },
19
+ :context_specific => {
20
+ :primitive => {
21
+ },
22
+ :constructed => {
23
+ 0 => :array, # GetRequest PDU (RFC1157 pgh 4.1.2)
24
+ 1 => :array, # GetNextRequest PDU (RFC1157 pgh 4.1.3)
25
+ 2 => :array # GetResponse PDU (RFC1157 pgh 4.1.4)
26
+ }
27
+ }
28
+ })
29
+
30
+ # SNMP 32-bit counter.
31
+ # Defined in RFC1155 (Structure of Mangement Information), section 6.
32
+ # A 32-bit counter is an ASN.1 application [1] implicit unsigned integer
33
+ # with a range from 0 to 2^^32 - 1.
34
+ class Counter32
35
+ def initialize value
36
+ @value = value
37
+ end
38
+ def to_ber
39
+ @value.to_ber_application(1)
40
+ end
41
+ end
42
+
43
+ # SNMP 32-bit gauge.
44
+ # Defined in RFC1155 (Structure of Mangement Information), section 6.
45
+ # A 32-bit counter is an ASN.1 application [2] implicit unsigned integer.
46
+ # This is also indistinguishable from Unsigned32. (Need to alias them.)
47
+ class Gauge32
48
+ def initialize value
49
+ @value = value
50
+ end
51
+ def to_ber
52
+ @value.to_ber_application(2)
53
+ end
54
+ end
55
+
56
+ # SNMP 32-bit timer-ticks.
57
+ # Defined in RFC1155 (Structure of Mangement Information), section 6.
58
+ # A 32-bit counter is an ASN.1 application [3] implicit unsigned integer.
59
+ class TimeTicks32
60
+ def initialize value
61
+ @value = value
62
+ end
63
+ def to_ber
64
+ @value.to_ber_application(3)
65
+ end
66
+ end
67
+ end
68
+
69
+ class SnmpPdu
70
+ class Error < StandardError; end
71
+
72
+ PduTypes = [
73
+ :get_request,
74
+ :get_next_request,
75
+ :get_response,
76
+ :set_request,
77
+ :trap
78
+ ]
79
+ ErrorStatusCodes = { # Per RFC1157, pgh 4.1.1
80
+ 0 => "noError",
81
+ 1 => "tooBig",
82
+ 2 => "noSuchName",
83
+ 3 => "badValue",
84
+ 4 => "readOnly",
85
+ 5 => "genErr"
86
+ }
87
+
88
+ class << self
89
+ def parse ber_object
90
+ n = new
91
+ n.send :parse, ber_object
92
+ n
93
+ end
94
+ end
95
+
96
+ attr_reader :version, :community, :pdu_type, :variables, :error_status
97
+ attr_accessor :request_id, :error_index
98
+
99
+
100
+ def initialize args={}
101
+ @version = args[:version] || 0
102
+ @community = args[:community] || "public"
103
+ @pdu_type = args[:pdu_type] # leave nil unless specified; there's no reasonable default value.
104
+ @error_status = args[:error_status] || 0
105
+ @error_index = args[:error_index] || 0
106
+ @variables = args[:variables] || []
107
+ end
108
+
109
+ #--
110
+ def parse ber_object
111
+ begin
112
+ parse_ber_object ber_object
113
+ rescue Error
114
+ # Pass through any SnmpPdu::Error instances
115
+ raise $!
116
+ rescue
117
+ # Wrap any basic parsing error so it becomes a PDU-format error
118
+ raise Error.new( "snmp-pdu format error" )
119
+ end
120
+ end
121
+ private :parse
122
+
123
+ def parse_ber_object ber_object
124
+ send :version=, ber_object[0].to_i
125
+ send :community=, ber_object[1].to_s
126
+
127
+ data = ber_object[2]
128
+ case (app_tag = data.ber_identifier & 31)
129
+ when 0
130
+ send :pdu_type=, :get_request
131
+ parse_get_request data
132
+ when 1
133
+ send :pdu_type=, :get_next_request
134
+ # This PDU is identical to get-request except for the type.
135
+ parse_get_request data
136
+ when 2
137
+ send :pdu_type=, :get_response
138
+ # This PDU is identical to get-request except for the type,
139
+ # the error_status and error_index values are meaningful,
140
+ # and the fact that the variable bindings will be non-null.
141
+ parse_get_response data
142
+ else
143
+ raise Error.new( "unknown snmp-pdu type: #{app_tag}" )
144
+ end
145
+ end
146
+ private :parse_ber_object
147
+
148
+ #--
149
+ # Defined in RFC1157, pgh 4.1.2.
150
+ def parse_get_request data
151
+ send :request_id=, data[0].to_i
152
+ # data[1] is error_status, always zero.
153
+ # data[2] is error_index, always zero.
154
+ send :error_status=, 0
155
+ send :error_index=, 0
156
+ data[3].each {|n,v|
157
+ # A variable-binding, of which there may be several,
158
+ # consists of an OID and a BER null.
159
+ # We're ignoring the null, we might want to verify it instead.
160
+ unless v.is_a?(Net::BER::BerIdentifiedNull)
161
+ raise Error.new(" invalid variable-binding in get-request" )
162
+ end
163
+ add_variable_binding n, nil
164
+ }
165
+ end
166
+ private :parse_get_request
167
+
168
+ #--
169
+ # Defined in RFC1157, pgh 4.1.4
170
+ def parse_get_response data
171
+ send :request_id=, data[0].to_i
172
+ send :error_status=, data[1].to_i
173
+ send :error_index=, data[2].to_i
174
+ data[3].each {|n,v|
175
+ # A variable-binding, of which there may be several,
176
+ # consists of an OID and a BER null.
177
+ # We're ignoring the null, we might want to verify it instead.
178
+ add_variable_binding n, v
179
+ }
180
+ end
181
+ private :parse_get_response
182
+
183
+
184
+ def version= ver
185
+ unless [0,2].include?(ver)
186
+ raise Error.new("unknown snmp-version: #{ver}")
187
+ end
188
+ @version = ver
189
+ end
190
+
191
+ def pdu_type= t
192
+ unless PduTypes.include?(t)
193
+ raise Error.new("unknown pdu-type: #{t}")
194
+ end
195
+ @pdu_type = t
196
+ end
197
+
198
+ def error_status= es
199
+ unless ErrorStatusCodes.has_key?(es)
200
+ raise Error.new("unknown error-status: #{es}")
201
+ end
202
+ @error_status = es
203
+ end
204
+
205
+ def community= c
206
+ @community = c.to_s
207
+ end
208
+
209
+ #--
210
+ # Syntactic sugar
211
+ def add_variable_binding name, value=nil
212
+ @variables ||= []
213
+ @variables << [name, value]
214
+ end
215
+
216
+ def to_ber_string
217
+ [
218
+ version.to_ber,
219
+ community.to_ber,
220
+ pdu_to_ber_string
221
+ ].to_ber_sequence
222
+ end
223
+
224
+ #--
225
+ # Helper method that returns a PDU payload in BER form,
226
+ # depending on the PDU type.
227
+ def pdu_to_ber_string
228
+ case pdu_type
229
+ when :get_request
230
+ [
231
+ request_id.to_ber,
232
+ error_status.to_ber,
233
+ error_index.to_ber,
234
+ [
235
+ @variables.map {|n,v|
236
+ [n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence
237
+ }
238
+ ].to_ber_sequence
239
+ ].to_ber_contextspecific(0)
240
+ when :get_next_request
241
+ [
242
+ request_id.to_ber,
243
+ error_status.to_ber,
244
+ error_index.to_ber,
245
+ [
246
+ @variables.map {|n,v|
247
+ [n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence
248
+ }
249
+ ].to_ber_sequence
250
+ ].to_ber_contextspecific(1)
251
+ when :get_response
252
+ [
253
+ request_id.to_ber,
254
+ error_status.to_ber,
255
+ error_index.to_ber,
256
+ [
257
+ @variables.map {|n,v|
258
+ [n.to_ber_oid, v.to_ber].to_ber_sequence
259
+ }
260
+ ].to_ber_sequence
261
+ ].to_ber_contextspecific(2)
262
+ else
263
+ raise Error.new( "unknown pdu-type: #{pdu_type}" )
264
+ end
265
+ end
266
+ private :pdu_to_ber_string
267
+
268
+ end
269
+ end
270
+ # :startdoc: