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
-
#binddn ⇒ Object
readonly
Returns the value of attribute binddn.
-
#opt ⇒ Object
readonly
Returns the value of attribute opt.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Instance Method Summary collapse
- #abandon(messageID) ⇒ Object
- #abandon_all ⇒ Object
-
#ber_read(io) ⇒ Object
Read one ASN1 element from the given stream.
- #debug(msg) ⇒ Object
- #handle_requests ⇒ Object
-
#initialize(io, opt = {}) ⇒ Connection
constructor
A new instance of Connection.
- #log(msg, severity = Logger::INFO) ⇒ Object
- #log_exception(e) ⇒ Object
- #send_notice_of_disconnection(resultCode, errorMessage = "") ⇒ Object
- #send_unsolicited_notification(resultCode, opt = {}) ⇒ Object
-
#start_op(messageId, protocolOp, controls, meth) ⇒ Object
Start an operation in a Thread.
-
#startssl ⇒ Object
:yields:.
- #write(data) ⇒ Object
- #writelock ⇒ Object
Constructor Details
#initialize(io, opt = {}) ⇒ Connection
Returns 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 @threadgroup = ThreadGroup.new @binddn = nil @version = 3 @logger = @opt[:logger] @ssl = false startssl if @opt[:ssl_on_connect] end |
Instance Attribute Details
#binddn ⇒ Object (readonly)
Returns the value of attribute binddn.
16 17 18 |
# File 'lib/ldap/server/connection.rb', line 16 def binddn @binddn end |
#opt ⇒ Object (readonly)
Returns the value of attribute opt.
16 17 18 |
# File 'lib/ldap/server/connection.rb', line 16 def opt @opt end |
#version ⇒ Object (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
#abandon(messageID) ⇒ Object
204 205 206 207 208 209 |
# File 'lib/ldap/server/connection.rb', line 204 def abandon() @mutex.synchronize do thread = @threadgroup.list.find { |t| t[:messageId] == } thread.raise LDAP::Abandon if thread end end |
#abandon_all ⇒ Object
211 212 213 214 215 216 217 |
# File 'lib/ldap/server/connection.rb', line 211 def abandon_all @mutex.synchronize do @threadgroup.list.each do |thread| thread.raise LDAP::Abandon end end end |
#ber_read(io) ⇒ Object
Read one ASN1 element from the given stream. Return String containing the raw element.
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 |
# File 'lib/ldap/server/connection.rb', line 57 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 |
#debug(msg) ⇒ Object
35 36 37 |
# File 'lib/ldap/server/connection.rb', line 35 def debug msg log msg, Logger::DEBUG end |
#handle_requests ⇒ Object
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 |
# File 'lib/ldap/server/connection.rb', line 95 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 start_op(,protocolOp,controls,:do_search) when 6 # ModifyRequest start_op(,protocolOp,controls,:do_modify) when 8 # AddRequest start_op(,protocolOp,controls,:do_add) when 10 # DelRequest start_op(,protocolOp,controls,:do_del) when 12 # ModifyDNRequest start_op(,protocolOp,controls,:do_modifydn) when 14 # CompareRequest start_op(,protocolOp,controls,:do_compare) when 16 # AbandonRequest abandon() 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 |
#log(msg, severity = Logger::INFO) ⇒ Object
31 32 33 |
# File 'lib/ldap/server/connection.rb', line 31 def log(msg, severity = Logger::INFO) @logger.add(severity, msg, @io.peeraddr[3]) end |
#log_exception(e) ⇒ Object
39 40 41 |
# File 'lib/ldap/server/connection.rb', line 39 def log_exception(e) log "#{e}: #{e.backtrace.join("\n\tfrom ")}", Logger::ERROR end |
#send_notice_of_disconnection(resultCode, errorMessage = "") ⇒ Object
243 244 245 246 247 248 |
# File 'lib/ldap/server/connection.rb', line 243 def send_notice_of_disconnection(resultCode, errorMessage="") send_unsolicited_notification(resultCode, :errorMessage=>errorMessage, :responseName=>"1.3.6.1.4.1.1466.20036" ) end |
#send_unsolicited_notification(resultCode, opt = {}) ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/ldap/server/connection.rb', line 219 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 |
#start_op(messageId, protocolOp, controls, meth) ⇒ Object
Start an operation in a Thread. Add this to a ThreadGroup to allow the operation to be abandoned later.
When the thread terminates, it automatically drops out of the group.
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 messageId in @threadgroup.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/ldap/server/connection.rb', line 175 def start_op(,protocolOp,controls,meth) operationClass = @opt[:operation_class] ocArgs = @opt[:operation_args] || [] thr = Thread.new do begin operationClass.new(self,,*ocArgs). send(meth,protocolOp,controls) rescue Exception => e log_exception e end end thr[:messageId] = @threadgroup.add(thr) end |
#startssl ⇒ Object
:yields:
43 44 45 46 47 48 49 50 51 52 |
# File 'lib/ldap/server/connection.rb', line 43 def startssl # :yields: @mutex.synchronize do raise LDAP::ResultError::OperationsError if @ssl or @threadgroup.list.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 |
#write(data) ⇒ Object
190 191 192 193 194 195 |
# File 'lib/ldap/server/connection.rb', line 190 def write(data) @mutex.synchronize do @io.write(data) @io.flush end end |
#writelock ⇒ Object
197 198 199 200 201 202 |
# File 'lib/ldap/server/connection.rb', line 197 def writelock @mutex.synchronize do yield @io @io.flush end end |