ruby-ldapserver 0.3.1
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.
- data/COPYING +27 -0
- data/ChangeLog +83 -0
- data/Manifest.txt +32 -0
- data/README +222 -0
- data/Rakefile +22 -0
- data/examples/README +89 -0
- data/examples/mkcert.rb +31 -0
- data/examples/rbslapd1.rb +111 -0
- data/examples/rbslapd2.rb +161 -0
- data/examples/rbslapd3.rb +172 -0
- data/examples/speedtest.rb +37 -0
- data/lib/ldap/server.rb +4 -0
- data/lib/ldap/server/connection.rb +273 -0
- data/lib/ldap/server/filter.rb +223 -0
- data/lib/ldap/server/match.rb +283 -0
- data/lib/ldap/server/operation.rb +487 -0
- data/lib/ldap/server/preforkserver.rb +93 -0
- data/lib/ldap/server/result.rb +71 -0
- data/lib/ldap/server/schema.rb +592 -0
- data/lib/ldap/server/server.rb +89 -0
- data/lib/ldap/server/syntax.rb +235 -0
- data/lib/ldap/server/tcpserver.rb +91 -0
- data/lib/ldap/server/util.rb +88 -0
- data/lib/ldap/server/version.rb +11 -0
- data/test/core.schema +582 -0
- data/test/encoding_test.rb +279 -0
- data/test/filter_test.rb +107 -0
- data/test/match_test.rb +59 -0
- data/test/schema_test.rb +113 -0
- data/test/syntax_test.rb +40 -0
- data/test/test_helper.rb +2 -0
- data/test/util_test.rb +51 -0
- metadata +98 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'ldap'
|
4
|
+
|
5
|
+
CHILDREN = 10
|
6
|
+
CONNECTS = 1 # per child
|
7
|
+
SEARCHES = 100 # per connection
|
8
|
+
|
9
|
+
pids = []
|
10
|
+
CHILDREN.times do
|
11
|
+
pids << fork do
|
12
|
+
CONNECTS.times do
|
13
|
+
conn = LDAP::Conn.new("localhost",1389)
|
14
|
+
conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
|
15
|
+
conn.bind
|
16
|
+
SEARCHES.times do
|
17
|
+
res = conn.search("cn=Fred Flintstone,dc=example,dc=com", LDAP::LDAP_SCOPE_BASE,
|
18
|
+
"(objectclass=*)") do |e|
|
19
|
+
#puts "#{$$} #{e.dn.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
conn.unbind
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
okcount = 0
|
27
|
+
badcount = 0
|
28
|
+
pids.each do |p|
|
29
|
+
Process.wait(p)
|
30
|
+
if $?.exitstatus == 0
|
31
|
+
okcount += 1
|
32
|
+
else
|
33
|
+
badcount += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
puts "Children finished: #{okcount} ok, #{badcount} failed"
|
37
|
+
exit badcount > 0 ? 1 : 0
|
data/lib/ldap/server.rb
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'openssl'
|
3
|
+
require 'ldap/server/result'
|
4
|
+
|
5
|
+
module LDAP
|
6
|
+
class Server
|
7
|
+
|
8
|
+
# An object which handles an LDAP connection. Note that LDAP allows
|
9
|
+
# requests and responses to be exchanged asynchronously: e.g. a client
|
10
|
+
# can send three requests, and the three responses can come back in
|
11
|
+
# any order. For that reason, we start a new thread for each request,
|
12
|
+
# and we need a mutex on the io object so that multiple responses don't
|
13
|
+
# interfere with each other.
|
14
|
+
|
15
|
+
class Connection
|
16
|
+
attr_reader :binddn, :version, :opt
|
17
|
+
|
18
|
+
def initialize(io, opt={})
|
19
|
+
@io = io
|
20
|
+
@opt = opt
|
21
|
+
@mutex = Mutex.new
|
22
|
+
@active_reqs = {} # map message ID to thread object
|
23
|
+
@binddn = nil
|
24
|
+
@version = 3
|
25
|
+
@logger = @opt[:logger] || $stderr
|
26
|
+
@ssl = false
|
27
|
+
|
28
|
+
startssl if @opt[:ssl_on_connect]
|
29
|
+
end
|
30
|
+
|
31
|
+
def log(msg)
|
32
|
+
@logger << "[#{@io.peeraddr[3]}]: #{msg}\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
def startssl # :yields:
|
36
|
+
@mutex.synchronize do
|
37
|
+
raise LDAP::ResultError::OperationsError if @ssl or @active_reqs.size > 0
|
38
|
+
yield if block_given?
|
39
|
+
@io = OpenSSL::SSL::SSLSocket.new(@io, @opt[:ssl_ctx])
|
40
|
+
@io.sync_close = true
|
41
|
+
@io.accept
|
42
|
+
@ssl = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Read one ASN1 element from the given stream.
|
47
|
+
# Return String containing the raw element.
|
48
|
+
|
49
|
+
def ber_read(io)
|
50
|
+
blk = io.read(2) # minimum: short tag, short length
|
51
|
+
throw(:close) if blk.nil?
|
52
|
+
tag = blk[0] & 0x1f
|
53
|
+
len = blk[1]
|
54
|
+
|
55
|
+
if tag == 0x1f # long form
|
56
|
+
tag = 0
|
57
|
+
while true
|
58
|
+
ch = io.getc
|
59
|
+
blk << ch
|
60
|
+
tag = (tag << 7) | (ch & 0x7f)
|
61
|
+
break if (ch & 0x80) == 0
|
62
|
+
end
|
63
|
+
len = io.getc
|
64
|
+
blk << len
|
65
|
+
end
|
66
|
+
|
67
|
+
if (len & 0x80) != 0 # long form
|
68
|
+
len = len & 0x7f
|
69
|
+
raise LDAP::ResultError::ProtocolError, "Indefinite length encoding not supported" if len == 0
|
70
|
+
offset = blk.length
|
71
|
+
blk << io.read(len)
|
72
|
+
# is there a more efficient way of doing this?
|
73
|
+
len = 0
|
74
|
+
blk[offset..-1].each_byte { |b| len = (len << 8) | b }
|
75
|
+
end
|
76
|
+
|
77
|
+
offset = blk.length
|
78
|
+
blk << io.read(len)
|
79
|
+
return blk
|
80
|
+
# or if we wanted to keep the partial decoding we've done:
|
81
|
+
# return blk, [blk[0] >> 6, tag], offset
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_requests
|
85
|
+
operationClass = @opt[:operation_class]
|
86
|
+
ocArgs = @opt[:operation_args] || []
|
87
|
+
catch(:close) do
|
88
|
+
while true
|
89
|
+
begin
|
90
|
+
blk = ber_read(@io)
|
91
|
+
asn1 = OpenSSL::ASN1::decode(blk)
|
92
|
+
# Debugging:
|
93
|
+
# puts "Request: #{blk.unpack("H*")}\n#{asn1.inspect}" if $debug
|
94
|
+
|
95
|
+
raise LDAP::ResultError::ProtocolError, "LDAPMessage must be SEQUENCE" unless asn1.is_a?(OpenSSL::ASN1::Sequence)
|
96
|
+
raise LDAP::ResultError::ProtocolError, "Bad Message ID" unless asn1.value[0].is_a?(OpenSSL::ASN1::Integer)
|
97
|
+
messageId = asn1.value[0].value
|
98
|
+
|
99
|
+
protocolOp = asn1.value[1]
|
100
|
+
raise LDAP::ResultError::ProtocolError, "Bad protocolOp" unless protocolOp.is_a?(OpenSSL::ASN1::ASN1Data)
|
101
|
+
raise LDAP::ResultError::ProtocolError, "Bad protocolOp tag class" unless protocolOp.tag_class == :APPLICATION
|
102
|
+
|
103
|
+
# controls are not properly implemented
|
104
|
+
c = asn1.value[2]
|
105
|
+
if c.is_a?(OpenSSL::ASN1::ASN1Data) and c.tag_class == :APPLICATION and c.tag == 0
|
106
|
+
controls = c.value
|
107
|
+
end
|
108
|
+
|
109
|
+
case protocolOp.tag
|
110
|
+
when 0 # BindRequest
|
111
|
+
abandon_all
|
112
|
+
@binddn, @version = operationClass.new(self,messageId,*ocArgs).
|
113
|
+
do_bind(protocolOp, controls)
|
114
|
+
|
115
|
+
when 2 # UnbindRequest
|
116
|
+
throw(:close)
|
117
|
+
|
118
|
+
when 3 # SearchRequest
|
119
|
+
# Note: RFC 2251 4.4.4.1 says behaviour is undefined if
|
120
|
+
# client sends an overlapping request with same message ID,
|
121
|
+
# so we don't have to worry about the case where there is
|
122
|
+
# already a thread with this id in @active_reqs.
|
123
|
+
# However, to avoid a race we copy messageId/
|
124
|
+
# protocolOp/controls into thread-local variables, because
|
125
|
+
# they will change when the next request comes in.
|
126
|
+
#
|
127
|
+
# There is a theoretical race condition here: a client could
|
128
|
+
# send an abandon request before Thread.current is assigned to
|
129
|
+
# @active_reqs[thrm]. It's not a problem, because abandon isn't
|
130
|
+
# guaranteed to work anyway. Doing it this way ensures that
|
131
|
+
# @active_reqs does not leak memory on a long-lived connection.
|
132
|
+
|
133
|
+
Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
|
134
|
+
begin
|
135
|
+
@active_reqs[thrm] = Thread.current
|
136
|
+
operationClass.new(self,thrm,*ocArgs).do_search(thrp, thrc)
|
137
|
+
ensure
|
138
|
+
@active_reqs.delete(thrm)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
when 6 # ModifyRequest
|
143
|
+
Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
|
144
|
+
begin
|
145
|
+
@active_reqs[thrm] = Thread.current
|
146
|
+
operationClass.new(self,thrm,*ocArgs).do_modify(thrp, thrc)
|
147
|
+
ensure
|
148
|
+
@active_reqs.delete(thrm)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
when 8 # AddRequest
|
153
|
+
Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
|
154
|
+
begin
|
155
|
+
@active_reqs[thrm] = Thread.current
|
156
|
+
operationClass.new(self,thrm,*ocArgs).do_add(thrp, thrc)
|
157
|
+
ensure
|
158
|
+
@active_reqs.delete(thrm)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
when 10 # DelRequest
|
163
|
+
Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
|
164
|
+
begin
|
165
|
+
@active_reqs[thrm] = Thread.current
|
166
|
+
operationClass.new(self,thrm,*ocArgs).do_del(thrp, thrc)
|
167
|
+
ensure
|
168
|
+
@active_reqs.delete(thrm)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
when 12 # ModifyDNRequest
|
173
|
+
Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
|
174
|
+
begin
|
175
|
+
@active_reqs[thrm] = Thread.current
|
176
|
+
operationClass.new(self,thrm,*ocArgs).do_modifydn(thrp, thrc)
|
177
|
+
ensure
|
178
|
+
@active_reqs.delete(thrm)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
when 14 # CompareRequest
|
183
|
+
Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
|
184
|
+
begin
|
185
|
+
@active_reqs[thrm] = Thread.current
|
186
|
+
operationClass.new(self,thrm,*ocArgs).do_compare(thrp, thrc)
|
187
|
+
ensure
|
188
|
+
@active_reqs.delete(thrm)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
when 16 # AbandonRequest
|
193
|
+
abandon(protocolOp.value)
|
194
|
+
|
195
|
+
else
|
196
|
+
raise LDAP::ResultError::ProtocolError, "Unrecognised protocolOp tag #{protocolOp.tag}"
|
197
|
+
end
|
198
|
+
|
199
|
+
rescue LDAP::ResultError::ProtocolError, OpenSSL::ASN1::ASN1Error => e
|
200
|
+
send_notice_of_disconnection(LDAP::ResultError::ProtocolError.new.to_i, e.message)
|
201
|
+
throw(:close)
|
202
|
+
|
203
|
+
# all other exceptions propagate up and are caught by tcpserver
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
abandon_all
|
208
|
+
end
|
209
|
+
|
210
|
+
def write(data)
|
211
|
+
@mutex.synchronize do
|
212
|
+
@io.write(data)
|
213
|
+
@io.flush
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def writelock
|
218
|
+
@mutex.synchronize do
|
219
|
+
yield @io
|
220
|
+
@io.flush
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def abandon(messageID)
|
225
|
+
@mutex.synchronize do
|
226
|
+
thread = @active_reqs.delete(messageID)
|
227
|
+
thread.raise LDAP::Abandon if thread and thread.alive?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def abandon_all
|
232
|
+
return if @active_reqs.size == 0
|
233
|
+
@mutex.synchronize do
|
234
|
+
@active_reqs.each do |id, thread|
|
235
|
+
thread.raise LDAP::Abandon if thread.alive?
|
236
|
+
end
|
237
|
+
@active_reqs = {}
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def send_unsolicited_notification(resultCode, opt={})
|
242
|
+
protocolOp = [
|
243
|
+
OpenSSL::ASN1::Enumerated(resultCode),
|
244
|
+
OpenSSL::ASN1::OctetString(opt[:matchedDN] || ""),
|
245
|
+
OpenSSL::ASN1::OctetString(opt[:errorMessage] || ""),
|
246
|
+
]
|
247
|
+
if opt[:referral]
|
248
|
+
rs = opt[:referral].collect { |r| OpenSSL::ASN1::OctetString(r) }
|
249
|
+
protocolOp << OpenSSL::ASN1::Sequence(rs, 3, :IMPLICIT, :APPLICATION)
|
250
|
+
end
|
251
|
+
if opt[:responseName]
|
252
|
+
protocolOp << OpenSSL::ASN1::OctetString(opt[:responseName], 10, :IMPLICIT, :APPLICATION)
|
253
|
+
end
|
254
|
+
if opt[:response]
|
255
|
+
protocolOp << OpenSSL::ASN1::OctetString(opt[:response], 11, :IMPLICIT, :APPLICATION)
|
256
|
+
end
|
257
|
+
message = [
|
258
|
+
OpenSSL::ASN1::Integer(0),
|
259
|
+
OpenSSL::ASN1::Sequence(protocolOp, 24, :IMPLICIT, :APPLICATION),
|
260
|
+
]
|
261
|
+
message << opt[:controls] if opt[:controls]
|
262
|
+
write(OpenSSL::ASN1::Sequence(message).to_der)
|
263
|
+
end
|
264
|
+
|
265
|
+
def send_notice_of_disconnection(resultCode, errorMessage="")
|
266
|
+
send_unsolicited_notification(resultCode,
|
267
|
+
:errorMessage=>errorMessage,
|
268
|
+
:responseName=>"1.3.6.1.4.1.1466.20036"
|
269
|
+
)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end # class Server
|
273
|
+
end # module LDAP
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'ldap/server/result'
|
2
|
+
require 'ldap/server/match'
|
3
|
+
|
4
|
+
module LDAP
|
5
|
+
class Server
|
6
|
+
|
7
|
+
# LDAP filters are parsed into a LISP-like internal representation:
|
8
|
+
#
|
9
|
+
# [:true]
|
10
|
+
# [:false]
|
11
|
+
# [:undef]
|
12
|
+
# [:and, ..., ..., ...]
|
13
|
+
# [:or, ..., ..., ...]
|
14
|
+
# [:not, ...]
|
15
|
+
# [:present, attr]
|
16
|
+
# [:eq, attr, MO, val]
|
17
|
+
# [:approx, attr, MO, val]
|
18
|
+
# [:substrings, attr, MO, initial=nil, {any, any...}, final=nil]
|
19
|
+
# [:ge, attr, MO, val]
|
20
|
+
# [:le, attr, MO, val]
|
21
|
+
#
|
22
|
+
# This is done rather than a more object-oriented approach, in the
|
23
|
+
# hope that it will make it easier to match certain filter structures
|
24
|
+
# when converting them into something else. e.g. certain LDAP filter
|
25
|
+
# constructs can be mapped to some fixed SQL queries.
|
26
|
+
#
|
27
|
+
# See RFC 2251 4.5.1 for the three-state(!) boolean logic from LDAP
|
28
|
+
#
|
29
|
+
# If no schema is provided: 'attr' is the raw attribute name as provided
|
30
|
+
# by the client. If a schema is provided: attr is converted to its
|
31
|
+
# normalized name as listed in the schema, e.g. 'commonname' becomes 'cn',
|
32
|
+
# 'objectclass' becomes 'objectClass' etc.
|
33
|
+
# If a schema is provided, MO is a matching object which can be used to
|
34
|
+
# perform the match. If no schema is provided, this is 'nil'. In that
|
35
|
+
# case you could use LDAP::Server::MatchingRule::DefaultMatch.
|
36
|
+
|
37
|
+
class Filter
|
38
|
+
|
39
|
+
# Parse a filter in OpenSSL::ASN1 format into our own format.
|
40
|
+
#
|
41
|
+
# There are some trivial optimisations we make: e.g.
|
42
|
+
# (&(objectClass=*)(cn=foo)) -> (&(cn=foo)) -> (cn=foo)
|
43
|
+
|
44
|
+
def self.parse(asn1, schema=nil)
|
45
|
+
case asn1.tag
|
46
|
+
when 0 # and
|
47
|
+
conds = asn1.value.collect { |a| parse(a) }
|
48
|
+
conds.delete([:true])
|
49
|
+
return [:true] if conds.size == 0
|
50
|
+
return conds.first if conds.size == 1
|
51
|
+
return [:false] if conds.include?([:false])
|
52
|
+
return conds.unshift(:and)
|
53
|
+
|
54
|
+
when 1 # or
|
55
|
+
conds = asn1.value.collect { |a| parse(a) }
|
56
|
+
conds.delete([:false])
|
57
|
+
return [:false] if conds.size == 0
|
58
|
+
return conds.first if conds.size == 1
|
59
|
+
return [:true] if conds.include?([:true])
|
60
|
+
return conds.unshift(:or)
|
61
|
+
|
62
|
+
when 2 # not
|
63
|
+
cond = parse(asn1.value[0])
|
64
|
+
case cond
|
65
|
+
when [:false]; return [:true]
|
66
|
+
when [:true]; return [:false]
|
67
|
+
when [:undef]; return [:undef]
|
68
|
+
end
|
69
|
+
return [:not, cond]
|
70
|
+
|
71
|
+
when 3 # equalityMatch
|
72
|
+
attr = asn1.value[0].value
|
73
|
+
val = asn1.value[1].value
|
74
|
+
return [:true] if attr =~ /\AobjectClass\z/i and val =~ /\Atop\z/i
|
75
|
+
if schema
|
76
|
+
a = schema.find_attrtype(attr)
|
77
|
+
return [:undef] unless a.equality
|
78
|
+
return [:eq, a.to_s, a.equality, val]
|
79
|
+
end
|
80
|
+
return [:eq, attr, nil, val]
|
81
|
+
|
82
|
+
when 4 # substrings
|
83
|
+
attr = asn1.value[0].value
|
84
|
+
if schema
|
85
|
+
a = schema.find_attrtype(attr)
|
86
|
+
return [:undef] unless a.substr
|
87
|
+
res = [:substrings, a.to_s, a.substr, nil]
|
88
|
+
else
|
89
|
+
res = [:substrings, attr, nil, nil]
|
90
|
+
end
|
91
|
+
final_val = nil
|
92
|
+
|
93
|
+
asn1.value[1].value.each do |ss|
|
94
|
+
case ss.tag
|
95
|
+
when 0
|
96
|
+
res[3] = ss.value
|
97
|
+
when 1
|
98
|
+
res << ss.value
|
99
|
+
when 2
|
100
|
+
final_val = ss.value
|
101
|
+
else
|
102
|
+
raise LDAP::ResultError::ProtocolError,
|
103
|
+
"Unrecognised substring tag #{ss.tag.inspect}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
res << final_val
|
107
|
+
return res
|
108
|
+
|
109
|
+
when 5 # greaterOrEqual
|
110
|
+
attr = asn1.value[0].value
|
111
|
+
val = asn1.value[1].value
|
112
|
+
if schema
|
113
|
+
a = schema.find_attrtype(attr)
|
114
|
+
return [:undef] unless a.ordering
|
115
|
+
return [:ge, a.to_s, a.ordering, val]
|
116
|
+
end
|
117
|
+
return [:ge, attr, nil, val]
|
118
|
+
|
119
|
+
when 6 # lessOrEqual
|
120
|
+
attr = asn1.value[0].value
|
121
|
+
val = asn1.value[1].value
|
122
|
+
if schema
|
123
|
+
a = schema.find_attrtype(attr)
|
124
|
+
return [:undef] unless a.ordering
|
125
|
+
return [:le, a.to_s, a.ordering, val]
|
126
|
+
end
|
127
|
+
return [:le, attr, nil, val]
|
128
|
+
|
129
|
+
when 7 # present
|
130
|
+
attr = asn1.value
|
131
|
+
return [:true] if attr =~ /\AobjectClass\z/i
|
132
|
+
if schema
|
133
|
+
begin
|
134
|
+
a = schema.find_attrtype(attr)
|
135
|
+
return [:present, a.to_s]
|
136
|
+
rescue LDAP::ResultError::UndefinedAttributeType
|
137
|
+
return [:false]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
return [:present, attr]
|
141
|
+
|
142
|
+
when 8 # approxMatch
|
143
|
+
attr = asn1.value[0].value
|
144
|
+
val = asn1.value[1].value
|
145
|
+
if schema
|
146
|
+
a = schema.find_attrtype(attr)
|
147
|
+
# I don't know how properly to deal with approxMatch. I'm assuming
|
148
|
+
# that the object will have an equality MatchingRule, and we
|
149
|
+
# can defer to that.
|
150
|
+
return [:undef] unless a.equality
|
151
|
+
return [:approx, a.to_s, a.equality, val]
|
152
|
+
end
|
153
|
+
return [:approx, attr, nil, val]
|
154
|
+
|
155
|
+
#when 9 # extensibleMatch
|
156
|
+
# FIXME
|
157
|
+
|
158
|
+
else
|
159
|
+
raise LDAP::ResultError::ProtocolError,
|
160
|
+
"Unrecognised Filter tag #{asn1.tag}"
|
161
|
+
end
|
162
|
+
|
163
|
+
# Unknown attribute type
|
164
|
+
rescue LDAP::ResultError::UndefinedAttributeType
|
165
|
+
return [:undef]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Run a parsed filter against an attr=>[val] hash.
|
169
|
+
#
|
170
|
+
# Returns true, false or nil.
|
171
|
+
|
172
|
+
def self.run(filter, av)
|
173
|
+
case filter[0]
|
174
|
+
when :and
|
175
|
+
res = true
|
176
|
+
filter[1..-1].each do |elem|
|
177
|
+
r = run(elem, av)
|
178
|
+
return false if r == false
|
179
|
+
res = nil if r.nil?
|
180
|
+
end
|
181
|
+
return res
|
182
|
+
|
183
|
+
when :or
|
184
|
+
res = false
|
185
|
+
filter[1..-1].each do |elem|
|
186
|
+
r = run(elem, av)
|
187
|
+
return true if r == true
|
188
|
+
res = nil if r.nil?
|
189
|
+
end
|
190
|
+
return res
|
191
|
+
|
192
|
+
when :not
|
193
|
+
case run(filter[1], av)
|
194
|
+
when true; return false
|
195
|
+
when false; return true
|
196
|
+
else return nil
|
197
|
+
end
|
198
|
+
|
199
|
+
when :present
|
200
|
+
return av.has_key?(filter[1])
|
201
|
+
|
202
|
+
when :eq, :approx, :le, :ge, :substrings
|
203
|
+
# the filter now includes a suitable matching object
|
204
|
+
return (filter[2] || LDAP::Server::MatchingRule::DefaultMatch).send(
|
205
|
+
filter.first, av[filter[1].to_s], *filter[3..-1])
|
206
|
+
|
207
|
+
when :true
|
208
|
+
return true
|
209
|
+
|
210
|
+
when :false
|
211
|
+
return false
|
212
|
+
|
213
|
+
when :undef
|
214
|
+
return nil
|
215
|
+
end
|
216
|
+
|
217
|
+
raise LDAP::ResultError::OperationsError,
|
218
|
+
"Unimplemented filter #{filter.first.inspect}"
|
219
|
+
end
|
220
|
+
|
221
|
+
end # class Filter
|
222
|
+
end # class Server
|
223
|
+
end # module LDAP
|