You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
7.2 KiB
220 lines
7.2 KiB
require 'ldap/server/dn'
|
|
require 'ldap/server/util'
|
|
require 'ldap/server/trie'
|
|
require 'ldap/server/request'
|
|
require 'ldap/server/filter'
|
|
|
|
module LDAP
|
|
class Server
|
|
class Router
|
|
@logger
|
|
@routes
|
|
|
|
# Scope
|
|
BaseObject = 0
|
|
SingleLevel = 1
|
|
WholeSubtree = 2
|
|
|
|
# DerefAliases
|
|
NeverDerefAliases = 0
|
|
DerefInSearching = 1
|
|
DerefFindingBaseObj = 2
|
|
DerefAlways = 3
|
|
|
|
def initialize(logger, &block)
|
|
@logger = logger
|
|
|
|
@routes = Hash.new
|
|
@routes = Trie.new do |trie|
|
|
# Add an artificial LDAP component
|
|
trie << "op=bind"
|
|
trie << "op=search"
|
|
end
|
|
|
|
self.instance_eval(&block)
|
|
end
|
|
|
|
def log_exception(e, level = :error)
|
|
@logger.send level, e.message
|
|
e.backtrace.each { |line| @logger.send level, "\tfrom#{line}" } if e.backtrace
|
|
end
|
|
|
|
######################
|
|
### Initialization ###
|
|
######################
|
|
|
|
def route(operation, hash)
|
|
hash.each do |key, value|
|
|
if key.nil?
|
|
@routes.insert "op=#{operation.to_s}", value
|
|
@logger.debug "map operation #{operation.to_s} all routes to #{value}"
|
|
else
|
|
@routes.insert "#{key},op=#{operation.to_s}", value
|
|
@logger.debug "map #{operation.to_s} #{key} to #{value}"
|
|
end
|
|
end
|
|
end
|
|
|
|
def method_missing(name, *args, &block)
|
|
if [:bind, :search, :add, :modify, :modifydn, :del, :compare].include? name
|
|
send :route, name, *args
|
|
else
|
|
super
|
|
end
|
|
end
|
|
|
|
|
|
####################################################
|
|
### Methods to parse and route each request type ###
|
|
####################################################
|
|
def parse_route(dn, method)
|
|
route, action = @routes.match("#{dn},op=#{method.to_s}")
|
|
if not route or route.empty?
|
|
@logger.warn "No route defined for \'#{route}\'"
|
|
raise LDAP::ResultError::UnwillingToPerform
|
|
end
|
|
if action.nil?
|
|
@logger.error "No action defined for route \'#{route}\'"
|
|
raise LDAP::ResultError::UnwillingToPerform
|
|
end
|
|
|
|
class_name = action.split('#').first
|
|
method_name = action.split('#').last
|
|
|
|
params = LDAP::Server::DN.new("#{dn},op=#{method.to_s}").parse(route)
|
|
|
|
return class_name, method_name, params
|
|
end
|
|
|
|
def do_bind(connection, messageId, protocolOp, controls) # :nodoc:
|
|
request = Request.new(connection, messageId)
|
|
version = protocolOp.value[0].value
|
|
dn = protocolOp.value[1].value
|
|
dn = nil if dn.empty?
|
|
authentication = protocolOp.value[2]
|
|
|
|
@logger.debug "subject:#{connection.binddn} predicate:bind object:#{dn}"
|
|
|
|
# Find a route in the routing tree
|
|
class_name, method_name, params = parse_route(dn, :bind)
|
|
|
|
case authentication.tag # tag_class == :CONTEXT_SPECIFIC (check why)
|
|
when 0
|
|
Object.const_get(class_name).send method_name, request, version, dn, authentication.value, params
|
|
when 3
|
|
mechanism = authentication.value[0].value
|
|
credentials = authentication.value[1].value
|
|
# sasl_bind(version, dn, mechanism, credentials)
|
|
# FIXME: needs to exchange further BindRequests
|
|
# route_sasl_bind(request, version, dn, mechanism, credentials)
|
|
raise LDAP::ResultError::AuthMethodNotSupported
|
|
else
|
|
raise LDAP::ResultError::ProtocolError, "BindRequest bad AuthenticationChoice"
|
|
end
|
|
request.send_BindResponse(0)
|
|
return dn, version
|
|
rescue NoMethodError => e
|
|
log_exception e
|
|
request.send_BindResponse(LDAP::ResultError::OperationsError.new.to_i, :errorMessage => e.message)
|
|
return nil, version
|
|
rescue LDAP::ResultError => e
|
|
log_exception e
|
|
request.send_BindResponse(e.to_i, :errorMessage => e.message)
|
|
return nil, version
|
|
end
|
|
|
|
def do_search(connection, messageId, protocolOp, controls) # :nodoc:
|
|
request = Request.new(connection, messageId)
|
|
server = connection.opt[:server]
|
|
schema = connection.opt[:schema]
|
|
baseObject = protocolOp.value[0].value
|
|
scope = protocolOp.value[1].value
|
|
deref = protocolOp.value[2].value
|
|
client_sizelimit = protocolOp.value[3].value
|
|
client_timelimit = protocolOp.value[4].value.to_i
|
|
request.typesOnly = protocolOp.value[5].value
|
|
filter = LDAP::Server::Filter::parse(protocolOp.value[6], schema)
|
|
request.attributes = protocolOp.value[7].value.collect {|x| x.value}
|
|
|
|
sizelimit = request.server_sizelimit
|
|
sizelimit = client_sizelimit if client_sizelimit > 0 and
|
|
(sizelimit.nil? or client_sizelimit < sizelimit)
|
|
request.sizelimit = sizelimit
|
|
|
|
if baseObject.empty? and scope == BaseObject
|
|
request.send_SearchResultEntry("", server.root_dse) if
|
|
server.root_dse and LDAP::Server::Filter.run(filter, server.root_dse)
|
|
request.send_SearchResultDone(0)
|
|
return
|
|
elsif schema and baseObject == schema.subschema_dn
|
|
request.send_SearchResultEntry(baseObject, schema.subschema_subentry) if
|
|
schema and schema.subschema_subentry and
|
|
LDAP::Server::Filter.run(filter, schema.subschema_subentry)
|
|
request.send_SearchResultDone(0)
|
|
return
|
|
end
|
|
|
|
t = request.server_timelimit || 10
|
|
t = client_timelimit if client_timelimit > 0 and client_timelimit < t
|
|
|
|
@logger.debug "subject:#{connection.binddn} predicate:search object:#{baseObject}"
|
|
|
|
# Find a route in the routing tree
|
|
class_name, method_name, params = parse_route(baseObject, :search)
|
|
|
|
Timeout::timeout(t, LDAP::ResultError::TimeLimitExceeded) do
|
|
Object.const_get(class_name).send method_name, request, baseObject, scope, deref, filter, params
|
|
end
|
|
request.send_SearchResultDone(0)
|
|
|
|
# Note that TimeLimitExceeded is a subclass of LDAP::ResultError
|
|
rescue LDAP::ResultError => e
|
|
request.send_SearchResultDone(e.to_i, :errorMessage=>e.message)
|
|
|
|
rescue Abandon
|
|
# send no response
|
|
|
|
# Since this Operation is running in its own thread, we have to
|
|
# catch all other exceptions. Otherwise, in the event of a programming
|
|
# error, this thread will silently terminate and the client will wait
|
|
# forever for a response.
|
|
|
|
rescue Exception => e
|
|
log_exception e
|
|
request.send_SearchResultDone(LDAP::ResultError::OperationsError.new.to_i, :errorMessage=>e.message)
|
|
end
|
|
|
|
|
|
###########################################################
|
|
### Methods to actually perform the work requested ###
|
|
### Use the signatures below to write your own handlers ###
|
|
###########################################################
|
|
|
|
# Handle a simple bind request; raise an exception if the bind is
|
|
# not acceptable, otherwise just return to accept the bind.
|
|
#
|
|
# Write your own class method using this signature
|
|
|
|
# def simple_bind(request, version, dn, password, params)
|
|
# if version != 3
|
|
# raise LDAP::ResultError::ProtocolError, "version 3 only"
|
|
# end
|
|
# if dn
|
|
# raise LDAP::ResultError::InappropriateAuthentication, "This server only supports anonymous bind"
|
|
# end
|
|
# end
|
|
|
|
# Handle a search request
|
|
#
|
|
# Call request. send_SearchResultEntry for each result found. Raise
|
|
# an exception if there is a problem. timeLimit, sizeLimit and
|
|
# typesOnly are taken care of, but you need to perform all
|
|
# authorisation checks yourself, using @connection.binddn
|
|
|
|
# def search(basedn, scope, deref, filter)
|
|
# debug "search(#{basedn}, #{scope}, #{deref}, #{filter})"
|
|
# raise LDAP::ResultError::UnwillingToPerform, "search not implemented"
|
|
# end
|
|
end
|
|
end
|
|
end
|