Chris@909
|
1 # $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $
|
Chris@909
|
2 #
|
Chris@909
|
3 # LDAP PDU support classes
|
Chris@909
|
4 #
|
Chris@909
|
5 #
|
Chris@909
|
6 #----------------------------------------------------------------------------
|
Chris@909
|
7 #
|
Chris@909
|
8 # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
Chris@909
|
9 #
|
Chris@909
|
10 # Gmail: garbagecat10
|
Chris@909
|
11 #
|
Chris@909
|
12 # This program is free software; you can redistribute it and/or modify
|
Chris@909
|
13 # it under the terms of the GNU General Public License as published by
|
Chris@909
|
14 # the Free Software Foundation; either version 2 of the License, or
|
Chris@909
|
15 # (at your option) any later version.
|
Chris@909
|
16 #
|
Chris@909
|
17 # This program is distributed in the hope that it will be useful,
|
Chris@909
|
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Chris@909
|
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Chris@909
|
20 # GNU General Public License for more details.
|
Chris@909
|
21 #
|
Chris@909
|
22 # You should have received a copy of the GNU General Public License
|
Chris@909
|
23 # along with this program; if not, write to the Free Software
|
Chris@909
|
24 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Chris@909
|
25 #
|
Chris@909
|
26 #---------------------------------------------------------------------------
|
Chris@909
|
27 #
|
Chris@909
|
28
|
Chris@909
|
29
|
Chris@909
|
30
|
Chris@909
|
31 module Net
|
Chris@909
|
32
|
Chris@909
|
33
|
Chris@909
|
34 class LdapPduError < Exception; end
|
Chris@909
|
35
|
Chris@909
|
36
|
Chris@909
|
37 class LdapPdu
|
Chris@909
|
38
|
Chris@909
|
39 BindResult = 1
|
Chris@909
|
40 SearchReturnedData = 4
|
Chris@909
|
41 SearchResult = 5
|
Chris@909
|
42 ModifyResponse = 7
|
Chris@909
|
43 AddResponse = 9
|
Chris@909
|
44 DeleteResponse = 11
|
Chris@909
|
45 ModifyRDNResponse = 13
|
Chris@909
|
46 SearchResultReferral = 19
|
Chris@909
|
47
|
Chris@909
|
48 attr_reader :msg_id, :app_tag
|
Chris@909
|
49 attr_reader :search_dn, :search_attributes, :search_entry
|
Chris@909
|
50 attr_reader :search_referrals
|
Chris@909
|
51
|
Chris@909
|
52 #
|
Chris@909
|
53 # initialize
|
Chris@909
|
54 # An LDAP PDU always looks like a BerSequence with
|
Chris@909
|
55 # at least two elements: an integer (message-id number), and
|
Chris@909
|
56 # an application-specific sequence.
|
Chris@909
|
57 # Some LDAPv3 packets also include an optional
|
Chris@909
|
58 # third element, which is a sequence of "controls"
|
Chris@909
|
59 # (See RFC 2251, section 4.1.12).
|
Chris@909
|
60 # The application-specific tag in the sequence tells
|
Chris@909
|
61 # us what kind of packet it is, and each kind has its
|
Chris@909
|
62 # own format, defined in RFC-1777.
|
Chris@909
|
63 # Observe that many clients (such as ldapsearch)
|
Chris@909
|
64 # do not necessarily enforce the expected application
|
Chris@909
|
65 # tags on received protocol packets. This implementation
|
Chris@909
|
66 # does interpret the RFC strictly in this regard, and
|
Chris@909
|
67 # it remains to be seen whether there are servers out
|
Chris@909
|
68 # there that will not work well with our approach.
|
Chris@909
|
69 #
|
Chris@909
|
70 # Added a controls-processor to SearchResult.
|
Chris@909
|
71 # Didn't add it everywhere because it just _feels_
|
Chris@909
|
72 # like it will need to be refactored.
|
Chris@909
|
73 #
|
Chris@909
|
74 def initialize ber_object
|
Chris@909
|
75 begin
|
Chris@909
|
76 @msg_id = ber_object[0].to_i
|
Chris@909
|
77 @app_tag = ber_object[1].ber_identifier - 0x60
|
Chris@909
|
78 rescue
|
Chris@909
|
79 # any error becomes a data-format error
|
Chris@909
|
80 raise LdapPduError.new( "ldap-pdu format error" )
|
Chris@909
|
81 end
|
Chris@909
|
82
|
Chris@909
|
83 case @app_tag
|
Chris@909
|
84 when BindResult
|
Chris@909
|
85 parse_ldap_result ber_object[1]
|
Chris@909
|
86 when SearchReturnedData
|
Chris@909
|
87 parse_search_return ber_object[1]
|
Chris@909
|
88 when SearchResultReferral
|
Chris@909
|
89 parse_search_referral ber_object[1]
|
Chris@909
|
90 when SearchResult
|
Chris@909
|
91 parse_ldap_result ber_object[1]
|
Chris@909
|
92 parse_controls(ber_object[2]) if ber_object[2]
|
Chris@909
|
93 when ModifyResponse
|
Chris@909
|
94 parse_ldap_result ber_object[1]
|
Chris@909
|
95 when AddResponse
|
Chris@909
|
96 parse_ldap_result ber_object[1]
|
Chris@909
|
97 when DeleteResponse
|
Chris@909
|
98 parse_ldap_result ber_object[1]
|
Chris@909
|
99 when ModifyRDNResponse
|
Chris@909
|
100 parse_ldap_result ber_object[1]
|
Chris@909
|
101 else
|
Chris@909
|
102 raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" )
|
Chris@909
|
103 end
|
Chris@909
|
104 end
|
Chris@909
|
105
|
Chris@909
|
106 #
|
Chris@909
|
107 # result_code
|
Chris@909
|
108 # This returns an LDAP result code taken from the PDU,
|
Chris@909
|
109 # but it will be nil if there wasn't a result code.
|
Chris@909
|
110 # That can easily happen depending on the type of packet.
|
Chris@909
|
111 #
|
Chris@909
|
112 def result_code code = :resultCode
|
Chris@909
|
113 @ldap_result and @ldap_result[code]
|
Chris@909
|
114 end
|
Chris@909
|
115
|
Chris@909
|
116 # Return RFC-2251 Controls if any.
|
Chris@909
|
117 # Messy. Does this functionality belong somewhere else?
|
Chris@909
|
118 def result_controls
|
Chris@909
|
119 @ldap_controls || []
|
Chris@909
|
120 end
|
Chris@909
|
121
|
Chris@909
|
122
|
Chris@909
|
123 #
|
Chris@909
|
124 # parse_ldap_result
|
Chris@909
|
125 #
|
Chris@909
|
126 def parse_ldap_result sequence
|
Chris@909
|
127 sequence.length >= 3 or raise LdapPduError
|
Chris@909
|
128 @ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
|
Chris@909
|
129 end
|
Chris@909
|
130 private :parse_ldap_result
|
Chris@909
|
131
|
Chris@909
|
132 #
|
Chris@909
|
133 # parse_search_return
|
Chris@909
|
134 # Definition from RFC 1777 (we're handling application-4 here)
|
Chris@909
|
135 #
|
Chris@909
|
136 # Search Response ::=
|
Chris@909
|
137 # CHOICE {
|
Chris@909
|
138 # entry [APPLICATION 4] SEQUENCE {
|
Chris@909
|
139 # objectName LDAPDN,
|
Chris@909
|
140 # attributes SEQUENCE OF SEQUENCE {
|
Chris@909
|
141 # AttributeType,
|
Chris@909
|
142 # SET OF AttributeValue
|
Chris@909
|
143 # }
|
Chris@909
|
144 # },
|
Chris@909
|
145 # resultCode [APPLICATION 5] LDAPResult
|
Chris@909
|
146 # }
|
Chris@909
|
147 #
|
Chris@909
|
148 # We concoct a search response that is a hash of the returned attribute values.
|
Chris@909
|
149 # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
|
Chris@909
|
150 # This is to make them more predictable for user programs, but it
|
Chris@909
|
151 # may not be a good idea. Maybe this should be configurable.
|
Chris@909
|
152 # ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes,
|
Chris@909
|
153 # we also return @search_entry, which is an LDAP::Entry object.
|
Chris@909
|
154 # If that works out well, then we'll remove the first two.
|
Chris@909
|
155 #
|
Chris@909
|
156 # Provisionally removed obsolete search_attributes and search_dn, 04May06.
|
Chris@909
|
157 #
|
Chris@909
|
158 def parse_search_return sequence
|
Chris@909
|
159 sequence.length >= 2 or raise LdapPduError
|
Chris@909
|
160 @search_entry = LDAP::Entry.new( sequence[0] )
|
Chris@909
|
161 #@search_dn = sequence[0]
|
Chris@909
|
162 #@search_attributes = {}
|
Chris@909
|
163 sequence[1].each {|seq|
|
Chris@909
|
164 @search_entry[seq[0]] = seq[1]
|
Chris@909
|
165 #@search_attributes[seq[0].downcase.intern] = seq[1]
|
Chris@909
|
166 }
|
Chris@909
|
167 end
|
Chris@909
|
168
|
Chris@909
|
169 #
|
Chris@909
|
170 # A search referral is a sequence of one or more LDAP URIs.
|
Chris@909
|
171 # Any number of search-referral replies can be returned by the server, interspersed
|
Chris@909
|
172 # with normal replies in any order.
|
Chris@909
|
173 # Until I can think of a better way to do this, we'll return the referrals as an array.
|
Chris@909
|
174 # It'll be up to higher-level handlers to expose something reasonable to the client.
|
Chris@909
|
175 def parse_search_referral uris
|
Chris@909
|
176 @search_referrals = uris
|
Chris@909
|
177 end
|
Chris@909
|
178
|
Chris@909
|
179
|
Chris@909
|
180 # Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
|
Chris@909
|
181 # of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
|
Chris@909
|
182 # Octet String. If only two fields are given, the second one may be
|
Chris@909
|
183 # either criticality or data, since criticality has a default value.
|
Chris@909
|
184 # Someday we may want to come back here and add support for some of
|
Chris@909
|
185 # more-widely used controls. RFC-2696 is a good example.
|
Chris@909
|
186 #
|
Chris@909
|
187 def parse_controls sequence
|
Chris@909
|
188 @ldap_controls = sequence.map do |control|
|
Chris@909
|
189 o = OpenStruct.new
|
Chris@909
|
190 o.oid,o.criticality,o.value = control[0],control[1],control[2]
|
Chris@909
|
191 if o.criticality and o.criticality.is_a?(String)
|
Chris@909
|
192 o.value = o.criticality
|
Chris@909
|
193 o.criticality = false
|
Chris@909
|
194 end
|
Chris@909
|
195 o
|
Chris@909
|
196 end
|
Chris@909
|
197 end
|
Chris@909
|
198 private :parse_controls
|
Chris@909
|
199
|
Chris@909
|
200
|
Chris@909
|
201 end
|
Chris@909
|
202
|
Chris@909
|
203
|
Chris@909
|
204 end # module Net
|
Chris@909
|
205
|