File: pdu.rb

package info (click to toggle)
ruby-net-ldap 0.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 640 kB
  • sloc: ruby: 4,583; sh: 53; makefile: 4
file content (321 lines) | stat: -rw-r--r-- 10,335 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# -*- ruby encoding: utf-8 -*-
require 'ostruct'

##
# Defines the Protocol Data Unit (PDU) for LDAP. An LDAP PDU always looks
# like a BER SEQUENCE with at least two elements: an INTEGER message ID
# number and an application-specific SEQUENCE. Some LDAPv3 packets also
# include an optional third element, a sequence of "controls" (see RFC 2251
# section 4.1.12 for more information).
#
# The application-specific tag in the sequence tells us what kind of packet
# it is, and each kind has its own format, defined in RFC-1777.
#
# Observe that many clients (such as ldapsearch) do not necessarily enforce
# the expected application tags on received protocol packets. This
# implementation does interpret the RFC strictly in this regard, and it
# remains to be seen whether there are servers out there that will not work
# well with our approach.
#
# Currently, we only support controls on SearchResult.
#
# http://tools.ietf.org/html/rfc4511#section-4.1.1
# http://tools.ietf.org/html/rfc4511#section-4.1.9
class Net::LDAP::PDU
  class Error < RuntimeError; end

  # http://tools.ietf.org/html/rfc4511#section-4.2
  BindRequest = 0
  # http://tools.ietf.org/html/rfc4511#section-4.2.2
  BindResult = 1
  # http://tools.ietf.org/html/rfc4511#section-4.3
  UnbindRequest = 2
  # http://tools.ietf.org/html/rfc4511#section-4.5.1
  SearchRequest = 3
  # http://tools.ietf.org/html/rfc4511#section-4.5.2
  SearchReturnedData = 4
  SearchResult = 5
  # see also SearchResultReferral (19)
  # http://tools.ietf.org/html/rfc4511#section-4.6
  ModifyRequest  = 6
  ModifyResponse = 7
  # http://tools.ietf.org/html/rfc4511#section-4.7
  AddRequest = 8
  AddResponse = 9
  # http://tools.ietf.org/html/rfc4511#section-4.8
  DeleteRequest = 10
  DeleteResponse = 11
  # http://tools.ietf.org/html/rfc4511#section-4.9
  ModifyRDNRequest  = 12
  ModifyRDNResponse = 13
  # http://tools.ietf.org/html/rfc4511#section-4.10
  CompareRequest = 14
  CompareResponse = 15
  # http://tools.ietf.org/html/rfc4511#section-4.11
  AbandonRequest = 16
  # http://tools.ietf.org/html/rfc4511#section-4.5.2
  SearchResultReferral = 19
  # http://tools.ietf.org/html/rfc4511#section-4.12
  ExtendedRequest = 23
  ExtendedResponse = 24
  # unused: http://tools.ietf.org/html/rfc4511#section-4.13
  IntermediateResponse = 25

  ##
  # The LDAP packet message ID.
  attr_reader :message_id
  alias_method :msg_id, :message_id

  ##
  # The application protocol format tag.
  attr_reader :app_tag

  attr_reader :search_entry
  attr_reader :search_referrals
  attr_reader :search_parameters
  attr_reader :bind_parameters
  attr_reader :extended_response

  ##
  # Returns RFC-2251 Controls if any.
  attr_reader :ldap_controls
  alias_method :result_controls, :ldap_controls
  # Messy. Does this functionality belong somewhere else?

  def initialize(ber_object)
    begin
      @message_id = ber_object[0].to_i
      # Grab the bottom five bits of the identifier so we know which type of
      # PDU this is.
      #
      # This is safe enough in LDAP-land, but it is recommended that other
      # approaches be taken for other protocols in the case that there's an
      # app-specific tag that has both primitive and constructed forms.
      @app_tag = ber_object[1].ber_identifier & 0x1f
      @ldap_controls = []
    rescue Exception => ex
      raise Net::LDAP::PDU::Error, "LDAP PDU Format Error: #{ex.message}"
    end

    case @app_tag
    when BindResult
      parse_bind_response(ber_object[1])
    when SearchReturnedData
      parse_search_return(ber_object[1])
    when SearchResultReferral
      parse_search_referral(ber_object[1])
    when SearchResult
      parse_ldap_result(ber_object[1])
    when ModifyResponse
      parse_ldap_result(ber_object[1])
    when AddResponse
      parse_ldap_result(ber_object[1])
    when DeleteResponse
      parse_ldap_result(ber_object[1])
    when ModifyRDNResponse
      parse_ldap_result(ber_object[1])
    when SearchRequest
      parse_ldap_search_request(ber_object[1])
    when BindRequest
      parse_bind_request(ber_object[1])
    when UnbindRequest
      parse_unbind_request(ber_object[1])
    when ExtendedResponse
      parse_extended_response(ber_object[1])
    else
      raise Error.new("unknown pdu-type: #{@app_tag}")
    end

    parse_controls(ber_object[2]) if ber_object[2]
  end

  ##
  # Returns a hash which (usually) defines the members :resultCode,
  # :errorMessage, and :matchedDN. These values come directly from an LDAP
  # response packet returned by the remote peer. Also see #result_code.
  def result
    @ldap_result || {}
  end

  def error_message
    result[:errorMessage] || ""
  end

  ##
  # This returns an LDAP result code taken from the PDU, but it will be nil
  # if there wasn't a result code. That can easily happen depending on the
  # type of packet.
  def result_code(code = :resultCode)
    @ldap_result and @ldap_result[code]
  end

  def status
    Net::LDAP::ResultCodesNonError.include?(result_code) ? :success : :failure
  end

  def success?
    status == :success
  end

  def failure?
    !success?
  end

  ##
  # Return serverSaslCreds, which are only present in BindResponse packets.
  #--
  # Messy. Does this functionality belong somewhere else? We ought to
  # refactor the accessors of this class before they get any kludgier.
  def result_server_sasl_creds
    @ldap_result && @ldap_result[:serverSaslCreds]
  end

  def parse_ldap_result(sequence)
    sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
    @ldap_result = {
      :resultCode => sequence[0],
      :matchedDN => sequence[1],
      :errorMessage => sequence[2],
    }
    parse_search_referral(sequence[3]) if @ldap_result[:resultCode] == Net::LDAP::ResultCodeReferral
  end
  private :parse_ldap_result

  ##
  # Parse an extended response
  #
  # http://www.ietf.org/rfc/rfc2251.txt
  #
  # Each Extended operation consists of an Extended request and an
  # Extended response.
  #
  #      ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
  #           requestName      [0] LDAPOID,
  #           requestValue     [1] OCTET STRING OPTIONAL }

  def parse_extended_response(sequence)
    sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
    @ldap_result = {
      :resultCode => sequence[0],
      :matchedDN => sequence[1],
      :errorMessage => sequence[2],
    }
    @extended_response = sequence[3]
  end
  private :parse_extended_response

  ##
  # A Bind Response may have an additional field, ID [7], serverSaslCreds,
  # per RFC 2251 pgh 4.2.3.
  def parse_bind_response(sequence)
    sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP Bind Response length."
    parse_ldap_result(sequence)
    @ldap_result[:serverSaslCreds] = sequence[3] if sequence.length >= 4
    @ldap_result
  end
  private :parse_bind_response

  # Definition from RFC 1777 (we're handling application-4 here).
  #
  # Search Response ::=
  #   CHOICE {
  #     entry      [APPLICATION 4] SEQUENCE {
  #                  objectName     LDAPDN,
  #                  attributes     SEQUENCE OF SEQUENCE {
  #                    AttributeType,
  #                    SET OF AttributeValue
  #                  }
  #                },
  #     resultCode [APPLICATION 5] LDAPResult
  #   }
  #
  # We concoct a search response that is a hash of the returned attribute
  # values.
  #
  # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
  #
  # This is to make them more predictable for user programs, but it may not
  # be a good idea. Maybe this should be configurable.
  def parse_search_return(sequence)
    sequence.length >= 2 or raise Net::LDAP::PDU::Error, "Invalid Search Response length."
    @search_entry = Net::LDAP::Entry.new(sequence[0])
    sequence[1].each { |seq| @search_entry[seq[0]] = seq[1] }
  end
  private :parse_search_return

  ##
  # A search referral is a sequence of one or more LDAP URIs. Any number of
  # search-referral replies can be returned by the server, interspersed with
  # normal replies in any order.
  #--
  # Until I can think of a better way to do this, we'll return the referrals
  # as an array. It'll be up to higher-level handlers to expose something
  # reasonable to the client.
  def parse_search_referral(uris)
    @search_referrals = uris
  end
  private :parse_search_referral

  ##
  # Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
  # of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
  # Octet String. If only two fields are given, the second one may be either
  # criticality or data, since criticality has a default value. Someday we
  # may want to come back here and add support for some of more-widely used
  # controls. RFC-2696 is a good example.
  def parse_controls(sequence)
    @ldap_controls = sequence.map do |control|
      o = OpenStruct.new
      o.oid, o.criticality, o.value = control[0], control[1], control[2]
      if o.criticality and o.criticality.is_a?(String)
        o.value = o.criticality
        o.criticality = false
      end
      o
    end
  end
  private :parse_controls

  # (provisional, must document)
  def parse_ldap_search_request(sequence)
    s = OpenStruct.new
    s.base_object, s.scope, s.deref_aliases, s.size_limit, s.time_limit,
      s.types_only, s.filter, s.attributes = sequence
    @search_parameters = s
  end
  private :parse_ldap_search_request

  # (provisional, must document)
  def parse_bind_request sequence
    s = OpenStruct.new
    s.version, s.name, s.authentication = sequence
    @bind_parameters = s
  end
  private :parse_bind_request

  # (provisional, must document)
  # UnbindRequest has no content so this is a no-op.
  def parse_unbind_request(sequence)
    nil
  end
  private :parse_unbind_request
end

module Net
  ##
  # Handle renamed constants Net::LdapPdu (Net::LDAP::PDU) and
  # Net::LdapPduError (Net::LDAP::PDU::Error).
  def self.const_missing(name) #:nodoc:
    case name.to_s
    when "LdapPdu"
      warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU instead."
      Net::LDAP::PDU
    when "LdapPduError"
      warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU::Error instead."
      Net::LDAP::PDU::Error
    when 'LDAP'
    else
      super
    end
  end
end # module Net