class DBus::Authentication::Client
Authenticates the connection before messages can be exchanged.
Attributes
@return [String]
@return [Boolean] have we negotiated Unix file descriptor passing NOTE: not implemented yet in upper layers
Public Class Methods
Create a new authentication client. @param mechs [Array<Mechanism,Class>,nil] custom list of auth Mechanism objects or classes
# File lib/dbus/auth.rb 133 def initialize(socket, mechs = nil) 134 @unix_fd = false 135 @address_uuid = nil 136 137 @socket = socket 138 @state = nil 139 @auth_list = mechs || [ 140 External, 141 DBusCookieSHA1, 142 ExternalWithoutUid, 143 Anonymous 144 ] 145 end
Public Instance Methods
Start the authentication process. @return [void] @raise [AuthenticationFailed]
# File lib/dbus/auth.rb 150 def authenticate 151 DBus.logger.debug "Authenticating" 152 send_nul_byte 153 154 use_next_mechanism 155 156 @state, command = next_state_via_mechanism.to_a 157 send(command) 158 159 loop do 160 DBus.logger.debug "auth STATE: #{@state}" 161 words = next_msg 162 163 @state, command = next_state(words).to_a 164 break if [:TerminatedOk, :TerminatedError].include? @state 165 166 send(command) 167 end 168 169 raise AuthenticationFailed, command.first if @state == :TerminatedError 170 171 send("BEGIN") 172 end
Private Instance Methods
decode hex to plain @param encoded [String,nil] @return [String,nil]
# File lib/dbus/auth.rb 203 def hex_decode(encoded) 204 return nil if encoded.nil? 205 206 [encoded].pack("H*") 207 end
encode plain to hex @param plain [String,nil] @return [String,nil]
# File lib/dbus/auth.rb 194 def hex_encode(plain) 195 return nil if plain.nil? 196 197 plain.unpack1("H*") 198 end
Read data (a buffer) from the bus until CR LF is encountered. Return the buffer without the CR LF characters. @return [Array<String>] received words
# File lib/dbus/auth.rb 239 def next_msg 240 read_line.chomp.split(" ") 241 end
Try to reach the next state based on the current state. @param received_words [Array<String>] @return [NextState]
# File lib/dbus/auth.rb 295 def next_state(received_words) 296 msg = received_words 297 298 case @state 299 when :WaitingForData 300 case msg[0] 301 when "DATA" 302 next_state_via_mechanism(msg[1], use_data: true) 303 when "REJECTED" 304 use_next_mechanism 305 next_state_via_mechanism 306 when "ERROR" 307 NextState.new(:WaitingForReject, ["CANCEL"]) 308 when "OK" 309 @address_uuid = msg[1] 310 # NextState.new(:TerminatedOk, []) 311 NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"]) 312 else 313 NextState.new(:WaitingForData, ["ERROR"]) 314 end 315 when :WaitingForOk 316 case msg[0] 317 when "OK" 318 @address_uuid = msg[1] 319 # NextState.new(:TerminatedOk, []) 320 NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"]) 321 when "REJECTED" 322 use_next_mechanism 323 next_state_via_mechanism 324 when "DATA", "ERROR" 325 NextState.new(:WaitingForReject, ["CANCEL"]) 326 else 327 # we don't understand server's response but still wait for a successful auth completion 328 NextState.new(:WaitingForOk, ["ERROR"]) 329 end 330 when :WaitingForReject 331 case msg[0] 332 when "REJECTED" 333 use_next_mechanism 334 next_state_via_mechanism 335 else 336 # TODO: spec says to close socket, clarify 337 NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} when expecting REJECTED"]) 338 end 339 when :WaitingForAgreeUnixFD 340 case msg[0] 341 when "AGREE_UNIX_FD" 342 @unix_fd = true 343 NextState.new(:TerminatedOk, []) 344 when "ERROR" 345 @unix_fd = false 346 NextState.new(:TerminatedOk, []) 347 else 348 # TODO: spec says to close socket, clarify 349 NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} to NEGOTIATE_UNIX_FD"]) 350 end 351 else 352 raise "Internal error: unhandled state #{@state.inspect}" 353 end 354 end
@param hex_challenge [String,nil] (nil when the server said “DATArn”) @param use_data [Boolean] say DATA instead of AUTH @return [NextState]
# File lib/dbus/auth.rb 271 def next_state_via_mechanism(hex_challenge = nil, use_data: false) 272 challenge = hex_decode(hex_challenge) 273 274 action, response = @mechanism.call(challenge) 275 DBus.logger.debug "auth mechanism action: #{action.inspect}" 276 277 command = use_data ? ["DATA"] : ["AUTH", @mechanism.name] 278 279 case action 280 when :MechError 281 NextState.new(:WaitingForData, ["ERROR", response]) 282 when :MechContinue 283 NextState.new(:WaitingForData, command + [hex_encode(response)]) 284 when :MechOk 285 NextState.new(:WaitingForOk, command + [hex_encode(response)]) 286 else 287 raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \ 288 "from our mechanism #{@mechanism.inspect}" 289 end 290 end
Read a line from the socket; good place for test mocks. @return [String] CRLF (rn) terminated
# File lib/dbus/auth.rb 245 def read_line 246 # TODO: probably can simply call @socket.readline 247 data = "" 248 crlf = "\r\n" 249 left = 1024 # 1024 byte, no idea if it's ever getting bigger 250 while left.positive? 251 buf = @socket.read(left > 1 ? 1 : left) 252 break if buf.nil? 253 254 left -= buf.bytesize 255 data += buf 256 break if data.include? crlf # crlf means line finished, the TCP socket keeps on listening, so we break 257 end 258 DBus.logger.debug "auth_read: #{data.inspect}" 259 data 260 end
Send words to the server as a single CRLF terminated string. @param words [Array<String>,String]
# File lib/dbus/auth.rb 217 def send(words) 218 joined = Array(words).compact.join(" ") 219 write_line("#{joined}\r\n") 220 end
The authentication protocol requires a nul byte that may carry credentials. @return [void]
# File lib/dbus/auth.rb 183 def send_nul_byte 184 if Platform.freebsd? 185 @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""]) 186 else 187 @socket.write(0.chr) 188 end 189 end
Try authentication using the next mechanism. @raise [AuthenticationFailed] if there are no more left @return [void]
# File lib/dbus/auth.rb 225 def use_next_mechanism 226 raise AuthenticationFailed, "Authentication mechanisms exhausted" if @auth_list.empty? 227 228 @mechanism = @auth_list.shift 229 @mechanism = @mechanism.new if @mechanism.is_a? Class 230 rescue AuthenticationFailed 231 # TODO: make this caller's responsibility 232 @socket.close 233 raise 234 end
Send a string to the socket; good place for test mocks.
# File lib/dbus/auth.rb 210 def write_line(str) 211 DBus.logger.debug "auth_write: #{str.inspect}" 212 @socket.write(str) 213 end