deveo-ruby-ldapserver 0.5.2
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/COPYING +27 -0
- data/ChangeLog +83 -0
- data/Gemfile +4 -0
- data/README +222 -0
- data/Rakefile +1 -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/rbslapd2.sql +11 -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 +251 -0
- data/lib/ldap/server/filter.rb +223 -0
- data/lib/ldap/server/match.rb +283 -0
- data/lib/ldap/server/operation.rb +492 -0
- data/lib/ldap/server/preforkserver.rb +92 -0
- data/lib/ldap/server/result.rb +71 -0
- data/lib/ldap/server/schema.rb +592 -0
- data/lib/ldap/server/server.rb +104 -0
- data/lib/ldap/server/syntax.rb +235 -0
- data/lib/ldap/server/tcpserver.rb +89 -0
- data/lib/ldap/server/util.rb +88 -0
- data/lib/ldap/server/version.rb +5 -0
- data/ruby-ldapserver.gemspec +22 -0
- data/test/core.schema +582 -0
- data/test/encoding_test.rb +323 -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 +47 -0
- metadata +117 -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,251 @@
|
|
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
|
+
@threadgroup = ThreadGroup.new
|
23
|
+
@binddn = nil
|
24
|
+
@version = 3
|
25
|
+
@logger = @opt[:logger]
|
26
|
+
@ssl = false
|
27
|
+
|
28
|
+
startssl if @opt[:ssl_on_connect]
|
29
|
+
end
|
30
|
+
|
31
|
+
def log(msg, severity = Logger::INFO)
|
32
|
+
@logger.add(severity, msg, @io.peeraddr[3])
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug msg
|
36
|
+
log msg, Logger::DEBUG
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_exception(e)
|
40
|
+
log "#{e}: #{e.backtrace.join("\n\tfrom ")}", Logger::ERROR
|
41
|
+
end
|
42
|
+
|
43
|
+
def startssl # :yields:
|
44
|
+
@mutex.synchronize do
|
45
|
+
raise LDAP::ResultError::OperationsError if @ssl or @threadgroup.list.size > 0
|
46
|
+
yield if block_given?
|
47
|
+
@io = OpenSSL::SSL::SSLSocket.new(@io, @opt[:ssl_ctx])
|
48
|
+
@io.sync_close = true
|
49
|
+
@io.accept
|
50
|
+
@ssl = true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Read one ASN1 element from the given stream.
|
55
|
+
# Return String containing the raw element.
|
56
|
+
|
57
|
+
def ber_read(io)
|
58
|
+
blk = io.read(2) # minimum: short tag, short length
|
59
|
+
throw(:close) if blk.nil?
|
60
|
+
|
61
|
+
codepoints = blk.respond_to?(:codepoints) ? blk.codepoints.to_a : blk
|
62
|
+
|
63
|
+
tag = codepoints[0] & 0x1f
|
64
|
+
len = codepoints[1]
|
65
|
+
|
66
|
+
if tag == 0x1f # long form
|
67
|
+
tag = 0
|
68
|
+
while true
|
69
|
+
ch = io.getc
|
70
|
+
blk << ch
|
71
|
+
tag = (tag << 7) | (ch & 0x7f)
|
72
|
+
break if (ch & 0x80) == 0
|
73
|
+
end
|
74
|
+
len = io.getc
|
75
|
+
blk << len
|
76
|
+
end
|
77
|
+
|
78
|
+
if (len & 0x80) != 0 # long form
|
79
|
+
len = len & 0x7f
|
80
|
+
raise LDAP::ResultError::ProtocolError, "Indefinite length encoding not supported" if len == 0
|
81
|
+
offset = blk.length
|
82
|
+
blk << io.read(len)
|
83
|
+
# is there a more efficient way of doing this?
|
84
|
+
len = 0
|
85
|
+
blk[offset..-1].each_byte { |b| len = (len << 8) | b }
|
86
|
+
end
|
87
|
+
|
88
|
+
offset = blk.length
|
89
|
+
blk << io.read(len)
|
90
|
+
return blk
|
91
|
+
# or if we wanted to keep the partial decoding we've done:
|
92
|
+
# return blk, [blk[0] >> 6, tag], offset
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_requests
|
96
|
+
operationClass = @opt[:operation_class]
|
97
|
+
ocArgs = @opt[:operation_args] || []
|
98
|
+
catch(:close) do
|
99
|
+
while true
|
100
|
+
begin
|
101
|
+
blk = ber_read(@io)
|
102
|
+
asn1 = OpenSSL::ASN1::decode(blk)
|
103
|
+
# Debugging:
|
104
|
+
# puts "Request: #{blk.unpack("H*")}\n#{asn1.inspect}" if $debug
|
105
|
+
|
106
|
+
raise LDAP::ResultError::ProtocolError, "LDAPMessage must be SEQUENCE" unless asn1.is_a?(OpenSSL::ASN1::Sequence)
|
107
|
+
raise LDAP::ResultError::ProtocolError, "Bad Message ID" unless asn1.value[0].is_a?(OpenSSL::ASN1::Integer)
|
108
|
+
messageId = asn1.value[0].value
|
109
|
+
|
110
|
+
protocolOp = asn1.value[1]
|
111
|
+
raise LDAP::ResultError::ProtocolError, "Bad protocolOp" unless protocolOp.is_a?(OpenSSL::ASN1::ASN1Data)
|
112
|
+
raise LDAP::ResultError::ProtocolError, "Bad protocolOp tag class" unless protocolOp.tag_class == :APPLICATION
|
113
|
+
|
114
|
+
# controls are not properly implemented
|
115
|
+
c = asn1.value[2]
|
116
|
+
if c.is_a?(OpenSSL::ASN1::ASN1Data) and c.tag_class == :APPLICATION and c.tag == 0
|
117
|
+
controls = c.value
|
118
|
+
end
|
119
|
+
|
120
|
+
case protocolOp.tag
|
121
|
+
when 0 # BindRequest
|
122
|
+
abandon_all
|
123
|
+
@binddn, @version = operationClass.new(self,messageId,*ocArgs).
|
124
|
+
do_bind(protocolOp, controls)
|
125
|
+
|
126
|
+
when 2 # UnbindRequest
|
127
|
+
throw(:close)
|
128
|
+
|
129
|
+
when 3 # SearchRequest
|
130
|
+
start_op(messageId,protocolOp,controls,:do_search)
|
131
|
+
|
132
|
+
when 6 # ModifyRequest
|
133
|
+
start_op(messageId,protocolOp,controls,:do_modify)
|
134
|
+
|
135
|
+
when 8 # AddRequest
|
136
|
+
start_op(messageId,protocolOp,controls,:do_add)
|
137
|
+
|
138
|
+
when 10 # DelRequest
|
139
|
+
start_op(messageId,protocolOp,controls,:do_del)
|
140
|
+
|
141
|
+
when 12 # ModifyDNRequest
|
142
|
+
start_op(messageId,protocolOp,controls,:do_modifydn)
|
143
|
+
|
144
|
+
when 14 # CompareRequest
|
145
|
+
start_op(messageId,protocolOp,controls,:do_compare)
|
146
|
+
|
147
|
+
when 16 # AbandonRequest
|
148
|
+
abandon(messageId)
|
149
|
+
|
150
|
+
else
|
151
|
+
raise LDAP::ResultError::ProtocolError, "Unrecognised protocolOp tag #{protocolOp.tag}"
|
152
|
+
end
|
153
|
+
|
154
|
+
rescue LDAP::ResultError::ProtocolError, OpenSSL::ASN1::ASN1Error => e
|
155
|
+
send_notice_of_disconnection(LDAP::ResultError::ProtocolError.new.to_i, e.message)
|
156
|
+
throw(:close)
|
157
|
+
|
158
|
+
# all other exceptions propagate up and are caught by tcpserver
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
abandon_all
|
163
|
+
end
|
164
|
+
|
165
|
+
# Start an operation in a Thread. Add this to a ThreadGroup to allow
|
166
|
+
# the operation to be abandoned later.
|
167
|
+
#
|
168
|
+
# When the thread terminates, it automatically drops out of the group.
|
169
|
+
#
|
170
|
+
# Note: RFC 2251 4.4.4.1 says behaviour is undefined if
|
171
|
+
# client sends an overlapping request with same message ID,
|
172
|
+
# so we don't have to worry about the case where there is
|
173
|
+
# already a thread with this messageId in @threadgroup.
|
174
|
+
|
175
|
+
def start_op(messageId,protocolOp,controls,meth)
|
176
|
+
operationClass = @opt[:operation_class]
|
177
|
+
ocArgs = @opt[:operation_args] || []
|
178
|
+
thr = Thread.new do
|
179
|
+
begin
|
180
|
+
operationClass.new(self,messageId,*ocArgs).
|
181
|
+
send(meth,protocolOp,controls)
|
182
|
+
rescue Exception => e
|
183
|
+
log_exception e
|
184
|
+
end
|
185
|
+
end
|
186
|
+
thr[:messageId] = messageId
|
187
|
+
@threadgroup.add(thr)
|
188
|
+
end
|
189
|
+
|
190
|
+
def write(data)
|
191
|
+
@mutex.synchronize do
|
192
|
+
@io.write(data)
|
193
|
+
@io.flush
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def writelock
|
198
|
+
@mutex.synchronize do
|
199
|
+
yield @io
|
200
|
+
@io.flush
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def abandon(messageID)
|
205
|
+
@mutex.synchronize do
|
206
|
+
thread = @threadgroup.list.find { |t| t[:messageId] == messageID }
|
207
|
+
thread.raise LDAP::Abandon if thread
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def abandon_all
|
212
|
+
@mutex.synchronize do
|
213
|
+
@threadgroup.list.each do |thread|
|
214
|
+
thread.raise LDAP::Abandon
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def send_unsolicited_notification(resultCode, opt={})
|
220
|
+
protocolOp = [
|
221
|
+
OpenSSL::ASN1::Enumerated(resultCode),
|
222
|
+
OpenSSL::ASN1::OctetString(opt[:matchedDN] || ""),
|
223
|
+
OpenSSL::ASN1::OctetString(opt[:errorMessage] || ""),
|
224
|
+
]
|
225
|
+
if opt[:referral]
|
226
|
+
rs = opt[:referral].collect { |r| OpenSSL::ASN1::OctetString(r) }
|
227
|
+
protocolOp << OpenSSL::ASN1::Sequence(rs, 3, :IMPLICIT, :APPLICATION)
|
228
|
+
end
|
229
|
+
if opt[:responseName]
|
230
|
+
protocolOp << OpenSSL::ASN1::OctetString(opt[:responseName], 10, :IMPLICIT, :APPLICATION)
|
231
|
+
end
|
232
|
+
if opt[:response]
|
233
|
+
protocolOp << OpenSSL::ASN1::OctetString(opt[:response], 11, :IMPLICIT, :APPLICATION)
|
234
|
+
end
|
235
|
+
message = [
|
236
|
+
OpenSSL::ASN1::Integer(0),
|
237
|
+
OpenSSL::ASN1::Sequence(protocolOp, 24, :IMPLICIT, :APPLICATION),
|
238
|
+
]
|
239
|
+
message << opt[:controls] if opt[:controls]
|
240
|
+
write(OpenSSL::ASN1::Sequence(message).to_der)
|
241
|
+
end
|
242
|
+
|
243
|
+
def send_notice_of_disconnection(resultCode, errorMessage="")
|
244
|
+
send_unsolicited_notification(resultCode,
|
245
|
+
:errorMessage=>errorMessage,
|
246
|
+
:responseName=>"1.3.6.1.4.1.1466.20036"
|
247
|
+
)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end # class Server
|
251
|
+
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
|