Class: LDAP::Server::Connection
- Inherits:
-
Object
- Object
- LDAP::Server::Connection
- Defined in:
- lib/ldap/server/connection.rb
Overview
An object which handles an LDAP connection. Note that LDAP allows requests and responses to be exchanged asynchronously: e.g. a client can send three requests, and the three responses can come back in any order. For that reason, we start a new thread for each request, and we need a mutex on the io object so that multiple responses don’t interfere with each other.
Instance Attribute Summary (collapse)
-
- (Object) binddn
readonly
Returns the value of attribute binddn.
-
- (Object) opt
readonly
Returns the value of attribute opt.
-
- (Object) version
readonly
Returns the value of attribute version.
Instance Method Summary (collapse)
- - (Object) abandon(messageID)
- - (Object) abandon_all
-
- (Object) ber_read(io)
Read one ASN1 element from the given stream.
- - (Object) handle_requests
-
- (Connection) initialize(io, opt = {})
constructor
A new instance of Connection.
- - (Object) log(msg)
- - (Object) send_notice_of_disconnection(resultCode, errorMessage = "")
- - (Object) send_unsolicited_notification(resultCode, opt = {})
-
- (Object) startssl
:yields:.
- - (Object) write(data)
- - (Object) writelock
Constructor Details
- (Connection) initialize(io, opt = {})
A new instance of Connection
18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/ldap/server/connection.rb', line 18 def initialize(io, opt={}) @io = io @opt = opt @mutex = Mutex.new @active_reqs = {} # map message ID to thread object @binddn = nil @version = 3 @logger = @opt[:logger] || $stderr @ssl = false startssl if @opt[:ssl_on_connect] end |
Instance Attribute Details
- (Object) binddn (readonly)
Returns the value of attribute binddn
16 17 18 |
# File 'lib/ldap/server/connection.rb', line 16 def binddn @binddn end |
- (Object) opt (readonly)
Returns the value of attribute opt
16 17 18 |
# File 'lib/ldap/server/connection.rb', line 16 def opt @opt end |
- (Object) version (readonly)
Returns the value of attribute version
16 17 18 |
# File 'lib/ldap/server/connection.rb', line 16 def version @version end |
Instance Method Details
- (Object) abandon(messageID)
227 228 229 230 231 232 |
# File 'lib/ldap/server/connection.rb', line 227 def abandon() @mutex.synchronize do thread = @active_reqs.delete() thread.raise LDAP::Abandon if thread and thread.alive? end end |
- (Object) abandon_all
234 235 236 237 238 239 240 241 242 |
# File 'lib/ldap/server/connection.rb', line 234 def abandon_all return if @active_reqs.size == 0 @mutex.synchronize do @active_reqs.each do |id, thread| thread.raise LDAP::Abandon if thread.alive? end @active_reqs = {} end end |
- (Object) ber_read(io)
Read one ASN1 element from the given stream. Return String containing the raw element.
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 |
# File 'lib/ldap/server/connection.rb', line 49 def ber_read(io) blk = io.read(2) # minimum: short tag, short length throw(:close) if blk.nil? codepoints = blk.respond_to?(:codepoints) ? blk.codepoints.to_a : blk tag = codepoints[0] & 0x1f len = codepoints[1] if tag == 0x1f # long form tag = 0 while true ch = io.getc blk << ch tag = (tag << 7) | (ch & 0x7f) break if (ch & 0x80) == 0 end len = io.getc blk << len end if (len & 0x80) != 0 # long form len = len & 0x7f raise LDAP::ResultError::ProtocolError, "Indefinite length encoding not supported" if len == 0 offset = blk.length blk << io.read(len) # is there a more efficient way of doing this? len = 0 blk[offset..-1].each_byte { |b| len = (len << 8) | b } end offset = blk.length blk << io.read(len) return blk # or if we wanted to keep the partial decoding we've done: # return blk, [blk[0] >> 6, tag], offset end |
- (Object) handle_requests
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 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/ldap/server/connection.rb', line 87 def handle_requests operationClass = @opt[:operation_class] ocArgs = @opt[:operation_args] || [] catch(:close) do while true begin blk = ber_read(@io) asn1 = OpenSSL::ASN1::decode(blk) # Debugging: # puts "Request: #{blk.unpack("H*")}\n#{asn1.inspect}" if $debug raise LDAP::ResultError::ProtocolError, "LDAPMessage must be SEQUENCE" unless asn1.is_a?(OpenSSL::ASN1::Sequence) raise LDAP::ResultError::ProtocolError, "Bad Message ID" unless asn1.value[0].is_a?(OpenSSL::ASN1::Integer) = asn1.value[0].value protocolOp = asn1.value[1] raise LDAP::ResultError::ProtocolError, "Bad protocolOp" unless protocolOp.is_a?(OpenSSL::ASN1::ASN1Data) raise LDAP::ResultError::ProtocolError, "Bad protocolOp tag class" unless protocolOp.tag_class == :APPLICATION # controls are not properly implemented c = asn1.value[2] if c.is_a?(OpenSSL::ASN1::ASN1Data) and c.tag_class == :APPLICATION and c.tag == 0 controls = c.value end case protocolOp.tag when 0 # BindRequest abandon_all @binddn, @version = operationClass.new(self,,*ocArgs). do_bind(protocolOp, controls) when 2 # UnbindRequest throw(:close) when 3 # SearchRequest # Note: RFC 2251 4.4.4.1 says behaviour is undefined if # client sends an overlapping request with same message ID, # so we don't have to worry about the case where there is # already a thread with this id in @active_reqs. # However, to avoid a race we copy messageId/ # protocolOp/controls into thread-local variables, because # they will change when the next request comes in. # # There is a theoretical race condition here: a client could # send an abandon request before Thread.current is assigned to # @active_reqs[thrm]. It's not a problem, because abandon isn't # guaranteed to work anyway. Doing it this way ensures that # @active_reqs does not leak memory on a long-lived connection. Thread.new(,protocolOp,controls) do |thrm,thrp,thrc| begin @active_reqs[thrm] = Thread.current operationClass.new(self,thrm,*ocArgs).do_search(thrp, thrc) ensure @active_reqs.delete(thrm) end end when 6 # ModifyRequest Thread.new(,protocolOp,controls) do |thrm,thrp,thrc| begin @active_reqs[thrm] = Thread.current operationClass.new(self,thrm,*ocArgs).do_modify(thrp, thrc) ensure @active_reqs.delete(thrm) end end when 8 # AddRequest Thread.new(,protocolOp,controls) do |thrm,thrp,thrc| begin @active_reqs[thrm] = Thread.current operationClass.new(self,thrm,*ocArgs).do_add(thrp, thrc) ensure @active_reqs.delete(thrm) end end when 10 # DelRequest Thread.new(,protocolOp,controls) do |thrm,thrp,thrc| begin @active_reqs[thrm] = Thread.current operationClass.new(self,thrm,*ocArgs).do_del(thrp, thrc) ensure @active_reqs.delete(thrm) end end when 12 # ModifyDNRequest Thread.new(,protocolOp,controls) do |thrm,thrp,thrc| begin @active_reqs[thrm] = Thread.current operationClass.new(self,thrm,*ocArgs).do_modifydn(thrp, thrc) ensure @active_reqs.delete(thrm) end end when 14 # CompareRequest Thread.new(,protocolOp,controls) do |thrm,thrp,thrc| begin @active_reqs[thrm] = Thread.current operationClass.new(self,thrm,*ocArgs).do_compare(thrp, thrc) ensure @active_reqs.delete(thrm) end end when 16 # AbandonRequest abandon(protocolOp.value) else raise LDAP::ResultError::ProtocolError, "Unrecognised protocolOp tag #{protocolOp.tag}" end rescue LDAP::ResultError::ProtocolError, OpenSSL::ASN1::ASN1Error => e send_notice_of_disconnection(LDAP::ResultError::ProtocolError.new.to_i, e.) throw(:close) # all other exceptions propagate up and are caught by tcpserver end end end abandon_all end |
- (Object) log(msg)
31 32 33 |
# File 'lib/ldap/server/connection.rb', line 31 def log(msg) @logger << "[#{@io.peeraddr[3]}]: #{msg}\n" end |
- (Object) send_notice_of_disconnection(resultCode, errorMessage = "")
268 269 270 271 272 273 |
# File 'lib/ldap/server/connection.rb', line 268 def send_notice_of_disconnection(resultCode, errorMessage="") send_unsolicited_notification(resultCode, :errorMessage=>errorMessage, :responseName=>"1.3.6.1.4.1.1466.20036" ) end |
- (Object) send_unsolicited_notification(resultCode, opt = {})
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/ldap/server/connection.rb', line 244 def send_unsolicited_notification(resultCode, opt={}) protocolOp = [ OpenSSL::ASN1::Enumerated(resultCode), OpenSSL::ASN1::OctetString(opt[:matchedDN] || ""), OpenSSL::ASN1::OctetString(opt[:errorMessage] || ""), ] if opt[:referral] rs = opt[:referral].collect { |r| OpenSSL::ASN1::OctetString(r) } protocolOp << OpenSSL::ASN1::Sequence(rs, 3, :IMPLICIT, :APPLICATION) end if opt[:responseName] protocolOp << OpenSSL::ASN1::OctetString(opt[:responseName], 10, :IMPLICIT, :APPLICATION) end if opt[:response] protocolOp << OpenSSL::ASN1::OctetString(opt[:response], 11, :IMPLICIT, :APPLICATION) end = [ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Sequence(protocolOp, 24, :IMPLICIT, :APPLICATION), ] << opt[:controls] if opt[:controls] write(OpenSSL::ASN1::Sequence().to_der) end |
- (Object) startssl
:yields:
35 36 37 38 39 40 41 42 43 44 |
# File 'lib/ldap/server/connection.rb', line 35 def startssl # :yields: @mutex.synchronize do raise LDAP::ResultError::OperationsError if @ssl or @active_reqs.size > 0 yield if block_given? @io = OpenSSL::SSL::SSLSocket.new(@io, @opt[:ssl_ctx]) @io.sync_close = true @io.accept @ssl = true end end |
- (Object) write(data)
213 214 215 216 217 218 |
# File 'lib/ldap/server/connection.rb', line 213 def write(data) @mutex.synchronize do @io.write(data) @io.flush end end |
- (Object) writelock
220 221 222 223 224 225 |
# File 'lib/ldap/server/connection.rb', line 220 def writelock @mutex.synchronize do yield @io @io.flush end end |