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, line 133 def initialize(socket, mechs = nil) @unix_fd = false @address_uuid = nil @socket = socket @state = nil @auth_list = mechs || [ External, DBusCookieSHA1, ExternalWithoutUid, Anonymous ] end
Public Instance Methods
Start the authentication process. @return [void] @raise [AuthenticationFailed]
# File lib/dbus/auth.rb, line 150 def authenticate DBus.logger.debug "Authenticating" send_nul_byte use_next_mechanism @state, command = next_state_via_mechanism.to_a send(command) loop do DBus.logger.debug "auth STATE: #{@state}" words = next_msg @state, command = next_state(words).to_a break if [:TerminatedOk, :TerminatedError].include? @state send(command) end raise AuthenticationFailed, command.first if @state == :TerminatedError send("BEGIN") end
Private Instance Methods
decode hex to plain @param encoded [String,nil] @return [String,nil]
# File lib/dbus/auth.rb, line 203 def hex_decode(encoded) return nil if encoded.nil? [encoded].pack("H*") end
encode plain to hex @param plain [String,nil] @return [String,nil]
# File lib/dbus/auth.rb, line 194 def hex_encode(plain) return nil if plain.nil? plain.unpack1("H*") 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, line 239 def next_msg read_line.chomp.split(" ") end
Try to reach the next state based on the current state. @param received_words [Array<String>] @return [NextState]
# File lib/dbus/auth.rb, line 295 def next_state(received_words) msg = received_words case @state when :WaitingForData case msg[0] when "DATA" next_state_via_mechanism(msg[1], use_data: true) when "REJECTED" use_next_mechanism next_state_via_mechanism when "ERROR" NextState.new(:WaitingForReject, ["CANCEL"]) when "OK" @address_uuid = msg[1] # NextState.new(:TerminatedOk, []) NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"]) else NextState.new(:WaitingForData, ["ERROR"]) end when :WaitingForOk case msg[0] when "OK" @address_uuid = msg[1] # NextState.new(:TerminatedOk, []) NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"]) when "REJECTED" use_next_mechanism next_state_via_mechanism when "DATA", "ERROR" NextState.new(:WaitingForReject, ["CANCEL"]) else # we don't understand server's response but still wait for a successful auth completion NextState.new(:WaitingForOk, ["ERROR"]) end when :WaitingForReject case msg[0] when "REJECTED" use_next_mechanism next_state_via_mechanism else # TODO: spec says to close socket, clarify NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} when expecting REJECTED"]) end when :WaitingForAgreeUnixFD case msg[0] when "AGREE_UNIX_FD" @unix_fd = true NextState.new(:TerminatedOk, []) when "ERROR" @unix_fd = false NextState.new(:TerminatedOk, []) else # TODO: spec says to close socket, clarify NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} to NEGOTIATE_UNIX_FD"]) end else raise "Internal error: unhandled state #{@state.inspect}" end 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, line 271 def next_state_via_mechanism(hex_challenge = nil, use_data: false) challenge = hex_decode(hex_challenge) action, response = @mechanism.call(challenge) DBus.logger.debug "auth mechanism action: #{action.inspect}" command = use_data ? ["DATA"] : ["AUTH", @mechanism.name] case action when :MechError NextState.new(:WaitingForData, ["ERROR", response]) when :MechContinue NextState.new(:WaitingForData, command + [hex_encode(response)]) when :MechOk NextState.new(:WaitingForOk, command + [hex_encode(response)]) else raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \ "from our mechanism #{@mechanism.inspect}" end end
Read a line from the socket; good place for test mocks. @return [String] CRLF (rn) terminated
# File lib/dbus/auth.rb, line 245 def read_line # TODO: probably can simply call @socket.readline data = "" crlf = "\r\n" left = 1024 # 1024 byte, no idea if it's ever getting bigger while left.positive? buf = @socket.read(left > 1 ? 1 : left) break if buf.nil? left -= buf.bytesize data += buf break if data.include? crlf # crlf means line finished, the TCP socket keeps on listening, so we break end DBus.logger.debug "auth_read: #{data.inspect}" data end
Send words to the server as a single CRLF terminated string. @param words [Array<String>,String]
# File lib/dbus/auth.rb, line 217 def send(words) joined = Array(words).compact.join(" ") write_line("#{joined}\r\n") end
The authentication protocol requires a nul byte that may carry credentials. @return [void]
# File lib/dbus/auth.rb, line 183 def send_nul_byte if Platform.freebsd? @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""]) else @socket.write(0.chr) end end
Try authentication using the next mechanism. @raise [AuthenticationFailed] if there are no more left @return [void]
# File lib/dbus/auth.rb, line 225 def use_next_mechanism raise AuthenticationFailed, "Authentication mechanisms exhausted" if @auth_list.empty? @mechanism = @auth_list.shift @mechanism = @mechanism.new if @mechanism.is_a? Class rescue AuthenticationFailed # TODO: make this caller's responsibility @socket.close raise end
Send a string to the socket; good place for test mocks.
# File lib/dbus/auth.rb, line 210 def write_line(str) DBus.logger.debug "auth_write: #{str.inspect}" @socket.write(str) end