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