fakeldap 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +3 -0
- data/lib/fakeldap.rb +46 -0
- data/lib/fakeldap/version.rb +4 -0
- data/vendor/ruby-ldapserver/COPYING +27 -0
- data/vendor/ruby-ldapserver/ChangeLog +83 -0
- data/vendor/ruby-ldapserver/Manifest.txt +32 -0
- data/vendor/ruby-ldapserver/README +222 -0
- data/vendor/ruby-ldapserver/Rakefile +22 -0
- data/vendor/ruby-ldapserver/examples/README +89 -0
- data/vendor/ruby-ldapserver/examples/mkcert.rb +31 -0
- data/vendor/ruby-ldapserver/examples/rbslapd1.rb +111 -0
- data/vendor/ruby-ldapserver/examples/rbslapd2.rb +161 -0
- data/vendor/ruby-ldapserver/examples/rbslapd3.rb +172 -0
- data/vendor/ruby-ldapserver/examples/speedtest.rb +37 -0
- data/vendor/ruby-ldapserver/lib/ldap/server.rb +4 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/connection.rb +276 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/filter.rb +223 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/match.rb +283 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/operation.rb +487 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/preforkserver.rb +93 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/result.rb +71 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/schema.rb +592 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/server.rb +89 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/syntax.rb +235 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/tcpserver.rb +91 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/util.rb +88 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/version.rb +11 -0
- data/vendor/ruby-ldapserver/test/core.schema +582 -0
- data/vendor/ruby-ldapserver/test/encoding_test.rb +279 -0
- data/vendor/ruby-ldapserver/test/filter_test.rb +107 -0
- data/vendor/ruby-ldapserver/test/match_test.rb +59 -0
- data/vendor/ruby-ldapserver/test/schema_test.rb +113 -0
- data/vendor/ruby-ldapserver/test/syntax_test.rb +40 -0
- data/vendor/ruby-ldapserver/test/test_helper.rb +2 -0
- data/vendor/ruby-ldapserver/test/util_test.rb +51 -0
- metadata +130 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'ldap/server/connection'
|
2
|
+
require 'ldap/server/operation'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module LDAP
|
6
|
+
class Server
|
7
|
+
|
8
|
+
attr_accessor :root_dse
|
9
|
+
|
10
|
+
DEFAULT_OPT = {
|
11
|
+
:port=>389,
|
12
|
+
:nodelay=>true,
|
13
|
+
}
|
14
|
+
|
15
|
+
# Create a new server. Options include all those to tcpserver/preforkserver
|
16
|
+
# plus:
|
17
|
+
# :operation_class=>Class - set Operation handler class
|
18
|
+
# :operation_args=>[...] - args to Operation.new
|
19
|
+
# :ssl_key_file=>pem, :ssl_cert_file=>pem - enable SSL
|
20
|
+
# :ssl_ca_path=>directory - verify peer certificates
|
21
|
+
# :schema=>Schema - Schema object
|
22
|
+
# :namingContexts=>[dn, ...] - base DN(s) we answer
|
23
|
+
|
24
|
+
def initialize(opt = DEFAULT_OPT)
|
25
|
+
@opt = opt
|
26
|
+
@opt[:server] = self
|
27
|
+
@opt[:operation_class] ||= LDAP::Server::Operation
|
28
|
+
@opt[:operation_args] ||= []
|
29
|
+
LDAP::Server.ssl_prepare(@opt)
|
30
|
+
@schema = opt[:schema] # may be nil
|
31
|
+
@root_dse = Hash.new { |h,k| h[k] = [] }.merge({
|
32
|
+
'objectClass' => ['top','openLDAProotDSE','extensibleObject'],
|
33
|
+
'supportedLDAPVersion' => ['3'],
|
34
|
+
#'altServer' =>
|
35
|
+
#'supportedExtension' =>
|
36
|
+
#'supportedControl' =>
|
37
|
+
#'supportedSASLMechanisms' =>
|
38
|
+
})
|
39
|
+
@root_dse['subschemaSubentry'] = [@schema.subschema_dn] if @schema
|
40
|
+
@root_dse['namingContexts'] = opt[:namingContexts] if opt[:namingContexts]
|
41
|
+
end
|
42
|
+
|
43
|
+
# create opt[:ssl_ctx] from the other ssl options
|
44
|
+
|
45
|
+
def self.ssl_prepare(opt) # :nodoc:
|
46
|
+
if opt[:ssl_key_file] and opt[:ssl_cert_file]
|
47
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
48
|
+
ctx.key = OpenSSL::PKey::RSA.new(File::read(opt[:ssl_key_file]))
|
49
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File::read(opt[:ssl_cert_file]))
|
50
|
+
if opt[:ssl_ca_path]
|
51
|
+
ctx.ca_path = opt[:ssl_ca_path]
|
52
|
+
ctx.verify_mode =
|
53
|
+
OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
54
|
+
else
|
55
|
+
$stderr.puts "Warning: SSL peer certificate won't be verified"
|
56
|
+
end
|
57
|
+
opt[:ssl_ctx] = ctx
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def run_tcpserver
|
62
|
+
require 'ldap/server/tcpserver'
|
63
|
+
|
64
|
+
opt = @opt
|
65
|
+
@thread = LDAP::Server.tcpserver(@opt) do
|
66
|
+
LDAP::Server::Connection::new(self,opt).handle_requests
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_prefork
|
71
|
+
require 'ldap/server/preforkserver'
|
72
|
+
|
73
|
+
opt = @opt
|
74
|
+
@thread = LDAP::Server.preforkserver(@opt) do
|
75
|
+
LDAP::Server::Connection::new(self,opt).handle_requests
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def join
|
80
|
+
@thread.join
|
81
|
+
end
|
82
|
+
|
83
|
+
def stop
|
84
|
+
@thread.raise Interrupt, "" # <= temporary fix for 1.8.6
|
85
|
+
@thread.join
|
86
|
+
end
|
87
|
+
|
88
|
+
end # class Server
|
89
|
+
end # module LDAP
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module LDAP
|
2
|
+
class Server
|
3
|
+
|
4
|
+
# A class which describes LDAP SyntaxDescriptions. For now there is
|
5
|
+
# a global pool of Syntax objects (rather than each Schema object
|
6
|
+
# having its own set)
|
7
|
+
|
8
|
+
class Syntax
|
9
|
+
attr_reader :oid, :nhr, :binary, :desc
|
10
|
+
|
11
|
+
# Create a new Syntax object
|
12
|
+
|
13
|
+
def initialize(oid, desc=nil, opt={}, &blk)
|
14
|
+
@oid = oid
|
15
|
+
@desc = desc
|
16
|
+
@nhr = opt[:nhr] # not human-readable?
|
17
|
+
@binary = opt[:binary] # binary encoding forced?
|
18
|
+
@re = opt[:re] # regular expression for parsing
|
19
|
+
@def = nil
|
20
|
+
instance_eval(&blk) if blk
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
@oid
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create a new Syntax object, given its description string
|
28
|
+
|
29
|
+
def self.from_def(str, &blk)
|
30
|
+
m = LDAPSyntaxDescription.match(str)
|
31
|
+
raise LDAP::ResultError::InvalidAttributeSyntax,
|
32
|
+
"Bad SyntaxTypeDescription #{str.inspect}" unless m
|
33
|
+
new(m[1], m[2], :nhr=>(m[3] == 'TRUE'), :binary=>(m[4] == 'TRUE'), &blk)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert this object to its description string
|
37
|
+
|
38
|
+
def to_def
|
39
|
+
return @def if @def
|
40
|
+
ans = "( #@oid "
|
41
|
+
ans << "DESC '#@desc' " if @desc
|
42
|
+
# These are OpenLDAP extensions
|
43
|
+
ans << "X-BINARY-TRANSFER-REQUIRED 'TRUE' " if @binary
|
44
|
+
ans << "X-NOT-HUMAN-READABLE 'TRUE' " if @nhr
|
45
|
+
ans << ")"
|
46
|
+
@def = ans
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return true or a MatchData object if the given value is allowed
|
50
|
+
# by this syntax
|
51
|
+
|
52
|
+
def match(val)
|
53
|
+
return true if @re.nil?
|
54
|
+
@re.match(value_to_s(val))
|
55
|
+
end
|
56
|
+
|
57
|
+
# Convert a value for this syntax into its canonical string representation
|
58
|
+
# (not yet used, but seemed like a good idea)
|
59
|
+
|
60
|
+
def value_to_s(val)
|
61
|
+
val.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
# Convert a string value for this syntax into a Ruby-like value
|
65
|
+
# (not yet used, but seemed like a good idea)
|
66
|
+
|
67
|
+
def value_from_s(val)
|
68
|
+
val
|
69
|
+
end
|
70
|
+
|
71
|
+
@@syntaxes = {}
|
72
|
+
|
73
|
+
# Add a new syntax definition
|
74
|
+
|
75
|
+
def self.add(*args, &blk)
|
76
|
+
s = new(*args, &blk)
|
77
|
+
@@syntaxes[s.oid] = s
|
78
|
+
end
|
79
|
+
|
80
|
+
# Find a Syntax object given an oid. If not known, return a new empty
|
81
|
+
# Syntax object associated with this oid.
|
82
|
+
|
83
|
+
def self.find(oid)
|
84
|
+
return oid if oid.nil? or oid.is_a?(LDAP::Server::Syntax)
|
85
|
+
return @@syntaxes[oid] if @@syntaxes[oid]
|
86
|
+
add(oid)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Return all known syntax objects
|
90
|
+
|
91
|
+
def self.all_syntaxes
|
92
|
+
@@syntaxes.values.uniq
|
93
|
+
end
|
94
|
+
|
95
|
+
# Shared constants for regexp-based syntax parsers
|
96
|
+
|
97
|
+
KEYSTR = "[a-zA-Z][a-zA-Z0-9;-]*"
|
98
|
+
NUMERICOID = "( \\d[\\d.]+\\d )"
|
99
|
+
WOID = "\\s* ( #{KEYSTR} | \\d[\\d.]+\\d ) \\s*"
|
100
|
+
_WOID = "\\s* (?: #{KEYSTR} | \\d[\\d.]+\\d ) \\s*"
|
101
|
+
OIDS = "( #{_WOID} | \\s* \\( #{_WOID} (?: \\$ #{_WOID} )* \\) \\s* )"
|
102
|
+
_QDESCR = "\\s* ' #{KEYSTR} ' \\s*"
|
103
|
+
QDESCRS = "( #{_QDESCR} | \\s* \\( (?:#{_QDESCR})+ \\) \\s* )"
|
104
|
+
QDSTRING = "\\s* ' (.*?) ' \\s*"
|
105
|
+
NOIDLEN = "(\\d[\\d.]+\\d) (?: \\{ (\\d+) \\} )?"
|
106
|
+
ATTRIBUTEUSAGE = "(userApplications|directoryOperation|distributedOperation|dSAOperation)"
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
class Syntax
|
111
|
+
|
112
|
+
# These are the 'SHOULD' support syntaxes from RFC2252 section 6
|
113
|
+
|
114
|
+
AttributeTypeDescription =
|
115
|
+
add("1.3.6.1.4.1.1466.115.121.1.3", "Attribute Type Description", :re=>
|
116
|
+
%r! \A \s* \( \s*
|
117
|
+
#{NUMERICOID} \s*
|
118
|
+
(?: NAME #{QDESCRS} )?
|
119
|
+
(?: DESC #{QDSTRING} )?
|
120
|
+
( OBSOLETE \s* )?
|
121
|
+
(?: SUP #{WOID} )?
|
122
|
+
(?: EQUALITY #{WOID} )?
|
123
|
+
(?: ORDERING #{WOID} )?
|
124
|
+
(?: SUBSTR #{WOID} )?
|
125
|
+
(?: SYNTAX \s* #{NOIDLEN} \s* )? # capture 2
|
126
|
+
( SINGLE-VALUE \s* )?
|
127
|
+
( COLLECTIVE \s* )?
|
128
|
+
( NO-USER-MODIFICATION \s* )?
|
129
|
+
(?: USAGE \s* #{ATTRIBUTEUSAGE} )?
|
130
|
+
\s* \) \s* \z !xu)
|
131
|
+
|
132
|
+
add("1.3.6.1.4.1.1466.115.121.1.5", "Binary", :nhr=>true)
|
133
|
+
# FIXME: value_to_s should BER-encode the value??
|
134
|
+
|
135
|
+
add("1.3.6.1.4.1.1466.115.121.1.6", "Bit String", :re=>/\A'([01]*)'B\z/)
|
136
|
+
# FIXME: convert to FixNum?
|
137
|
+
|
138
|
+
add("1.3.6.1.4.1.1466.115.121.1.7", "Boolean", :re=>/\A(TRUE|FALSE)\z/) do
|
139
|
+
def self.value_to_s(v)
|
140
|
+
return v if v.is_a?(string)
|
141
|
+
v ? "TRUE" : "FALSE"
|
142
|
+
end
|
143
|
+
def self.value_from_s(v)
|
144
|
+
v.upcase == "TRUE"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
add("1.3.6.1.4.1.1466.115.121.1.8", "Certificate", :binary=>true, :nhr=>true)
|
149
|
+
add("1.3.6.1.4.1.1466.115.121.1.9", "Certificate List", :binary=>true, :nhr=>true)
|
150
|
+
add("1.3.6.1.4.1.1466.115.121.1.10", "Certificate Pair", :binary=>true, :nhr=>true)
|
151
|
+
add("1.3.6.1.4.1.1466.115.121.1.11", "Country String", :re=>/\A[A-Z]{2}\z/i)
|
152
|
+
add("1.3.6.1.4.1.1466.115.121.1.12", "Distinguished Name")
|
153
|
+
# FIXME: validate DN?
|
154
|
+
add("1.3.6.1.4.1.1466.115.121.1.15", "Directory String")
|
155
|
+
# missed due to lack of interest: "DIT Content Rule Description"
|
156
|
+
add("1.3.6.1.4.1.1466.115.121.1.22", "Facsimile Telephone Number")
|
157
|
+
add(" 1.3.6.1.4.1.1466.115.121.1.23", "Fax", :nhr=>true)
|
158
|
+
add("1.3.6.1.4.1.1466.115.121.1.24", "Generalized Time")
|
159
|
+
# FIXME: Validate Generalized Time (find X.208) and convert to/from Ruby Time
|
160
|
+
add("1.3.6.1.4.1.1466.115.121.1.26", "IA5 String")
|
161
|
+
add("1.3.6.1.4.1.1466.115.121.1.27", "Integer", :re=>/\A\d+\z/) do
|
162
|
+
def self.value_from_s(v)
|
163
|
+
v.to_i
|
164
|
+
end
|
165
|
+
end
|
166
|
+
add("1.3.6.1.4.1.1466.115.121.1.28", "JPEG", :nhr=>true)
|
167
|
+
MatchingRuleDescription =
|
168
|
+
add("1.3.6.1.4.1.1466.115.121.1.30", "Matching Rule Description", :re=>
|
169
|
+
%r! \A \s* \( \s*
|
170
|
+
#{NUMERICOID} \s*
|
171
|
+
(?: NAME #{QDESCRS} )?
|
172
|
+
(?: DESC #{QDSTRING} )?
|
173
|
+
( OBSOLETE \s* )?
|
174
|
+
SYNTAX \s* #{NUMERICOID} \s*
|
175
|
+
\s* \) \s* \z !xu)
|
176
|
+
MatchingRuleUseDescription =
|
177
|
+
add("1.3.6.1.4.1.1466.115.121.1.31", "Matching Rule Use Description", :re=>
|
178
|
+
%r! \A \s* \( \s*
|
179
|
+
#{NUMERICOID} \s*
|
180
|
+
(?: NAME #{QDESCRS} )?
|
181
|
+
(?: DESC #{QDSTRING} )?
|
182
|
+
( OBSOLETE \s* )?
|
183
|
+
APPLIES \s* #{OIDS} \s*
|
184
|
+
\s* \) \s* \z !xu)
|
185
|
+
add("1.3.6.1.4.1.1466.115.121.1.33", "MHS OR Address")
|
186
|
+
add("1.3.6.1.4.1.1466.115.121.1.34", "Name And Optional UID")
|
187
|
+
# missed due to lack of interest: "Name Form Description"
|
188
|
+
add("1.3.6.1.4.1.1466.115.121.1.36", "Numeric String", :re=>/\A\d+\z/)
|
189
|
+
ObjectClassDescription =
|
190
|
+
add("1.3.6.1.4.1.1466.115.121.1.37", "Object Class Description", :re=>
|
191
|
+
%r! \A \s* \( \s*
|
192
|
+
#{NUMERICOID} \s*
|
193
|
+
(?: NAME #{QDESCRS} )?
|
194
|
+
(?: DESC #{QDSTRING} )?
|
195
|
+
( OBSOLETE \s* )?
|
196
|
+
(?: SUP #{OIDS} )?
|
197
|
+
(?: ( ABSTRACT|STRUCTURAL|AUXILIARY ) \s* )?
|
198
|
+
(?: MUST #{OIDS} )?
|
199
|
+
(?: MAY #{OIDS} )?
|
200
|
+
\s* \) \s* \z !xu)
|
201
|
+
add("1.3.6.1.4.1.1466.115.121.1.38", "OID", :re=>/\A#{WOID}\z/xu)
|
202
|
+
add("1.3.6.1.4.1.1466.115.121.1.39", "Other Mailbox")
|
203
|
+
add("1.3.6.1.4.1.1466.115.121.1.41", "Postal Address") do
|
204
|
+
def self.value_from_s(v)
|
205
|
+
v.split(/\$/)
|
206
|
+
end
|
207
|
+
def self.value_to_s(v)
|
208
|
+
return v.join("$") if v.is_a?(Array)
|
209
|
+
return v
|
210
|
+
end
|
211
|
+
end
|
212
|
+
add("1.3.6.1.4.1.1466.115.121.1.43", "Presentation Address")
|
213
|
+
add("1.3.6.1.4.1.1466.115.121.1.44", "Printable String")
|
214
|
+
add("1.3.6.1.4.1.1466.115.121.1.50", "Telephone Number")
|
215
|
+
add("1.3.6.1.4.1.1466.115.121.1.53", "UTC Time")
|
216
|
+
|
217
|
+
LDAPSyntaxDescription =
|
218
|
+
add("1.3.6.1.4.1.1466.115.121.1.54", "LDAP Syntax Description", :re=>
|
219
|
+
%r! \A \s* \( \s*
|
220
|
+
#{NUMERICOID} \s*
|
221
|
+
(?: DESC #{QDSTRING} )?
|
222
|
+
(?: X-BINARY-TRANSFER-REQUIRED \s* ' (TRUE|FALSE) ' \s* )?
|
223
|
+
(?: X-NOT-HUMAN-READABLE \s* ' (TRUE|FALSE) ' \s* )?
|
224
|
+
\s* \) \s* \z !xu)
|
225
|
+
|
226
|
+
# Missed due to lack of interest: "DIT Structure Rule Description"
|
227
|
+
|
228
|
+
# A few others from RFC2252 section 4.3.2
|
229
|
+
add("1.3.6.1.4.1.1466.115.121.1.4", "Audio", :nhr=>true)
|
230
|
+
add("1.3.6.1.4.1.1466.115.121.1.40", "Octet String")
|
231
|
+
add("1.3.6.1.4.1.1466.115.121.1.58", "Substring Assertion")
|
232
|
+
end
|
233
|
+
|
234
|
+
end # class Server
|
235
|
+
end # module LDAP
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module LDAP
|
4
|
+
class Server
|
5
|
+
|
6
|
+
# Accept connections on a port, and for each one start a new thread
|
7
|
+
# and run the given block. Returns the Thread object for the listener.
|
8
|
+
#
|
9
|
+
# FIXME:
|
10
|
+
# - have a limit on total number of concurrent connects
|
11
|
+
# - have a limit on connections from a single IP, or from a /24
|
12
|
+
# (to avoid the trivial DoS that the first limit creates)
|
13
|
+
# - ACL using source IP address (or perhaps that belongs in application)
|
14
|
+
#
|
15
|
+
# Options:
|
16
|
+
# :port=>port number [required]
|
17
|
+
# :bindaddr=>"IP address"
|
18
|
+
# :user=>"username" - drop privileges after bind
|
19
|
+
# :group=>"groupname" - ditto
|
20
|
+
# :logger=>object - implements << method
|
21
|
+
# :listen=>number - listen queue depth
|
22
|
+
# :nodelay=>true - set TCP_NODELAY option
|
23
|
+
|
24
|
+
def self.tcpserver(opt, &blk)
|
25
|
+
logger = opt[:logger] || $stderr
|
26
|
+
server = TCPServer.new(opt[:bindaddr] || "0.0.0.0", opt[:port])
|
27
|
+
|
28
|
+
# Drop privileges if requested
|
29
|
+
require 'etc' if opt[:group] or opt[:user]
|
30
|
+
Process.gid = Process.egid = Etc.getgrnam(opt[:group]).gid if opt[:group]
|
31
|
+
Process.uid = Process.euid = Etc.getpwnam(opt[:user]).uid if opt[:user]
|
32
|
+
|
33
|
+
# Typically the O/S will buffer response data for 100ms before sending.
|
34
|
+
# If the response is sent as a single write() then there's no need for it.
|
35
|
+
if opt[:nodelay]
|
36
|
+
begin
|
37
|
+
server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
38
|
+
rescue Exception
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# set queue size for incoming connections (default is 5)
|
42
|
+
server.listen(opt[:listen]) if opt[:listen]
|
43
|
+
|
44
|
+
Thread.new do
|
45
|
+
while true
|
46
|
+
begin
|
47
|
+
session = server.accept
|
48
|
+
# subtlety: copy 'session' into a block-local variable because
|
49
|
+
# it will change when the next session is accepted
|
50
|
+
Thread.new(session) do |s|
|
51
|
+
begin
|
52
|
+
s.instance_eval(&blk)
|
53
|
+
rescue Exception => e
|
54
|
+
logger << "[#{s.peeraddr[3]}]: #{e}: #{e.backtrace[0]}\n"
|
55
|
+
#logger << "[#{s.peeraddr[3]}]: #{e}: #{e.backtrace.join("\n\tfrom ")}\n"
|
56
|
+
ensure
|
57
|
+
s.close
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rescue Interrupt
|
61
|
+
# This exception can be raised to shut the server down
|
62
|
+
server.close if server and not server.closed?
|
63
|
+
break
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end # class Server
|
70
|
+
end # module LDAP
|
71
|
+
|
72
|
+
if __FILE__ == $0
|
73
|
+
# simple test
|
74
|
+
puts "Running a test POP3 server on port 1110"
|
75
|
+
t = LDAP::Server.tcpserver(:port=>1110) do
|
76
|
+
print "+OK I am a fake POP3 server\r\n"
|
77
|
+
while line = gets
|
78
|
+
case line
|
79
|
+
when /^quit/i
|
80
|
+
break
|
81
|
+
when /^crash/i
|
82
|
+
raise Errno::EPERM, "dammit!"
|
83
|
+
else
|
84
|
+
print "-ERR I don't understand #{line}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
print "+OK bye\r\n"
|
88
|
+
end
|
89
|
+
#sleep 10; t.raise Interrupt # uncomment to run for fixed time period
|
90
|
+
t.join
|
91
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'ldap/server/result'
|
2
|
+
|
3
|
+
module LDAP
|
4
|
+
class Server
|
5
|
+
|
6
|
+
class Operation
|
7
|
+
|
8
|
+
# Return true if connection is not authenticated
|
9
|
+
|
10
|
+
def anonymous?
|
11
|
+
@connection.binddn.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
# Split dn string into its component parts, returning
|
15
|
+
# [ {attr=>val}, {attr=>val}, ... ]
|
16
|
+
#
|
17
|
+
# This is pretty horrible legacy stuff from X500; see RFC2253 for the
|
18
|
+
# full gore. It's stupid that the LDAP protocol sends the DN in string
|
19
|
+
# form, rather than in ASN1 form (as it does with search filters, for
|
20
|
+
# example), even though the DN syntax is defined in terms of ASN1!
|
21
|
+
#
|
22
|
+
# Attribute names are downcased, but values are not. For any
|
23
|
+
# case-insensitive attributes it's up to you to downcase them.
|
24
|
+
#
|
25
|
+
# Note that only v2 clients should add extra space around the comma.
|
26
|
+
# This is accepted, and so is semicolon instead of comma, but the
|
27
|
+
# full RFC1779 backwards-compatibility rules (e.g. quoted values)
|
28
|
+
# are not implemented.
|
29
|
+
#
|
30
|
+
# I *think* these functions will work correctly with UTF8-encoded
|
31
|
+
# characters, given that a multibyte UTF8 character does not contain
|
32
|
+
# the bytes 00-7F and therefore we cannot confuse '\', '+' etc
|
33
|
+
|
34
|
+
def self.split_dn(dn)
|
35
|
+
# convert \\ to \5c, \+ to \2b etc
|
36
|
+
dn2 = dn.gsub(/\\([ #,+"\\<>;])/) { "\\%02x" % $1[0] }
|
37
|
+
|
38
|
+
# Now we know that \\ and \, do not exist, it's safe to split
|
39
|
+
parts = dn2.split(/\s*[,;]\s*/)
|
40
|
+
|
41
|
+
parts.collect do |part|
|
42
|
+
res = {}
|
43
|
+
|
44
|
+
# Split each part into attr=val+attr=val
|
45
|
+
avs = part.split(/\+/)
|
46
|
+
|
47
|
+
avs.each do |av|
|
48
|
+
# These should all be of form attr=value
|
49
|
+
unless av =~ /^([^=]+)=(.*)$/
|
50
|
+
raise LDAP::ResultError::ProtocolError, "Bad DN component: #{av}"
|
51
|
+
end
|
52
|
+
attr, val = $1.downcase, $2
|
53
|
+
# Now we can decode those bits
|
54
|
+
attr.gsub!(/\\([a-f0-9][a-f0-9])/i) { $1.hex.chr }
|
55
|
+
val.gsub!(/\\([a-f0-9][a-f0-9])/i) { $1.hex.chr }
|
56
|
+
res[attr] = val
|
57
|
+
end
|
58
|
+
res
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Reverse of split_dn. Join [elements...]
|
63
|
+
# where each element can be {attr=>val,...} or [[attr,val],...]
|
64
|
+
# or just [attr,val]
|
65
|
+
|
66
|
+
def self.join_dn(elements)
|
67
|
+
dn = ""
|
68
|
+
elements.each do |elem|
|
69
|
+
av = ""
|
70
|
+
elem = [elem] if elem[0].is_a?(String)
|
71
|
+
elem.each do |attr,val|
|
72
|
+
av << "+" unless av == ""
|
73
|
+
|
74
|
+
av << attr << "=" <<
|
75
|
+
val.sub(/^([# ])/, '\\\\\\1').
|
76
|
+
sub(/( )$/, '\\\\\\1').
|
77
|
+
gsub(/([,+"\\<>;])/, '\\\\\\1')
|
78
|
+
end
|
79
|
+
dn << "," unless dn == ""
|
80
|
+
dn << av
|
81
|
+
end
|
82
|
+
dn
|
83
|
+
end
|
84
|
+
|
85
|
+
end # class Operation
|
86
|
+
|
87
|
+
end # class Server
|
88
|
+
end # module LDAP
|