3 Commits

  1. 15
      README.md
  2. 27
      examples/client.rb
  3. 22
      examples/ldapdb.yaml
  4. 42
      examples/rbslapd4.rb
  5. 153
      examples/server1.rb
  6. 93
      examples/server2.rb
  7. 97
      lib/ldap/server/router.rb
  8. 41
      lib/ldap/server/util.rb

15
README.md

@ -1,5 +1,20 @@
# ruby-ldapserver # ruby-ldapserver
## ZOURIT-ADMIN
# dependencies
* gem install simple_ldap_authenticator net-ldap
# server
* ruby rbslapd4.rb
# client
* ruby client.rb
---------------
ruby-ldapserver is a lightweight, pure Ruby framework for implementing LDAP server applications. It is intended primarily for building a gateway from LDAP queries into some other protocol or database. It does not attempt to be a full or correct implementation of the standard LDAP data model itself (although you could build one using this as a frontend). ruby-ldapserver is a lightweight, pure Ruby framework for implementing LDAP server applications. It is intended primarily for building a gateway from LDAP queries into some other protocol or database. It does not attempt to be a full or correct implementation of the standard LDAP data model itself (although you could build one using this as a frontend).
Since it's written entirely in Ruby, it benefits from Ruby's threading engine. Since it's written entirely in Ruby, it benefits from Ruby's threading engine.

27
examples/client.rb

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
require 'simple_ldap_authenticator'
raise 'Error: 2 arguments required => email + password' if ARGV.length != 2
email = ARGV.shift
password = ARGV.shift
# puts "email=#{email}"
user = email.split('@').first
fqdn = email.split('@')[1]
fqdn_items = fqdn.split('.')
tld = fqdn_items.last
domain = fqdn_items[0..-2].join('.')
# puts "user=#{user}"
# puts "domain=#{domain}"
# puts "tld=#{tld}"
#
SimpleLdapAuthenticator.servers = ['127.0.0.1']
SimpleLdapAuthenticator.port = 1389
SimpleLdapAuthenticator.use_ssl = false
# SimpleLdapAuthenticator.login_format = '%s@mydomain.com'
SimpleLdapAuthenticator.login_format = "uid=%s,ou=Users,dc=#{domain},dc=#{tld}"
p SimpleLdapAuthenticator.valid?(user, password)

22
examples/ldapdb.yaml

@ -1,17 +1,21 @@
--- ---
dc=example,dc=com:
dc=zourit,dc=re:
cn: cn:
- Top object - Top object
cn=fred flintstone,dc=example,dc=com:
cn=pvincent,dc=zourit,dc=re:
cn: cn:
- Fred Flintstone
- pvincent
sn: sn:
- Flintstone
- P.Vincent
mail: mail:
- fred@bedrock.org
- fred.flintstone@bedrock.org
cn=wilma flintstone,dc=example,dc=com:
- pvincent@zourit.re
dc=pvincent,dc=re:
cn: cn:
- Wilma Flintstone
- Top object
cn=admin,dc=pvincent,dc=re:
cn:
- admin
sn:
- Vincentdmin
mail: mail:
- wilma@bedrock.org
- admin@pvincent.re

42
examples/rbslapd4.rb

