1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
# $Id$
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
# Gmail account: garbagecat10.
#
# This is an LDAP server intended for unit testing of Net::LDAP.
# It implements as much of the protocol as we have the stomach
# to implement but serves static data. Use ldapsearch to test
# this server!
#
# To make this easier to write, we use the Ruby/EventMachine
# reactor library.
#
#------------------------------------------------
module LdapServer
LdapServerAsnSyntaxTemplate = {
:application => {
:constructed => {
0 => :array, # LDAP BindRequest
3 => :array # LDAP SearchRequest
},
:primitive => {
2 => :string, # ldapsearch sends this to unbind
},
},
:context_specific => {
:primitive => {
0 => :string, # simple auth (password)
7 => :string # present filter
},
:constructed => {
3 => :array # equality filter
},
},
}
def post_init
$logger.info "Accepted LDAP connection"
@authenticated = false
end
def receive_data data
@data ||= ""; @data << data
while pdu = @data.read_ber!(LdapServerAsnSyntax)
begin
handle_ldap_pdu pdu
rescue
$logger.error "closing connection due to error #{$!}"
close_connection
end
end
end
def handle_ldap_pdu pdu
tag_id = pdu[1].ber_identifier
case tag_id
when 0x60
handle_bind_request pdu
when 0x63
handle_search_request pdu
when 0x42
# bizarre thing, it's a null object (primitive application-2)
# sent by ldapsearch to request an unbind (or a kiss-off, not sure which)
close_connection_after_writing
else
$logger.error "received unknown packet-type #{tag_id}"
close_connection_after_writing
end
end
def handle_bind_request pdu
# TODO, return a proper LDAP error instead of blowing up on version error
if pdu[1][0] != 3
send_ldap_response 1, pdu[0].to_i, 2, "", "We only support version 3"
elsif pdu[1][1] != "cn=bigshot,dc=bayshorenetworks,dc=com"
send_ldap_response 1, pdu[0].to_i, 48, "", "Who are you?"
elsif pdu[1][2].ber_identifier != 0x80
send_ldap_response 1, pdu[0].to_i, 7, "", "Keep it simple, man"
elsif pdu[1][2] != "opensesame"
send_ldap_response 1, pdu[0].to_i, 49, "", "Make my day"
else
@authenticated = true
send_ldap_response 1, pdu[0].to_i, 0, pdu[1][1], "I'll take it"
end
end
# --
# Search Response ::=
# CHOICE {
# entry [APPLICATION 4] SEQUENCE {
# objectName LDAPDN,
# attributes SEQUENCE OF SEQUENCE {
# AttributeType,
# SET OF AttributeValue
# }
# },
# resultCode [APPLICATION 5] LDAPResult
# }
def handle_search_request pdu
unless @authenticated
# NOTE, early exit.
send_ldap_response 5, pdu[0].to_i, 50, "", "Who did you say you were?"
return
end
treebase = pdu[1][0]
if treebase != "dc=bayshorenetworks,dc=com"
send_ldap_response 5, pdu[0].to_i, 32, "", "unknown treebase"
return
end
msgid = pdu[0].to_i.to_ber
# pdu[1][7] is the list of requested attributes.
# If it's an empty array, that means that *all* attributes were requested.
requested_attrs = if pdu[1][7].length > 0
pdu[1][7].map(&:downcase)
else
:all
end
filters = pdu[1][6]
if filters.length == 0
# NOTE, early exit.
send_ldap_response 5, pdu[0].to_i, 53, "", "No filter specified"
end
# TODO, what if this returns nil?
filter = Net::LDAP::Filter.parse_ldap_filter(filters)
$ldif.each do |dn, entry|
if filter.match(entry)
attrs = []
entry.each do |k, v|
if requested_attrs == :all || requested_attrs.include?(k.downcase)
attrvals = v.map(&:to_ber).to_ber_set
attrs << [k.to_ber, attrvals].to_ber_sequence
end
end
appseq = [dn.to_ber, attrs.to_ber_sequence].to_ber_appsequence(4)
pkt = [msgid.to_ber, appseq].to_ber_sequence
send_data pkt
end
end
send_ldap_response 5, pdu[0].to_i, 0, "", "Was that what you wanted?"
end
def send_ldap_response pkt_tag, msgid, code, dn, text
send_data([msgid.to_ber, [code.to_ber, dn.to_ber, text.to_ber].to_ber_appsequence(pkt_tag)].to_ber)
end
end
#------------------------------------------------
# Rather bogus, a global method, which reads a HARDCODED filename
# parses out LDIF data. It will be used to serve LDAP queries out of this server.
#
def load_test_data
ary = File.readlines("./testdata.ldif")
hash = {}
while (line = ary.shift) && line.chomp!
if line =~ /^dn:[\s]*/i
dn = $'
hash[dn] = {}
while (attr = ary.shift) && attr.chomp! && attr =~ /^([\w]+)[\s]*:[\s]*/
hash[dn][$1.downcase] ||= []
hash[dn][$1.downcase] << $'
end
end
end
hash
end
#------------------------------------------------
if __FILE__ == $0
require 'rubygems'
require 'eventmachine'
require 'logger'
$logger = Logger.new $stderr
$logger.info "adding ../lib to loadpath, to pick up dev version of Net::LDAP."
$:.unshift "../lib"
$ldif = load_test_data
require 'net/ldap'
LdapServerAsnSyntax = Net::BER.compile_syntax(LdapServerAsnSyntaxTemplate)
EventMachine.run do
$logger.info "starting LDAP server on 127.0.0.1 port 3890"
EventMachine.start_server "127.0.0.1", 3890, LdapServer
EventMachine.add_periodic_timer 60, proc { $logger.info "heartbeat" }
end
end
|