pruby-net-ldap 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/COPYING +272 -0
- data/ChangeLog +58 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +16 -0
- data/LICENCE +55 -0
- data/LICENSE.txt +20 -0
- data/README +32 -0
- data/README.rdoc +62 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/net/ber.rb +294 -0
- data/lib/net/ldap/dataset.rb +108 -0
- data/lib/net/ldap/entry.rb +165 -0
- data/lib/net/ldap/filter.rb +387 -0
- data/lib/net/ldap/pdu.rb +205 -0
- data/lib/net/ldap/psw.rb +64 -0
- data/lib/net/ldap.rb +1311 -0
- data/lib/net/ldif.rb +39 -0
- data/lib/pruby-net-ldap.rb +1 -0
- data/pre-setup.rb +46 -0
- data/pruby-net-ldap.gemspec +74 -0
- data/setup.rb +1366 -0
- data/tests/testber.rb +42 -0
- data/tests/testdata.ldif +101 -0
- data/tests/testem.rb +12 -0
- data/tests/testfilter.rb +37 -0
- data/tests/testldap.rb +190 -0
- data/tests/testldif.rb +69 -0
- data/tests/testpsw.rb +28 -0
- metadata +112 -0
@@ -0,0 +1,387 @@
|
|
1
|
+
# $Id: filter.rb 151 2006-08-15 08:34:53Z blackhedd $
|
2
|
+
#
|
3
|
+
#
|
4
|
+
#----------------------------------------------------------------------------
|
5
|
+
#
|
6
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
7
|
+
#
|
8
|
+
# Gmail: garbagecat10
|
9
|
+
#
|
10
|
+
# This program is free software; you can redistribute it and/or modify
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
12
|
+
# the Free Software Foundation; either version 2 of the License, or
|
13
|
+
# (at your option) any later version.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU General Public License
|
21
|
+
# along with this program; if not, write to the Free Software
|
22
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
23
|
+
#
|
24
|
+
#---------------------------------------------------------------------------
|
25
|
+
#
|
26
|
+
#
|
27
|
+
|
28
|
+
|
29
|
+
module Net
|
30
|
+
class LDAP
|
31
|
+
|
32
|
+
|
33
|
+
# Class Net::LDAP::Filter is used to constrain
|
34
|
+
# LDAP searches. An object of this class is
|
35
|
+
# passed to Net::LDAP#search in the parameter :filter.
|
36
|
+
#
|
37
|
+
# Net::LDAP::Filter supports the complete set of search filters
|
38
|
+
# available in LDAP, including conjunction, disjunction and negation
|
39
|
+
# (AND, OR, and NOT). This class supplants the (infamous) RFC-2254
|
40
|
+
# standard notation for specifying LDAP search filters.
|
41
|
+
#
|
42
|
+
# Here's how to code the familiar "objectclass is present" filter:
|
43
|
+
# f = Net::LDAP::Filter.pres( "objectclass" )
|
44
|
+
# The object returned by this code can be passed directly to
|
45
|
+
# the <tt>:filter</tt> parameter of Net::LDAP#search.
|
46
|
+
#
|
47
|
+
# See the individual class and instance methods below for more examples.
|
48
|
+
#
|
49
|
+
class Filter
|
50
|
+
|
51
|
+
def initialize op, a, b
|
52
|
+
@op = op
|
53
|
+
@left = a
|
54
|
+
@right = b
|
55
|
+
end
|
56
|
+
|
57
|
+
# #eq creates a filter object indicating that the value of
|
58
|
+
# a paticular attribute must be either <i>present</i> or must
|
59
|
+
# match a particular string.
|
60
|
+
#
|
61
|
+
# To specify that an attribute is "present" means that only
|
62
|
+
# directory entries which contain a value for the particular
|
63
|
+
# attribute will be selected by the filter. This is useful
|
64
|
+
# in case of optional attributes such as <tt>mail.</tt>
|
65
|
+
# Presence is indicated by giving the value "*" in the second
|
66
|
+
# parameter to #eq. This example selects only entries that have
|
67
|
+
# one or more values for <tt>sAMAccountName:</tt>
|
68
|
+
# f = Net::LDAP::Filter.eq( "sAMAccountName", "*" )
|
69
|
+
#
|
70
|
+
# To match a particular range of values, pass a string as the
|
71
|
+
# second parameter to #eq. The string may contain one or more
|
72
|
+
# "*" characters as wildcards: these match zero or more occurrences
|
73
|
+
# of any character. Full regular-expressions are <i>not</i> supported
|
74
|
+
# due to limitations in the underlying LDAP protocol.
|
75
|
+
# This example selects any entry with a <tt>mail</tt> value containing
|
76
|
+
# the substring "anderson":
|
77
|
+
# f = Net::LDAP::Filter.eq( "mail", "*anderson*" )
|
78
|
+
#--
|
79
|
+
# Removed gt and lt. They ain't in the standard!
|
80
|
+
#
|
81
|
+
def Filter::eq attribute, value; Filter.new :eq, attribute, value; end
|
82
|
+
def Filter::ne attribute, value; Filter.new :ne, attribute, value; end
|
83
|
+
#def Filter::gt attribute, value; Filter.new :gt, attribute, value; end
|
84
|
+
#def Filter::lt attribute, value; Filter.new :lt, attribute, value; end
|
85
|
+
def Filter::ge attribute, value; Filter.new :ge, attribute, value; end
|
86
|
+
def Filter::le attribute, value; Filter.new :le, attribute, value; end
|
87
|
+
|
88
|
+
# #pres( attribute ) is a synonym for #eq( attribute, "*" )
|
89
|
+
#
|
90
|
+
def Filter::pres attribute; Filter.eq attribute, "*"; end
|
91
|
+
|
92
|
+
# operator & ("AND") is used to conjoin two or more filters.
|
93
|
+
# This expression will select only entries that have an <tt>objectclass</tt>
|
94
|
+
# attribute AND have a <tt>mail</tt> attribute that begins with "George":
|
95
|
+
# f = Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.eq( "mail", "George*" )
|
96
|
+
#
|
97
|
+
def & filter; Filter.new :and, self, filter; end
|
98
|
+
|
99
|
+
# operator | ("OR") is used to disjoin two or more filters.
|
100
|
+
# This expression will select entries that have either an <tt>objectclass</tt>
|
101
|
+
# attribute OR a <tt>mail</tt> attribute that begins with "George":
|
102
|
+
# f = Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.eq( "mail", "George*" )
|
103
|
+
#
|
104
|
+
def | filter; Filter.new :or, self, filter; end
|
105
|
+
|
106
|
+
|
107
|
+
#
|
108
|
+
# operator ~ ("NOT") is used to negate a filter.
|
109
|
+
# This expression will select only entries that <i>do not</i> have an <tt>objectclass</tt>
|
110
|
+
# attribute:
|
111
|
+
# f = ~ Net::LDAP::Filter.pres( "objectclass" )
|
112
|
+
#
|
113
|
+
#--
|
114
|
+
# This operator can't be !, evidently. Try it.
|
115
|
+
# Removed GT and LT. They're not in the RFC.
|
116
|
+
def ~@; Filter.new :not, self, nil; end
|
117
|
+
|
118
|
+
|
119
|
+
def to_s
|
120
|
+
case @op
|
121
|
+
when :ne
|
122
|
+
"(!(#{@left}=#{@right}))"
|
123
|
+
when :eq
|
124
|
+
"(#{@left}=#{@right})"
|
125
|
+
#when :gt
|
126
|
+
# "#{@left}>#{@right}"
|
127
|
+
#when :lt
|
128
|
+
# "#{@left}<#{@right}"
|
129
|
+
when :ge
|
130
|
+
"#{@left}>=#{@right}"
|
131
|
+
when :le
|
132
|
+
"#{@left}<=#{@right}"
|
133
|
+
when :and
|
134
|
+
"(&(#{@left})(#{@right}))"
|
135
|
+
when :or
|
136
|
+
"(|(#{@left})(#{@right}))"
|
137
|
+
when :not
|
138
|
+
"(!(#{@left}))"
|
139
|
+
else
|
140
|
+
raise "invalid or unsupported operator in LDAP Filter"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
#--
|
146
|
+
# to_ber
|
147
|
+
# Filter ::=
|
148
|
+
# CHOICE {
|
149
|
+
# and [0] SET OF Filter,
|
150
|
+
# or [1] SET OF Filter,
|
151
|
+
# not [2] Filter,
|
152
|
+
# equalityMatch [3] AttributeValueAssertion,
|
153
|
+
# substrings [4] SubstringFilter,
|
154
|
+
# greaterOrEqual [5] AttributeValueAssertion,
|
155
|
+
# lessOrEqual [6] AttributeValueAssertion,
|
156
|
+
# present [7] AttributeType,
|
157
|
+
# approxMatch [8] AttributeValueAssertion
|
158
|
+
# }
|
159
|
+
#
|
160
|
+
# SubstringFilter
|
161
|
+
# SEQUENCE {
|
162
|
+
# type AttributeType,
|
163
|
+
# SEQUENCE OF CHOICE {
|
164
|
+
# initial [0] LDAPString,
|
165
|
+
# any [1] LDAPString,
|
166
|
+
# final [2] LDAPString
|
167
|
+
# }
|
168
|
+
# }
|
169
|
+
#
|
170
|
+
# Parsing substrings is a little tricky.
|
171
|
+
# We use the split method to break a string into substrings
|
172
|
+
# delimited by the * (star) character. But we also need
|
173
|
+
# to know whether there is a star at the head and tail
|
174
|
+
# of the string. A Ruby particularity comes into play here:
|
175
|
+
# if you split on * and the first character of the string is
|
176
|
+
# a star, then split will return an array whose first element
|
177
|
+
# is an _empty_ string. But if the _last_ character of the
|
178
|
+
# string is star, then split will return an array that does
|
179
|
+
# _not_ add an empty string at the end. So we have to deal
|
180
|
+
# with all that specifically.
|
181
|
+
#
|
182
|
+
def to_ber
|
183
|
+
case @op
|
184
|
+
when :eq
|
185
|
+
if @right == "*" # present
|
186
|
+
@left.to_s.to_ber_contextspecific 7
|
187
|
+
elsif @right =~ /[\*]/ #substring
|
188
|
+
ary = @right.split( /[\*]+/ )
|
189
|
+
final_star = @right =~ /[\*]$/
|
190
|
+
initial_star = ary.first == "" and ary.shift
|
191
|
+
|
192
|
+
seq = []
|
193
|
+
unless initial_star
|
194
|
+
seq << ary.shift.to_ber_contextspecific(0)
|
195
|
+
end
|
196
|
+
n_any_strings = ary.length - (final_star ? 0 : 1)
|
197
|
+
#p n_any_strings
|
198
|
+
n_any_strings.times {
|
199
|
+
seq << ary.shift.to_ber_contextspecific(1)
|
200
|
+
}
|
201
|
+
unless final_star
|
202
|
+
seq << ary.shift.to_ber_contextspecific(2)
|
203
|
+
end
|
204
|
+
[@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific 4
|
205
|
+
else #equality
|
206
|
+
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 3
|
207
|
+
end
|
208
|
+
when :ge
|
209
|
+
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 5
|
210
|
+
when :le
|
211
|
+
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 6
|
212
|
+
when :and
|
213
|
+
ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
|
214
|
+
ary.map {|a| a.to_ber}.to_ber_contextspecific( 0 )
|
215
|
+
when :or
|
216
|
+
ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
|
217
|
+
ary.map {|a| a.to_ber}.to_ber_contextspecific( 1 )
|
218
|
+
when :not
|
219
|
+
[@left.to_ber].to_ber_contextspecific 2
|
220
|
+
else
|
221
|
+
# ERROR, we'll return objectclass=* to keep things from blowing up,
|
222
|
+
# but that ain't a good answer and we need to kick out an error of some kind.
|
223
|
+
raise "unimplemented search filter"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
#--
|
228
|
+
# coalesce
|
229
|
+
# This is a private helper method for dealing with chains of ANDs and ORs
|
230
|
+
# that are longer than two. If BOTH of our branches are of the specified
|
231
|
+
# type of joining operator, then return both of them as an array (calling
|
232
|
+
# coalesce recursively). If they're not, then return an array consisting
|
233
|
+
# only of self.
|
234
|
+
#
|
235
|
+
def coalesce operator
|
236
|
+
if @op == operator
|
237
|
+
[@left.coalesce( operator ), @right.coalesce( operator )]
|
238
|
+
else
|
239
|
+
[self]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
#--
|
246
|
+
# We get a Ruby object which comes from parsing an RFC-1777 "Filter"
|
247
|
+
# object. Convert it to a Net::LDAP::Filter.
|
248
|
+
# TODO, we're hardcoding the RFC-1777 BER-encodings of the various
|
249
|
+
# filter types. Could pull them out into a constant.
|
250
|
+
#
|
251
|
+
def Filter::parse_ldap_filter obj
|
252
|
+
case obj.ber_identifier
|
253
|
+
when 0x87 # present. context-specific primitive 7.
|
254
|
+
Filter.eq( obj.to_s, "*" )
|
255
|
+
when 0xa3 # equalityMatch. context-specific constructed 3.
|
256
|
+
Filter.eq( obj[0], obj[1] )
|
257
|
+
else
|
258
|
+
raise LdapError.new( "unknown ldap search-filter type: #{obj.ber_identifier}" )
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
#--
|
264
|
+
# We got a hash of attribute values.
|
265
|
+
# Do we match the attributes?
|
266
|
+
# Return T/F, and call match recursively as necessary.
|
267
|
+
def match entry
|
268
|
+
case @op
|
269
|
+
when :eq
|
270
|
+
if @right == "*"
|
271
|
+
l = entry[@left] and l.length > 0
|
272
|
+
else
|
273
|
+
l = entry[@left] and l = l.to_a and l.index(@right)
|
274
|
+
end
|
275
|
+
else
|
276
|
+
raise LdapError.new( "unknown filter type in match: #{@op}" )
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254)
|
281
|
+
# to a Net::LDAP::Filter.
|
282
|
+
def self.construct ldap_filter_string
|
283
|
+
FilterParser.new(ldap_filter_string).filter
|
284
|
+
end
|
285
|
+
|
286
|
+
# Synonym for #construct.
|
287
|
+
# to a Net::LDAP::Filter.
|
288
|
+
def self.from_rfc2254 ldap_filter_string
|
289
|
+
construct ldap_filter_string
|
290
|
+
end
|
291
|
+
|
292
|
+
end # class Net::LDAP::Filter
|
293
|
+
|
294
|
+
|
295
|
+
|
296
|
+
class FilterParser #:nodoc:
|
297
|
+
|
298
|
+
attr_reader :filter
|
299
|
+
|
300
|
+
def initialize str
|
301
|
+
require 'strscan'
|
302
|
+
@filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" )
|
303
|
+
end
|
304
|
+
|
305
|
+
def parse scanner
|
306
|
+
parse_filter_branch(scanner) or parse_paren_expression(scanner)
|
307
|
+
end
|
308
|
+
|
309
|
+
def parse_paren_expression scanner
|
310
|
+
if scanner.scan /\s*\(\s*/
|
311
|
+
b = if scanner.scan /\s*\&\s*/
|
312
|
+
a = nil
|
313
|
+
branches = []
|
314
|
+
while br = parse_paren_expression(scanner)
|
315
|
+
branches << br
|
316
|
+
end
|
317
|
+
if branches.length >= 2
|
318
|
+
a = branches.shift
|
319
|
+
while branches.length > 0
|
320
|
+
a = a & branches.shift
|
321
|
+
end
|
322
|
+
a
|
323
|
+
end
|
324
|
+
elsif scanner.scan /\s*\|\s*/
|
325
|
+
# TODO: DRY!
|
326
|
+
a = nil
|
327
|
+
branches = []
|
328
|
+
while br = parse_paren_expression(scanner)
|
329
|
+
branches << br
|
330
|
+
end
|
331
|
+
if branches.length >= 2
|
332
|
+
a = branches.shift
|
333
|
+
while branches.length > 0
|
334
|
+
a = a | branches.shift
|
335
|
+
end
|
336
|
+
a
|
337
|
+
end
|
338
|
+
elsif scanner.scan /\s*\!\s*/
|
339
|
+
br = parse_paren_expression(scanner)
|
340
|
+
if br
|
341
|
+
~ br
|
342
|
+
end
|
343
|
+
else
|
344
|
+
parse_filter_branch( scanner )
|
345
|
+
end
|
346
|
+
|
347
|
+
if b and scanner.scan( /\s*\)\s*/ )
|
348
|
+
b
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Added a greatly-augmented filter contributed by Andre Nathan
|
354
|
+
# for detecting special characters in values. (15Aug06)
|
355
|
+
def parse_filter_branch scanner
|
356
|
+
scanner.scan /\s*/
|
357
|
+
if token = scanner.scan( /[\w\-_]+/ )
|
358
|
+
scanner.scan /\s*/
|
359
|
+
if op = scanner.scan( /\=|\<\=|\<|\>\=|\>|\!\=/ )
|
360
|
+
scanner.scan /\s*/
|
361
|
+
#if value = scanner.scan( /[\w\*\.]+/ ) (ORG)
|
362
|
+
if value = scanner.scan( /[\w\*\.\+\-@=#\$%&!]+/ )
|
363
|
+
case op
|
364
|
+
when "="
|
365
|
+
Filter.eq( token, value )
|
366
|
+
when "!="
|
367
|
+
Filter.ne( token, value )
|
368
|
+
when "<"
|
369
|
+
Filter.lt( token, value )
|
370
|
+
when "<="
|
371
|
+
Filter.le( token, value )
|
372
|
+
when ">"
|
373
|
+
Filter.gt( token, value )
|
374
|
+
when ">="
|
375
|
+
Filter.ge( token, value )
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
end # class Net::LDAP::FilterParser
|
383
|
+
|
384
|
+
end # class Net::LDAP
|
385
|
+
end # module Net
|
386
|
+
|
387
|
+
|
data/lib/net/ldap/pdu.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $
|
2
|
+
#
|
3
|
+
# LDAP PDU support classes
|
4
|
+
#
|
5
|
+
#
|
6
|
+
#----------------------------------------------------------------------------
|
7
|
+
#
|
8
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
9
|
+
#
|
10
|
+
# Gmail: garbagecat10
|
11
|
+
#
|
12
|
+
# This program is free software; you can redistribute it and/or modify
|
13
|
+
# it under the terms of the GNU General Public License as published by
|
14
|
+
# the Free Software Foundation; either version 2 of the License, or
|
15
|
+
# (at your option) any later version.
|
16
|
+
#
|
17
|
+
# This program is distributed in the hope that it will be useful,
|
18
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
19
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
20
|
+
# GNU General Public License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU General Public License
|
23
|
+
# along with this program; if not, write to the Free Software
|
24
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
25
|
+
#
|
26
|
+
#---------------------------------------------------------------------------
|
27
|
+
#
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
module Net
|
32
|
+
|
33
|
+
|
34
|
+
class LdapPduError < Exception; end
|
35
|
+
|
36
|
+
|
37
|
+
class LdapPdu
|
38
|
+
|
39
|
+
BindResult = 1
|
40
|
+
SearchReturnedData = 4
|
41
|
+
SearchResult = 5
|
42
|
+
ModifyResponse = 7
|
43
|
+
AddResponse = 9
|
44
|
+
DeleteResponse = 11
|
45
|
+
ModifyRDNResponse = 13
|
46
|
+
SearchResultReferral = 19
|
47
|
+
|
48
|
+
attr_reader :msg_id, :app_tag
|
49
|
+
attr_reader :search_dn, :search_attributes, :search_entry
|
50
|
+
attr_reader :search_referrals
|
51
|
+
|
52
|
+
#
|
53
|
+
# initialize
|
54
|
+
# An LDAP PDU always looks like a BerSequence with
|
55
|
+
# at least two elements: an integer (message-id number), and
|
56
|
+
# an application-specific sequence.
|
57
|
+
# Some LDAPv3 packets also include an optional
|
58
|
+
# third element, which is a sequence of "controls"
|
59
|
+
# (See RFC 2251, section 4.1.12).
|
60
|
+
# The application-specific tag in the sequence tells
|
61
|
+
# us what kind of packet it is, and each kind has its
|
62
|
+
# own format, defined in RFC-1777.
|
63
|
+
# Observe that many clients (such as ldapsearch)
|
64
|
+
# do not necessarily enforce the expected application
|
65
|
+
# tags on received protocol packets. This implementation
|
66
|
+
# does interpret the RFC strictly in this regard, and
|
67
|
+
# it remains to be seen whether there are servers out
|
68
|
+
# there that will not work well with our approach.
|
69
|
+
#
|
70
|
+
# Added a controls-processor to SearchResult.
|
71
|
+
# Didn't add it everywhere because it just _feels_
|
72
|
+
# like it will need to be refactored.
|
73
|
+
#
|
74
|
+
def initialize ber_object
|
75
|
+
begin
|
76
|
+
@msg_id = ber_object[0].to_i
|
77
|
+
@app_tag = ber_object[1].ber_identifier - 0x60
|
78
|
+
rescue
|
79
|
+
# any error becomes a data-format error
|
80
|
+
raise LdapPduError.new( "ldap-pdu format error" )
|
81
|
+
end
|
82
|
+
|
83
|
+
case @app_tag
|
84
|
+
when BindResult
|
85
|
+
parse_ldap_result ber_object[1]
|
86
|
+
when SearchReturnedData
|
87
|
+
parse_search_return ber_object[1]
|
88
|
+
when SearchResultReferral
|
89
|
+
parse_search_referral ber_object[1]
|
90
|
+
when SearchResult
|
91
|
+
parse_ldap_result ber_object[1]
|
92
|
+
parse_controls(ber_object[2]) if ber_object[2]
|
93
|
+
when ModifyResponse
|
94
|
+
parse_ldap_result ber_object[1]
|
95
|
+
when AddResponse
|
96
|
+
parse_ldap_result ber_object[1]
|
97
|
+
when DeleteResponse
|
98
|
+
parse_ldap_result ber_object[1]
|
99
|
+
when ModifyRDNResponse
|
100
|
+
parse_ldap_result ber_object[1]
|
101
|
+
else
|
102
|
+
raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" )
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# result_code
|
108
|
+
# This returns an LDAP result code taken from the PDU,
|
109
|
+
# but it will be nil if there wasn't a result code.
|
110
|
+
# That can easily happen depending on the type of packet.
|
111
|
+
#
|
112
|
+
def result_code code = :resultCode
|
113
|
+
@ldap_result and @ldap_result[code]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return RFC-2251 Controls if any.
|
117
|
+
# Messy. Does this functionality belong somewhere else?
|
118
|
+
def result_controls
|
119
|
+
@ldap_controls || []
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
#
|
124
|
+
# parse_ldap_result
|
125
|
+
#
|
126
|
+
def parse_ldap_result sequence
|
127
|
+
sequence.length >= 3 or raise LdapPduError
|
128
|
+
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
|
129
|
+
end
|
130
|
+
private :parse_ldap_result
|
131
|
+
|
132
|
+
#
|
133
|
+
# parse_search_return
|
134
|
+
# Definition from RFC 1777 (we're handling application-4 here)
|
135
|
+
#
|
136
|
+
# Search Response ::=
|
137
|
+
# CHOICE {
|
138
|
+
# entry [APPLICATION 4] SEQUENCE {
|
139
|
+
# objectName LDAPDN,
|
140
|
+
# attributes SEQUENCE OF SEQUENCE {
|
141
|
+
# AttributeType,
|
142
|
+
# SET OF AttributeValue
|
143
|
+
# }
|
144
|
+
# },
|
145
|
+
# resultCode [APPLICATION 5] LDAPResult
|
146
|
+
# }
|
147
|
+
#
|
148
|
+
# We concoct a search response that is a hash of the returned attribute values.
|
149
|
+
# NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
|
150
|
+
# This is to make them more predictable for user programs, but it
|
151
|
+
# may not be a good idea. Maybe this should be configurable.
|
152
|
+
# ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes,
|
153
|
+
# we also return @search_entry, which is an LDAP::Entry object.
|
154
|
+
# If that works out well, then we'll remove the first two.
|
155
|
+
#
|
156
|
+
# Provisionally removed obsolete search_attributes and search_dn, 04May06.
|
157
|
+
#
|
158
|
+
def parse_search_return sequence
|
159
|
+
sequence.length >= 2 or raise LdapPduError
|
160
|
+
@search_entry = LDAP::Entry.new( sequence[0] )
|
161
|
+
#@search_dn = sequence[0]
|
162
|
+
#@search_attributes = {}
|
163
|
+
sequence[1].each {|seq|
|
164
|
+
@search_entry[seq[0]] = seq[1]
|
165
|
+
#@search_attributes[seq[0].downcase.intern] = seq[1]
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# A search referral is a sequence of one or more LDAP URIs.
|
171
|
+
# Any number of search-referral replies can be returned by the server, interspersed
|
172
|
+
# with normal replies in any order.
|
173
|
+
# Until I can think of a better way to do this, we'll return the referrals as an array.
|
174
|
+
# It'll be up to higher-level handlers to expose something reasonable to the client.
|
175
|
+
def parse_search_referral uris
|
176
|
+
@search_referrals = uris
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
|
181
|
+
# of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
|
182
|
+
# Octet String. If only two fields are given, the second one may be
|
183
|
+
# either criticality or data, since criticality has a default value.
|
184
|
+
# Someday we may want to come back here and add support for some of
|
185
|
+
# more-widely used controls. RFC-2696 is a good example.
|
186
|
+
#
|
187
|
+
def parse_controls sequence
|
188
|
+
@ldap_controls = sequence.map do |control|
|
189
|
+
o = OpenStruct.new
|
190
|
+
o.oid,o.criticality,o.value = control[0],control[1],control[2]
|
191
|
+
if o.criticality and o.criticality.is_a?(String)
|
192
|
+
o.value = o.criticality
|
193
|
+
o.criticality = false
|
194
|
+
end
|
195
|
+
o
|
196
|
+
end
|
197
|
+
end
|
198
|
+
private :parse_controls
|
199
|
+
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
end # module Net
|
205
|
+
|
data/lib/net/ldap/psw.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# $Id: psw.rb 73 2006-04-24 21:59:35Z blackhedd $
|
2
|
+
#
|
3
|
+
#
|
4
|
+
#----------------------------------------------------------------------------
|
5
|
+
#
|
6
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
7
|
+
#
|
8
|
+
# Gmail: garbagecat10
|
9
|
+
#
|
10
|
+
# This program is free software; you can redistribute it and/or modify
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
12
|
+
# the Free Software Foundation; either version 2 of the License, or
|
13
|
+
# (at your option) any later version.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU General Public License
|
21
|
+
# along with this program; if not, write to the Free Software
|
22
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
23
|
+
#
|
24
|
+
#---------------------------------------------------------------------------
|
25
|
+
#
|
26
|
+
#
|
27
|
+
|
28
|
+
|
29
|
+
module Net
|
30
|
+
class LDAP
|
31
|
+
|
32
|
+
|
33
|
+
class Password
|
34
|
+
class << self
|
35
|
+
|
36
|
+
# Generate a password-hash suitable for inclusion in an LDAP attribute.
|
37
|
+
# Pass a hash type (currently supported: :md5 and :sha) and a plaintext
|
38
|
+
# password. This function will return a hashed representation.
|
39
|
+
# STUB: This is here to fulfill the requirements of an RFC, which one?
|
40
|
+
# TODO, gotta do salted-sha and (maybe) salted-md5.
|
41
|
+
# Should we provide sha1 as a synonym for sha1? I vote no because then
|
42
|
+
# should you also provide ssha1 for symmetry?
|
43
|
+
def generate( type, str )
|
44
|
+
case type
|
45
|
+
when :md5
|
46
|
+
require 'md5'
|
47
|
+
"{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }"
|
48
|
+
when :sha
|
49
|
+
require 'sha1'
|
50
|
+
"{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }"
|
51
|
+
# when ssha
|
52
|
+
else
|
53
|
+
raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" )
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end # class LDAP
|
62
|
+
end # module Net
|
63
|
+
|
64
|
+
|