@ -17,8 +17,8 @@ $logger = Logger.new($stderr)
class LDAPController class LDAPController
def self.bind(request, version, dn, password, params) def self.bind(request, version, dn, password, params)
$logger.debug "Catchall bind request"
raise LDAP::ResultError::UnwillingToPerform, "Invalid bind DN"
$logger.debug 'Catchall bind request'
raise LDAP::ResultError::UnwillingToPerform, 'Invalid bind DN'
end end
def self.bindUser(request, version, dn, password, params) def self.bindUser(request, version, dn, password, params)
@ -26,7 +26,7 @@ class LDAPController
params[:uid] != 'admin' or params[:uid] != 'admin' or
password != 'adminpassword' password != 'adminpassword'
$logger.warn "Denied access for user #{params[:uid]}: Invalid credentials" $logger.warn "Denied access for user #{params[:uid]}: Invalid credentials"
raise LDAP::ResultError::InvalidCredentials, "Invalid credentials"
raise LDAP::ResultError::InvalidCredentials, 'Invalid credentials'
end end
$logger.info "Authenticated user #{params[:uid]}" $logger.info "Authenticated user #{params[:uid]}"
@ -34,28 +34,28 @@ class LDAPController
def self.search(request, baseObject, scope, deref, filter, params) def self.search(request, baseObject, scope, deref, filter, params)
$logger.info "Catchall search request for #{baseObject}" $logger.info "Catchall search request for #{baseObject}"
raise LDAP::ResultError::UnwillingToPerform, "Invalid search DN"
raise LDAP::ResultError::UnwillingToPerform, 'Invalid search DN'
end end
def self.searchUsers(request, baseObject, scope, deref, filter, params) def self.searchUsers(request, baseObject, scope, deref, filter, params)
$logger.info "Search users"
$logger.info 'Search users'
end end
end end
router = LDAP::Server::Router.new($logger) do router = LDAP::Server::Router.new($logger) do
# Different syntax but same thing # Different syntax but same thing
bind nil => "LDAPController#bind"
route :bind, nil => "LDAPController#bind"
bind nil => 'LDAPController#bind'
route :bind, nil => 'LDAPController#bind'
# Bind a route using variables. A hash with the variables will be passed # Bind a route using variables. A hash with the variables will be passed
# to your function as last argument. # to your function as last argument.
bind "uid=:uid,ou=Users,dc=mydomain,dc=com" => "LDAPController#bindUser"
# bind 'uid=:uid,ou=Users,dc=mydomain,dc=com' => 'LDAPController#bindUser'
bind 'uid=:uid,ou=Users,dc=:domain,dc=:tld' => 'LDAPController#bindUser'
search nil => "LDAPController#search"
search "ou=Users,dc=mydomain,dc=com" => "LDAPController#searchUsers"
search nil => 'LDAPController#search'
search 'ou=Users,dc=mydomain,dc=com' => 'LDAPController#searchUsers'
end end
# This is the shared object which carries our actual directory entries. # This is the shared object which carries our actual directory entries.
# It's just a hash of {dn=>entry}, where each entry is {attr=>[val,val,...]} # It's just a hash of {dn=>entry}, where each entry is {attr=>[val,val,...]}
@ -65,26 +65,26 @@ directory = {}
require 'yaml' require 'yaml'
begin begin
File.open("ldapdb.yaml") { |f| directory = YAML::load(f.read) }
File.open('ldapdb.yaml') { |f| directory = YAML.load(f.read) }
rescue Errno::ENOENT rescue Errno::ENOENT
end end
at_exit do at_exit do
File.open("ldapdb.new","w") { |f| f.write(YAML::dump(directory)) }
File.rename("ldapdb.new","ldapdb.yaml")
File.open('ldapdb.new', 'w') { |f| f.write(YAML.dump(directory)) }
File.rename('ldapdb.new', 'ldapdb.yaml')
end end
# Listen for incoming LDAP connections. For each one, create a Connection # Listen for incoming LDAP connections. For each one, create a Connection
# object, which will invoke a HashOperation object for each request. # object, which will invoke a HashOperation object for each request.
s = LDAP::Server.new( s = LDAP::Server.new(
:port => 1389,
:nodelay => true,
:listen => 10,
# :ssl_key_file => "key.pem",
# :ssl_cert_file => "cert.pem",
# :ssl_on_connect => true,
:router => router
port: 1389,
nodelay: true,
listen: 10,
# :ssl_key_file => "key.pem",
# :ssl_cert_file => "cert.pem",
# :ssl_on_connect => true,
router: router
) )
s.run_tcpserver s.run_tcpserver
s.join s.join

153
examples/server1.rb

