# class DBus::Authentication::Client

Authenticates the connection before messages can be exchanged.

### Attributes

address_uuid[R]

@return [String]

unix_fd[R]

@return [Boolean] have we negotiated Unix file descriptor passing NOTE: not implemented yet in upper layers

### Public Class Methods

new(socket, mechs = nil) click to toggle source

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

authenticate() click to toggle source

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

hex_decode(encoded) click to toggle source

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

hex_encode(plain) click to toggle source

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

next_msg() click to toggle source

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

next_state(received_words) click to toggle source

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

next_state_via_mechanism(hex_challenge = nil, use_data: false) click to toggle source

@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_line() click to toggle source

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) click to toggle source

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

send_nul_byte() click to toggle source

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

use_next_mechanism() click to toggle source

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

write_line(str) click to toggle source

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