ldumbd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Sebastian Boehm
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/NEWS ADDED
@@ -0,0 +1,8 @@
1
+ * ldumbd NEWS
2
+ ** 0.1.0 (2014-03-21)
3
+
4
+ - First release
5
+
6
+ # Local Variables:
7
+ # mode: org
8
+ # End:
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ ldumbd
2
+ ======
3
+
4
+ A simple, self-contained LDAP server with a database back end.
5
+
6
+ Documentation
7
+ -------------
8
+
9
+ Ldumbd is a simple, self-contained read-only LDAP server that uses PostgreSQL, MySQL/MariaDB or SQLite as a back end.
10
+
11
+ Ldumbd is designed primarily to act as an LDAP gateway to a simple SQL user database for use with the `nss-pam-ldapd` Name Service Switch (NSS) module.
12
+
13
+ Limitations
14
+ -----------
15
+
16
+ At the moment, ldumbd has no support for any of the following:
17
+
18
+ - LDAP schemas
19
+ - LDAP binds
20
+ - any request type other than search requests
21
+ - "approximately equal" operators in search filters
22
+
23
+ Installation: Debian Wheezy
24
+ ---------------------------
25
+
26
+ sudo -i
27
+ export LDUMBD_DIR=/var/lib/ldumbd
28
+ mkdir -p ${LDUMBD_DIR}
29
+ groupadd -r ldumbd
30
+ useradd -r -s /bin/false -g ldumbd -d ${LDUMBD_DIR} ldumbd
31
+ chown ldumbd:ldumbd ${LDUMBD_DIR}
32
+ chmod 700 ${LDUMBD_DIR}
33
+ gem install ldumbd
34
+ export MIGRATIONS=$(dirname $(gem contents ldumbd | grep migrations/001))
35
+
36
+ Database setup: SQLite
37
+ ----------------------
38
+
39
+ aptitude install libsqlite3-dev
40
+ gem install sqlite3
41
+ sudo -u ldumbd sequel -m ${MIGRATIONS} sqlite://${LDUMBD_DIR}/ldumbd.sqlite3
42
+
43
+ Database setup: PostgreSQL
44
+ --------------------------
45
+
46
+ aptitude install postgresql libpq-dev
47
+ sudo -u postgres createuser ldumbd
48
+ sudo -u postgres createdb -O ldumbd ldumbd
49
+ gem install pg
50
+ sudo -u ldumbd sequel -m ${MIGRATIONS} postgres:///ldumbd
51
+
52
+ Database setup: MySQL/MariaDB
53
+ -----------------------------
54
+
55
+ export DB_PASSWORD='secret'
56
+ aptitude install mysql-server libmysqlclient-dev
57
+ cat <<EOS | mysql -u root -p
58
+ CREATE DATABASE ldumbd;
59
+ CREATE USER 'ldumbd'@'localhost' IDENTIFIED BY 'secret';
60
+ GRANT ALL PRIVILEGES ON ldumbd.* TO 'ldumbd'@'localhost';
61
+ EOS
62
+ gem install mysql2
63
+ sequel -m ${MIGRATIONS} "mysql2://ldumbd:${DB_PASSWORD}@localhost/ldumbd"
64
+
65
+ Running ldumbd
66
+ --------------
67
+
68
+ cp ${LDUMBD_DIR}/config.yml.sample /etc/ldumbd.yml
69
+ $EDITOR /etc/ldumbd.yml
70
+ ldumbd /etc/ldumbd.yml
71
+
72
+ Copyright
73
+ ---------
74
+
75
+ Copyright (c) 2014 Sebastian Boehm. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require 'rake/testtask'
2
+
3
+ MIGRATIONS = 'db/migrations'
4
+ DATABASE_URL = ENV['DATABASE_URL'] || 'sqlite://ldumbd-test.sqlite3'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ namespace :db do
11
+ desc 'Run database migrations'
12
+ task :migrate do
13
+ sh "bundle exec sequel -m #{MIGRATIONS} #{DATABASE_URL}"
14
+ end
15
+
16
+ desc 'Drop all tables'
17
+ task :nuke do
18
+ nuke_all_tables = 'DB.tables.each {|t| DB.drop_table?(t) }'
19
+ sh "bundle exec sequel -m #{MIGRATIONS} -M 0 #{DATABASE_URL}"
20
+ sh "bundle exec sequel -c '#{nuke_all_tables}' #{DATABASE_URL}"
21
+ end
22
+
23
+ desc 'Reset database'
24
+ task :reset => [:nuke, :migrate]
25
+ end
data/TODO.org ADDED
@@ -0,0 +1,4 @@
1
+ * Filters
2
+ - add documentation for missing :approx filter
3
+
4
+ * Add some simple server specs using LDAP client library
data/bin/ldumbd ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sequel'
4
+ require 'yaml'
5
+
6
+ require 'ldumbd/operation'
7
+ require 'ldumbd/ldap_tree'
8
+ require 'ldumbd/preforkserver'
9
+
10
+ if ARGV.size != 1
11
+ puts 'usage: ldumbd LDUMBD_CONFIG'
12
+ exit false
13
+ end
14
+
15
+ begin
16
+ debug = ENV['DEBUG'] == '1'
17
+ config = YAML.load(File.read(ARGV[0]))
18
+ DB = Sequel.connect(config['database'])
19
+ require 'ldumbd/user'
20
+ require 'ldumbd/group'
21
+ DB.disconnect
22
+
23
+ ldap_tree = Ldumbd::LdapTree.new(config['basedn'])
24
+
25
+ puts "ldumbd #{Ldumbd::VERSION} starting..."
26
+ server = LDAP::Server.new(bindaddr: config['bind_address'],
27
+ port: config['port'],
28
+ nodelay: true,
29
+ listen: 10,
30
+ num_processes: 10,
31
+ operation_class: Ldumbd::Operation,
32
+ operation_args: ldap_tree,
33
+ user: config['user'],
34
+ group: config['group'],
35
+ debug: debug)
36
+ server.run_preforkserver
37
+ trap('INT') { puts 'caught SIGINT, shutting down'; exit }
38
+ puts "prefork ok, resuming normal operation"
39
+ server.join
40
+ rescue => e
41
+ $stderr.puts e.message
42
+ $stderr.puts e.backtrace if debug
43
+ end
data/config.yml.sample ADDED
@@ -0,0 +1,11 @@
1
+ #bind_address: 0.0.0.0
2
+ #port: 1389
3
+ #user: ldumbd
4
+ #group: ldumbd
5
+ basedn: dc=example,dc=org
6
+
7
+ # database: postgres:///ldumbd
8
+
9
+ # database: postgres://user:password@host/database
10
+ # database: mysql2://user:password@host:port/database
11
+ # database: sqlite:///path/to/database.sqlite3
@@ -0,0 +1,12 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:users) do
4
+ primary_key :id
5
+ String :name, size: 32, null: false, unique: true
6
+ String :realname, size: 64, null: false, default: ''
7
+ String :shell, size: 32, null: false, default: '/bin/false'
8
+ String :homedir, size: 128, null: false
9
+ index :name
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:groups) do
4
+ primary_key :id
5
+ String :name, size: 32, null: false, unique: true
6
+ index :name
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ Sequel.migration do
2
+ nogroup_gid = 65534
3
+
4
+ up do
5
+ self[:groups].insert(id: nogroup_gid, name: 'nogroup')
6
+ alter_table(:users) do
7
+ add_foreign_key :group_id, :groups, null: false, default: nogroup_gid
8
+ # MySQL automatically adds indexes for foreign key columns
9
+ add_index :group_id unless DB.database_type == :mysql
10
+ end
11
+ end
12
+
13
+ down do
14
+ alter_table(:users) do
15
+ drop_index :group_id unless DB.database_type == :mysql
16
+ drop_foreign_key :group_id
17
+ end
18
+ self[:groups].where(id: nogroup_gid).delete
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:groups_users) do
4
+ foreign_key :group_id, :groups, null: false
5
+ foreign_key :user_id, :users, null: false
6
+ primary_key [:group_id, :user_id]
7
+ index :user_id
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,59 @@
1
+ def set_min_max_value_commands(dbtype, min, max)
2
+ case dbtype
3
+ when :postgres
4
+ options = "start #{min} restart #{min} minvalue #{min} maxvalue #{max}"
5
+ ["alter sequence users_id_seq #{options};",
6
+ "alter sequence groups_id_seq #{options};"]
7
+ when :sqlite
8
+ id = min-1
9
+ name = '#dummyname#'
10
+ dir = '#dummydir#'
11
+ ["insert into users(id, name, homedir) values(#{id}, '#{name}', '#{dir}');",
12
+ "delete from users where id = #{id};",
13
+ "insert into groups(id, name) values(#{id}, '#{name}');",
14
+ "delete from groups where id = #{id};"]
15
+ when :mysql
16
+ ["alter table users auto_increment = #{min};",
17
+ "alter table groups auto_increment = #{min};"]
18
+ else
19
+ raise Sequel::Error, "database type '#{dbtype}' not supported"
20
+ end
21
+ end
22
+
23
+ def reset_min_max_value_commands(dbtype, next_uid, next_gid)
24
+ case dbtype
25
+ when :postgres
26
+ ["alter sequence users_id_seq start #{next_uid} restart #{next_uid} \
27
+ no minvalue no maxvalue;",
28
+ "alter sequence groups_id_seq start #{next_gid} restart #{next_gid} \
29
+ no minvalue no maxvalue;"]
30
+ when :sqlite
31
+ ["delete from sqlite_sequence where name = 'users';",
32
+ "delete from sqlite_sequence where name = 'groups';"]
33
+ when :mysql
34
+ ["alter table users auto_increment = #{next_uid};",
35
+ "alter table groups auto_increment = #{next_gid};"]
36
+ else
37
+ raise Sequel::Error, "database type '#{dbtype}' not supported"
38
+ end
39
+ end
40
+
41
+ Sequel.migration do
42
+ dbtype = DB.database_type
43
+ min_id = 100_000
44
+ max_id = 200_000
45
+
46
+ up do
47
+ set_min_max_value_commands(dbtype, min_id, max_id).each do |command|
48
+ run command
49
+ end
50
+ end
51
+
52
+ down do
53
+ next_uid = self[:users].max(:id).to_i + 1
54
+ next_gid = self[:groups].max(:id).to_i + 1
55
+ reset_min_max_value_commands(dbtype, next_uid, next_gid).each do |command|
56
+ run command
57
+ end
58
+ end
59
+ end
data/ldumbd.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'ldumbd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ldumbd'
8
+ spec.version = Ldumbd::VERSION
9
+ spec.author = 'Sebastian Boehm'
10
+ spec.email = 'sebastian@sometimesfood.org'
11
+
12
+ spec.summary = %q{A simple, self-contained LDAP server}
13
+ spec.homepage = 'https://github.com/sometimesfood/ldumbd'
14
+ spec.license = 'MIT'
15
+ spec.description = <<EOS
16
+ Ldumbd is a simple, self-contained read-only LDAP server that uses
17
+ PostgreSQL, MySQL/MariaDB or SQLite as a back end.
18
+ EOS
19
+
20
+ spec.files = `git ls-files -z`.split("\0") &
21
+ Dir['config.yml.sample',
22
+ 'Gemfile',
23
+ 'ldumbd.gemspec',
24
+ 'LICENSE',
25
+ 'NEWS',
26
+ 'Rakefile',
27
+ 'README.md',
28
+ 'TODO.org',
29
+ '{bin,lib,db,spec}/**/*']
30
+ spec.executables = ['ldumbd']
31
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
32
+
33
+ spec.add_dependency 'ruby-ldapserver', '~> 0.5.0'
34
+ spec.add_dependency 'sequel', '~> 4.7.0'
35
+
36
+ spec.add_development_dependency 'sqlite3', '~> 1.3.9'
37
+ spec.add_development_dependency 'pg', '~> 0.17.1'
38
+ spec.add_development_dependency 'mysql2', '~> 0.3.15'
39
+ spec.add_development_dependency 'rake', '~> 10.0.4'
40
+ spec.add_development_dependency 'net-ldap', '~> 0.3.1'
41
+ spec.add_development_dependency 'minitest', '~> 5.3.0'
42
+ end
@@ -0,0 +1,190 @@
1
+ require 'ldumbd/table_map'
2
+
3
+ module Ldumbd
4
+ class FilterConverter
5
+ # Public: Converts a parsed LDAP filter to a Sequel dataset filter.
6
+ #
7
+ # base_model: The dataset's base model.
8
+ # filter: A parsed LDAP filter.
9
+ #
10
+ # Examples
11
+ #
12
+ # base_model = User
13
+ # filter = [:and,
14
+ # [:eq, 'uid', nil, 'john'],
15
+ # [:ge, 'uidNumber', nil, '1000']]
16
+ # base_model.where(Ldumbd::FilterConverter.filter_to_sequel(base_model,
17
+ # filter)).sql
18
+ # # => "SELECT * FROM \"users\" WHERE ((\"users\".\"name\" = 'john') AND (\"users\".\"id\" >= '1000'))"
19
+ #
20
+ # Returns a Sequel dataset filter.
21
+ def self.filter_to_sequel(base_model, filter)
22
+ filter = filter.dup
23
+ op = filter.shift
24
+ case op
25
+ when :and, :or, :not
26
+ nested_filter_to_sequel(base_model, filter, op)
27
+ when :true, :false, :eq, :ge, :le, :present, :substrings
28
+ simple_filter_to_sequel(base_model, filter, op)
29
+ else
30
+ raise 'not implemented yet'
31
+ end
32
+ end
33
+
34
+ private
35
+ # Internal: Transforms a nested filter expression into a
36
+ # Sequel::SQL::BooleanExpression
37
+ #
38
+ # base_model: The dataset's base model.
39
+ # filters: An array of parsed LDAP filters.
40
+ # op: The type of nested filter, i.e. :or, :and or :not.
41
+ #
42
+ # Examples
43
+ #
44
+ # base_model = User
45
+ # filters = [[:eq, 'uid', nil, 'john'],
46
+ # [:ge, 'uidNumber', nil, '1000']]
47
+ # op = :and
48
+ # query = Ldumbd::FilterConverter.nested_filter_to_sequel(base_model,
49
+ # filters,
50
+ # op)
51
+ # base_model.where(query).sql
52
+ # # => "SELECT * FROM \"users\" WHERE ((\"users\".\"name\" = 'john') AND (\"users\".\"id\" >= '1000'))"
53
+ def self.nested_filter_to_sequel(base_model, filters, op)
54
+ case op
55
+ when :and
56
+ Sequel.&(*filters.map { |f| filter_to_sequel(base_model, f) })
57
+ when :or
58
+ Sequel.|(*filters.map { |f| filter_to_sequel(base_model, f) })
59
+ when :not
60
+ raise ArgumentError, 'Too many :not arguments' unless filters.size == 1
61
+ subfilter = filters[0]
62
+ # ... id NOT IN (SELECT id FROM ...)
63
+ Sequel.~(id: base_model.where(filter_to_sequel(base_model,
64
+ subfilter)).select(:id))
65
+ end
66
+ end
67
+
68
+ # Internal: Transforms a non-nested filter expression into a sequel
69
+ # dataset filter.
70
+ #
71
+ # base_model: The dataset's base model.
72
+ # filter: A parsed LDAP filter without a filter operator.
73
+ # op: The filter type, e.g. :eq, :le, :ge, ...
74
+ #
75
+ # Examples
76
+ #
77
+ # base_model = User
78
+ # filter = ['uid', nil, 'john']
79
+ # op = :eq
80
+ # query = Ldumbd::FilterConverter.simple_filter_to_sequel(base_model,
81
+ # filter,
82
+ # op)
83
+ # base_model.where(query).sql
84
+ # # => "SELECT * FROM \"users\" WHERE (\"users\".\"name\" = 'john')"
85
+ def self.simple_filter_to_sequel(base_model, filter, op)
86
+ key, value = extract_key_value(base_model, filter, op)
87
+ return { key => value } if key == :users
88
+ return oc_match?(base_model, op, value) if key == :object_class
89
+
90
+ case op
91
+ when :true
92
+ true
93
+ when :false
94
+ false
95
+ when :eq
96
+ # TODO: no case insensitive search support yet
97
+ # User.where(Sequel.function(:lower, :name) => v.downcase)
98
+ { key => value }
99
+ when :le
100
+ Sequel.expr(key) <= Sequel.expr(value)
101
+ when :ge
102
+ Sequel.expr(key) >= Sequel.expr(value)
103
+ when :present
104
+ !key.nil?
105
+ when :substrings
106
+ Sequel.like(key, value)
107
+ end
108
+ end
109
+
110
+ # Internal: Extracts keys and values or Sequel filters for use in
111
+ # simple filters.
112
+ #
113
+ # model: The dataset's model.
114
+ # filter: A parsed LDAP filter without a filter operator.
115
+ # op: The filter type, e.g. :eq, :le, :ge, ...
116
+ #
117
+ # Examples
118
+ #
119
+ # model = User
120
+ # filter = ['uid', nil, 'john']
121
+ # op = :eq
122
+ # Ldumbd::FilterConverter.extract_key_value(model, filter, op)
123
+ # # => [:users__name, "john"]
124
+ def self.extract_key_value(model, filter, op)
125
+ ldap_value = filter[2..-1] || []
126
+ key = extract_key(model, filter)
127
+
128
+ value = if key == :users
129
+ User.where(filter_to_sequel(User, [op, 'uid', nil, ldap_value]))
130
+ elsif op == :substrings
131
+ ldap_value.join('%')
132
+ else
133
+ ldap_value[0]
134
+ end
135
+
136
+ return key, value
137
+ end
138
+
139
+ # Internal: Extracts LDAP keys from parsed LDAP filters and
140
+ # converts them to SQL attributes.
141
+ #
142
+ # model: The dataset model.
143
+ # filter: A parsed LDAP filter without a filter operator.
144
+ #
145
+ # Examples
146
+ #
147
+ # model = User
148
+ # filter = ['uid', nil, 'john']
149
+ # Ldumbd::FilterConverter.extract_key(model, filter)
150
+ # # => :users__name
151
+ def self.extract_key(model, filter)
152
+ ldap_key = filter[0]
153
+ if ldap_key == 'objectClass'
154
+ # object classes are not saved in the database
155
+ :object_class
156
+ else
157
+ TableMap.db_key(model, ldap_key)
158
+ end
159
+ end
160
+
161
+ # Internal: Checks whether a model matches a target object class.
162
+ #
163
+ # model: The model.
164
+ # op: An LDAP filter operator, i.e. :eq, :le, :ge or :substrings.
165
+ # target_oc: The target object class.
166
+ #
167
+ # Examples
168
+ #
169
+ # Ldumbd::FilterConverter.oc_match?(User, :ge, 'posixA')
170
+ # # => true
171
+ #
172
+ # Ldumbd::FilterConverter.oc_match?(Group, :eq, 'nonexistent')
173
+ # # => false
174
+ def self.oc_match?(model, op, target_oc)
175
+ object_classes = TableMap.object_classes(model)
176
+
177
+ case op
178
+ when :eq
179
+ object_classes.any? { |oc| oc == target_oc }
180
+ when :le
181
+ object_classes.any? { |oc| oc <= target_oc }
182
+ when :ge
183
+ object_classes.any? { |oc| oc >= target_oc }
184
+ when :substrings
185
+ target_oc_re = /\A#{Regexp.escape(target_oc).gsub('%', '.*')}\Z/
186
+ object_classes.any? { |oc| oc =~ target_oc_re }
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,3 @@
1
+ class Group < Sequel::Model
2
+ many_to_many :users
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'ldumbd/tree_object'
2
+
3
+ module Ldumbd
4
+ class LdapTree
5
+ def find_by_dn(dn)
6
+ object = @tree[dn]
7
+ unless object
8
+ object = TreeObject.by_dn(@basedn, dn)
9
+ end
10
+ object or raise LDAP::ResultError::NoSuchObject
11
+ end
12
+
13
+ def initialize(basedn)
14
+ @basedn = basedn
15
+ /\Adc=(?<dc>[^,]*)/ =~ basedn
16
+ root = TreeObject.new({ 'dn_prefix' => '',
17
+ 'dc' => dc,
18
+ 'objectClass' => ['top', 'domain']})
19
+ people = TreeObject.new({ 'dn_prefix' => 'ou=People',
20
+ 'ou' => 'People',
21
+ 'objectClass' => ['top',
22
+ 'organizationalUnit'] })
23
+ groups = TreeObject.new({ 'dn_prefix' => 'ou=Groups',
24
+ 'ou' => 'Groups',
25
+ 'objectClass' => ['top',
26
+ 'organizationalUnit'] })
27
+ root.children << people
28
+ root.children << groups
29
+ people.children << User
30
+ groups.children << Group
31
+
32
+ @tree = Hash[
33
+ [root, people, groups].map do |object|
34
+ dn_prefix = object.attributes['dn_prefix']
35
+ [dn(dn_prefix), object]
36
+ end
37
+ ]
38
+ end
39
+
40
+ def dn(dn_prefix)
41
+ [dn_prefix, @basedn].reject(&:empty?).join(',')
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,61 @@
1
+ require 'ldap/server'
2
+
3
+ module Ldumbd
4
+ class Operation < LDAP::Server::Operation
5
+ def initialize(connection, messageID, ldap_tree)
6
+ super(connection, messageID)
7
+ @ldap_tree = ldap_tree
8
+ end
9
+
10
+ def search(basedn, scope, deref, filter)
11
+ search_results(basedn, scope, deref, filter) do |ldap_object|
12
+ dn = @ldap_tree.dn(ldap_object['dn_prefix'])
13
+ object = ldap_object.reject { |k, v| k == 'dn_prefix' }
14
+ send_SearchResultEntry(dn, object)
15
+ end
16
+ end
17
+
18
+ private
19
+ def search_results(basedn, scope, deref, filter, &block)
20
+ case scope
21
+ when LDAP::Server::BaseObject
22
+ search_results_baseobject(basedn, filter, &block)
23
+ when LDAP::Server::SingleLevel
24
+ search_results_singlelevel(basedn, filter, &block)
25
+ when LDAP::Server::WholeSubtree
26
+ search_results_subtree(basedn, filter, &block)
27
+ else
28
+ raise LDAP::ResultError::UnwillingToPerform, 'Invalid search scope'
29
+ end
30
+ end
31
+
32
+ def search_results_baseobject(basedn, filter, &block)
33
+ # the base object only
34
+ object = @ldap_tree.find_by_dn(basedn)
35
+ if object.matches_filter?(filter) && block_given?
36
+ block.call(TableMap.sequel_to_ldap_object(object.attributes))
37
+ end
38
+ end
39
+
40
+ def search_results_singlelevel(basedn, filter, &block)
41
+ # objects immediately subordinate to the base object; does not
42
+ # include the base object itself.
43
+ object = @ldap_tree.find_by_dn(basedn)
44
+ object.each_child(filter) do |r|
45
+ block.call(TableMap.sequel_to_ldap_object(r)) if block_given?
46
+ end
47
+ end
48
+
49
+ def search_results_subtree(basedn, filter, &block)
50
+ # base object and the entire subtree, also includes the base
51
+ # object itself.
52
+ object = @ldap_tree.find_by_dn(basedn)
53
+ if object.matches_filter?(filter) && block_given?
54
+ block.call(TableMap.sequel_to_ldap_object(object.attributes))
55
+ end
56
+ object.each_child(filter, true) do |r|
57
+ block.call(TableMap.sequel_to_ldap_object(r)) if block_given?
58
+ end
59
+ end
60
+ end # Operation
61
+ end # Ldumbd