@ -0,0 +1,153 @@
#!/usr/bin/env ruby
# This is a modified version of rbslapd1.rb which uses a Router instead of
# subclassing the LDAP::Server::Operation class.
# This is a trivial LDAP server which just stores directory entries in RAM.
# It does no validation or authentication. This is intended just to
# demonstrate the API, it's not for real-world use!!
$:.unshift('../lib')
$debug = false
require 'ldap/server'
require 'ldap/server/router'
$logger = Logger.new($stderr)
class HashOperation < LDAP::Server::Operation
def initialize
super
@hash = YAML.load_from_file('ldapdbp.yaml') # an object reference to our directory data
end
def search(basedn, scope, deref, filter)
basedn = basedn.downcase
case scope
when LDAP::Server::BaseObject
# client asked for single object by DN
obj = @hash[basedn]
raise LDAP::ResultError::NoSuchObject unless obj
send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj)
when LDAP::Server::WholeSubtree
@hash.each do |dn, av|
next unless dn.index(basedn, -basedn.length) # under basedn?
next unless LDAP::Server::Filter.run(filter, av) # attribute filter?
send_SearchResultEntry(dn, av)
end
else
raise LDAP::ResultError::UnwillingToPerform, 'OneLevel not implemented'
end
end
end
class LDAPController
def initialize
@directory = {}
File.open('ldapdb.yaml') { |f| @directory = YAML.load(f.read) }
end
def self.bind(request, version, dn, password, params)
$logger.debug 'Catchall bind request'
raise LDAP::ResultError::UnwillingToPerform, 'Invalid bind DN'
end
def self.bindUser(request, version, dn, password, params)
user = params[:uid]
domain = params[:domain]
tld = params[:tld]
# p "bindUser user=#{user} dn=#{dn}, password=#{password}, params=#{params}"
if user.length < 2
$logger.warn "Denied access for user #{user}: Size < 2"
raise LDAP::ResultError::InvalidCredentials, 'Invalid credentials'
end
$logger.info "Authenticated email=#{user}@#{domain}.#{tld} with password=<ANY>"
end
def self.searchUsers(request, baseObject, scope, deref, filter, params)
$logger.info 'Search users'
domain = params[:domain]
tld = params[:tld]
basedn = "#{domain}.#{tld}"
operation = HashOperation.new
case scope
when LDAP::Server::BaseObject
# client asked for single object by DN
obj = directory
raise LDAP::ResultError::NoSuchObject unless obj
operation.send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj)
when LDAP::Server::WholeSubtree
directory.each do |dn, av|
next unless dn.index(basedn, -basedn.length) # under basedn?
next unless LDAP::Server::Filter.run(filter, av) # attribute filter?
operation.send_SearchResultEntry(dn, av)
end
else
raise LDAP::ResultError::UnwillingToPerform, 'OneLevel not implemented'
end
end
end
router = LDAP::Server::Router.new($logger) do
# Different syntax but same thing
# bind nil => 'LDAPController#bind'
# route :bind, nil => 'LDAPController#bind'
# Bind a route using variables. A hash with the variables will be passed
# to your function as last argument.
# bind 'uid=:uid,ou=Users,dc=mydomain,dc=com' => 'LDAPController#bindUser'
bind 'uid=:uid,dc=:domain,dc=:tld' => 'LDAPController#bindUser'
search 'dc=:domain,dc=:tld' => 'LDAPController#searchUsers'
# search nil => 'LDAPController#search'
# search 'ou=Users,dc=mydomain,dc=com' => 'LDAPController#searchUsers'
end
# This is the shared object which carries our actual directory entries.
# It's just a hash of {dn=>entry}, where each entry is {attr=>[val,val,...]}
directory = {}
# Let's put some backing store on it
require 'yaml'
begin
File.open('ldapdb.yaml') { |f| directory = YAML.load(f.read) }
rescue Errno::ENOENT
end
at_exit do
File.open('ldapdb.new', 'w') { |f| f.write(YAML.dump(directory)) }
File.rename('ldapdb.new', 'ldapdb.yaml')
end
# Listen for incoming LDAP connections. For each one, create a Connection
# object, which will invoke a HashOperation object for each request.
s = LDAP::Server.new(
port: 1389,
nodelay: true,
listen: 10,
# :ssl_key_file => "key.pem",
# :ssl_cert_file => "cert.pem",
# :ssl_on_connect => true,
router: router
)
s.run_tcpserver
s.join

