prathe-net-ldap 0.2.20110317223538

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