93
examples/server2.rb

@ -0,0 +1,93 @@
#!/usr/bin/env ruby
$:.unshift('lib')
$debug = true
require 'ldap/server'
$logger = Logger.new($stderr)
# We subclass the Operation class, overriding the methods to do what we need
class HashOperation < LDAP::Server::Operation
def initialize(connection, messageID, hash)
super(connection, messageID)
@hash = hash # an object reference to our directory data
end
# ici, c'est bizarre
# systèmatiquement appelé (SEARCH ou AUTH)
# du coup, je retourne super si pas de dn => SEARCH normal sans authentication
# sinon ça passe aussi !!!
def do_bind(protocolOp, controls) # :nodoc:
dn = protocolOp.value[1].value
dn = nil if dn == ''
return super if dn.nil?
version = protocolOp.value[0].value
authentication = protocolOp.value[2]
password = authentication.value
$logger.info("AUTHENTICATION ..version=#{version}, dn=#{dn}")
super
end
# def simple_bind(version, dn, password)
# super.simple_bind(version, dn, password)
# $logger.info("SIMPLE BIND...version=#{version}, dn=#{dn}")
#
# raise LDAP::ResultError::ProtocolError, 'version 3 only' if version != 3
# raise LDAP::ResultError::InvalidCredentials, 'Invalid credentials' if password.nil? || password == ''
#
# $logger.info('authentication SUCCESS')
# end
#
def search(basedn, scope, deref, filter)
$logger.info("SEARCHING...basedn=#{basedn}, scope=#{scope}, deref=#{deref}, filter=#{filter}")
basedn = basedn.downcase
result = nil
case scope
when LDAP::Server::BaseObject
# client asked for single object by DN
obj = @hash[basedn]
raise LDAP::ResultError::NoSuchObject unless obj
result = send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj)
when LDAP::Server::WholeSubtree
@hash.each do |dn, av|
next unless dn.index(basedn, -basedn.length) # under basedn?
next unless LDAP::Server::Filter.run(filter, av) # attribute filter?
result = send_SearchResultEntry(dn, av)
end
else
raise LDAP::ResultError::UnwillingToPerform, 'OneLevel not implemented'
end
$logger.info "result=#{result}"
result
end
end
# This is the shared object which carries our actual directory entries.
# It's just a hash of {dn=>entry}, where each entry is {attr=>[val,val,...]}
directory = {}
require 'yaml'
File.open('examples/ldapdb.yaml') { |f| directory = YAML.load(f.read) }
$logger.info("DIRECTORY=#{directory}")
# Listen for incoming LDAP connections. For each one, create a Connection
# object, which will invoke a HashOperation object for each request.
s = LDAP::Server.new(
port: 1389,
nodelay: true,
listen: 10,
operation_class: HashOperation,
operation_args: [directory]
)
$logger.info('server2 RUNNING...')
s.run_tcpserver
s.join

97
lib/ldap/server/router.rb

@ -5,11 +5,8 @@ require 'ldap/server/request'
require 'ldap/server/filter' require 'ldap/server/filter'
module LDAP module LDAP
class Server
class Router
@logger
@routes
class Server
class Router
# Scope # Scope
BaseObject = 0 BaseObject = 0
SingleLevel = 1 SingleLevel = 1
@ -24,14 +21,14 @@ class Router
def initialize(logger, &block) def initialize(logger, &block)
@logger = logger @logger = logger
@routes = Hash.new
@routes = {}
@routes = Trie.new do |trie| @routes = Trie.new do |trie|
# Add an artificial LDAP component # Add an artificial LDAP component
trie << "op=bind"
trie << "op=search"
trie << 'op=bind'
trie << 'op=search'
end end
self.instance_eval(&block)
instance_eval(&block)
end end
def log_exception(e, level = :error) def log_exception(e, level = :error)
@ -46,30 +43,29 @@ class Router
def route(operation, hash) def route(operation, hash)
hash.each do |key, value| hash.each do |key, value|
if key.nil? if key.nil?
@routes.insert "op=#{operation.to_s}", value
@logger.debug "map operation #{operation.to_s} all routes to #{value}"
@routes.insert "op=#{operation}", value
@logger.debug "map operation #{operation} all routes to #{value}"
else else
@routes.insert "#{key},op=#{operation.to_s}", value
@logger.debug "map #{operation.to_s} #{key} to #{value}"
@routes.insert "#{key},op=#{operation}", value
@logger.debug "map #{operation} #{key} to #{value}"
end end
end end
end end
def method_missing(name, *args, &block) def method_missing(name, *args, &block)
if [:bind, :search, :add, :modify, :modifydn, :del, :compare].include? name
if %i[bind search add modify modifydn del compare].include? name
send :route, name, *args send :route, name, *args
else else
super super
end end
end end
#################################################### ####################################################
### Methods to parse and route each request type ### ### Methods to parse and route each request type ###
#################################################### ####################################################
def parse_route(dn, method) def parse_route(dn, method)
route, action = @routes.match("#{dn},op=#{method.to_s}")
if not route or route.empty?
route, action = @routes.match("#{dn},op=#{method}")
if !route or route.empty?
@logger.warn "No route defined for \'#{route}\'" @logger.warn "No route defined for \'#{route}\'"
raise LDAP::ResultError::UnwillingToPerform raise LDAP::ResultError::UnwillingToPerform
end end
@ -81,12 +77,12 @@ class Router
class_name = action.split('#').first class_name = action.split('#').first
method_name = action.split('#').last method_name = action.split('#').last
params = LDAP::Server::DN.new("#{dn},op=#{method.to_s}").parse(route)
params = LDAP::Server::DN.new("#{dn},op=#{method}").parse(route)
return class_name, method_name, params
[class_name, method_name, params]
end end
def do_bind(connection, messageId, protocolOp, controls) # :nodoc:
def do_bind(connection, messageId, protocolOp, _controls) # :nodoc:
request = Request.new(connection, messageId) request = Request.new(connection, messageId)
version = protocolOp.value[0].value version = protocolOp.value[0].value
dn = protocolOp.value[1].value dn = protocolOp.value[1].value
@ -102,28 +98,28 @@ class Router
when 0 when 0
Object.const_get(class_name).send method_name, request, version, dn, authentication.value, params Object.const_get(class_name).send method_name, request, version, dn, authentication.value, params
when 3 when 3
mechanism = authentication.value[0].value
credentials = authentication.value[1].value
authentication.value[0].value
authentication.value[1].value
# sasl_bind(version, dn, mechanism, credentials) # sasl_bind(version, dn, mechanism, credentials)
# FIXME: needs to exchange further BindRequests # FIXME: needs to exchange further BindRequests
# route_sasl_bind(request, version, dn, mechanism, credentials) # route_sasl_bind(request, version, dn, mechanism, credentials)
raise LDAP::ResultError::AuthMethodNotSupported raise LDAP::ResultError::AuthMethodNotSupported
else else
raise LDAP::ResultError::ProtocolError, "BindRequest bad AuthenticationChoice"
raise LDAP::ResultError::ProtocolError, 'BindRequest bad AuthenticationChoice'
end end
request.send_BindResponse(0) request.send_BindResponse(0)
return dn, version
[dn, version]
rescue NoMethodError => e rescue NoMethodError => e
log_exception e log_exception e
request.send_BindResponse(LDAP::ResultError::OperationsError.new.to_i, :errorMessage => e.message)
return nil, version
request.send_BindResponse(LDAP::ResultError::OperationsError.new.to_i, errorMessage: e.message)
[nil, version]
rescue LDAP::ResultError => e rescue LDAP::ResultError => e
log_exception e log_exception e
request.send_BindResponse(e.to_i, :errorMessage => e.message)
return nil, version
request.send_BindResponse(e.to_i, errorMessage: e.message)
[nil, version]
end end
def do_search(connection, messageId, protocolOp, controls) # :nodoc:
def do_search(connection, messageId, protocolOp, _controls) # :nodoc:
request = Request.new(connection, messageId) request = Request.new(connection, messageId)
server = connection.opt[:server] server = connection.opt[:server]
schema = connection.opt[:schema] schema = connection.opt[:schema]
@ -133,8 +129,8 @@ class Router
client_sizelimit = protocolOp.value[3].value client_sizelimit = protocolOp.value[3].value
client_timelimit = protocolOp.value[4].value.to_i client_timelimit = protocolOp.value[4].value.to_i
request.typesOnly = protocolOp.value[5].value 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}
filter = LDAP::Server::Filter.parse(protocolOp.value[6], schema)
request.attributes = protocolOp.value[7].value.collect { |x| x.value }
sizelimit = request.server_sizelimit sizelimit = request.server_sizelimit
sizelimit = client_sizelimit if client_sizelimit > 0 and sizelimit = client_sizelimit if client_sizelimit > 0 and
@ -142,7 +138,7 @@ class Router
request.sizelimit = sizelimit request.sizelimit = sizelimit
if baseObject.empty? and scope == BaseObject if baseObject.empty? and scope == BaseObject
request.send_SearchResultEntry("", server.root_dse) if
request.send_SearchResultEntry('', server.root_dse) if
server.root_dse and LDAP::Server::Filter.run(filter, server.root_dse) server.root_dse and LDAP::Server::Filter.run(filter, server.root_dse)
request.send_SearchResultDone(0) request.send_SearchResultDone(0)
return return
@ -162,15 +158,14 @@ class Router
# Find a route in the routing tree # Find a route in the routing tree
class_name, method_name, params = parse_route(baseObject, :search) class_name, method_name, params = parse_route(baseObject, :search)
Timeout::timeout(t, LDAP::ResultError::TimeLimitExceeded) do
Timeout.timeout(t, LDAP::ResultError::TimeLimitExceeded) do
Object.const_get(class_name).send method_name, request, baseObject, scope, deref, filter, params Object.const_get(class_name).send method_name, request, baseObject, scope, deref, filter, params
end end
request.send_SearchResultDone(0) request.send_SearchResultDone(0)
# Note that TimeLimitExceeded is a subclass of LDAP::ResultError # Note that TimeLimitExceeded is a subclass of LDAP::ResultError
rescue LDAP::ResultError => e rescue LDAP::ResultError => e
request.send_SearchResultDone(e.to_i, :errorMessage=>e.message)
request.send_SearchResultDone(e.to_i, errorMessage: e.message)
rescue Abandon rescue Abandon
# send no response # send no response
@ -178,13 +173,11 @@ class Router
# catch all other exceptions. Otherwise, in the event of a programming # catch all other exceptions. Otherwise, in the event of a programming
# error, this thread will silently terminate and the client will wait # error, this thread will silently terminate and the client will wait
# forever for a response. # forever for a response.
rescue Exception => e rescue Exception => e
log_exception e log_exception e
request.send_SearchResultDone(LDAP::ResultError::OperationsError.new.to_i, :errorMessage=>e.message)
request.send_SearchResultDone(LDAP::ResultError::OperationsError.new.to_i, errorMessage: e.message)
end end
########################################################### ###########################################################
### Methods to actually perform the work requested ### ### Methods to actually perform the work requested ###
### Use the signatures below to write your own handlers ### ### Use the signatures below to write your own handlers ###
@ -195,14 +188,14 @@ class Router
# #
# Write your own class method using this signature # 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
# 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 # Handle a search request
# #
@ -211,10 +204,10 @@ class Router
# typesOnly are taken care of, but you need to perform all # typesOnly are taken care of, but you need to perform all
# authorisation checks yourself, using @connection.binddn # 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
# def search(basedn, scope, deref, filter)
# debug "search(#{basedn}, #{scope}, #{deref}, #{filter})"
# raise LDAP::ResultError::UnwillingToPerform, "search not implemented"
# end
end
end
end end

41
lib/ldap/server/util.rb

@ -1,10 +1,8 @@
require 'ldap/server/result' require 'ldap/server/result'
module LDAP module LDAP
class Server
class Server
class Operation class Operation
# Return true if connection is not authenticated # Return true if connection is not authenticated
def anonymous? def anonymous?
@ -33,7 +31,8 @@ class Server
def self.split_dn(dn) def self.split_dn(dn)
# convert \\ to \5c, \+ to \2b etc # convert \\ to \5c, \+ to \2b etc
dn.gsub!(/\\([ #,+"\\<>;])/) { |match| format "\\%02x", match[1].ord }
p "dn=#{dn}"
dn.gsub!(/\\([ #,+"\\<>;])/) { |match| format '\\%02x', match[1].ord }
# Now we know that \\ and \, do not exist, it's safe to split # Now we know that \\ and \, do not exist, it's safe to split
parts = dn.split(/\s*[,;]\s*/) parts = dn.split(/\s*[,;]\s*/)
@ -46,13 +45,13 @@ class Server
avs.each do |av| avs.each do |av|
# These should all be of form attr=value # These should all be of form attr=value
unless av =~ /^([^=]+)=(.*)$/
raise LDAP::ResultError::ProtocolError, "Bad DN component: #{av}"
end
attr, val = $1.downcase, $2
raise LDAP::ResultError::ProtocolError, "Bad DN component: #{av}" unless av =~ /^([^=]+)=(.*)$/
attr = ::Regexp.last_match(1).downcase
val = ::Regexp.last_match(2)
# Now we can decode those bits # Now we can decode those bits
attr.gsub!(/\\([a-f0-9][a-f0-9])/i) { $1.hex.chr }
val.gsub!(/\\([a-f0-9][a-f0-9])/i) { $1.hex.chr }
attr.gsub!(/\\([a-f0-9][a-f0-9])/i) { ::Regexp.last_match(1).hex.chr }
val.gsub!(/\\([a-f0-9][a-f0-9])/i) { ::Regexp.last_match(1).hex.chr }
res[attr] = val res[attr] = val
end end
res res
@ -64,25 +63,23 @@ class Server
# or just [attr,val] # or just [attr,val]
def self.join_dn(elements) def self.join_dn(elements)
dn = ""
dn = ''
elements.each do |elem| elements.each do |elem|
av = ""
av = ''
elem = [elem] if elem[0].is_a?(String) elem = [elem] if elem[0].is_a?(String)
elem.each do |attr,val|
av << "+" unless av == ""
elem.each do |attr, val|
av << '+' unless av == ''
av << attr << "=" <<
val.sub(/^([# ])/, '\\\\\\1').
sub(/( )$/, '\\\\\\1').
gsub(/([,+"\\<>;])/, '\\\\\\1')
av << attr << '=' <<
val.sub(/^([# ])/, '\\\\\\1')
.sub(/( )$/, '\\\\\\1')
.gsub(/([,+"\\<>;])/, '\\\\\\1')
end end
dn << "," unless dn == ""
dn << ',' unless dn == ''
dn << av dn << av
end end
dn dn
end end
end # class Operation end # class Operation
end # class Server
end # class Server
end # module LDAP end # module LDAP
Loading…
Cancel
Save