ruby-dbus 0.16.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +288 -3
  3. data/README.md +3 -5
  4. data/Rakefile +27 -12
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +109 -10
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/no-bus/pulseaudio.rb +50 -0
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +3 -3
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +6 -3
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +10 -1
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +307 -217
  22. data/lib/dbus/bus.rb +155 -588
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/connection.rb +363 -0
  25. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  26. data/lib/dbus/data.rb +821 -0
  27. data/lib/dbus/emits_changed_signal.rb +83 -0
  28. data/lib/dbus/error.rb +4 -2
  29. data/lib/dbus/introspect.rb +133 -31
  30. data/lib/dbus/logger.rb +6 -3
  31. data/lib/dbus/main.rb +66 -0
  32. data/lib/dbus/marshall.rb +247 -296
  33. data/lib/dbus/matchrule.rb +16 -16
  34. data/lib/dbus/message.rb +66 -57
  35. data/lib/dbus/message_queue.rb +40 -25
  36. data/lib/dbus/node_tree.rb +105 -0
  37. data/lib/dbus/object.rb +442 -33
  38. data/lib/dbus/object_manager.rb +61 -0
  39. data/lib/dbus/object_path.rb +11 -6
  40. data/lib/dbus/object_server.rb +149 -0
  41. data/lib/dbus/org.freedesktop.DBus.xml +97 -0
  42. data/lib/dbus/platform.rb +26 -0
  43. data/lib/dbus/proxy_object.rb +30 -11
  44. data/lib/dbus/proxy_object_factory.rb +13 -7
  45. data/lib/dbus/proxy_object_interface.rb +63 -30
  46. data/lib/dbus/proxy_service.rb +107 -0
  47. data/lib/dbus/raw_message.rb +91 -0
  48. data/lib/dbus/type.rb +319 -86
  49. data/lib/dbus/xml.rb +40 -19
  50. data/lib/dbus.rb +28 -17
  51. data/ruby-dbus.gemspec +13 -6
  52. data/spec/async_spec.rb +2 -0
  53. data/spec/auth_spec.rb +225 -0
  54. data/spec/binding_spec.rb +2 -0
  55. data/spec/bus_and_xml_backend_spec.rb +5 -1
  56. data/spec/bus_connection_spec.rb +93 -0
  57. data/spec/bus_name_spec.rb +3 -1
  58. data/spec/bus_spec.rb +2 -0
  59. data/spec/byte_array_spec.rb +2 -0
  60. data/spec/client_robustness_spec.rb +4 -2
  61. data/spec/connection_spec.rb +37 -0
  62. data/spec/coverage_helper.rb +39 -0
  63. data/spec/data/marshall.yaml +1667 -0
  64. data/spec/data_spec.rb +673 -0
  65. data/spec/dbus_spec.rb +22 -0
  66. data/spec/emits_changed_signal_spec.rb +58 -0
  67. data/spec/err_msg_spec.rb +2 -0
  68. data/spec/introspect_xml_parser_spec.rb +2 -0
  69. data/spec/introspection_spec.rb +2 -0
  70. data/spec/main_loop_spec.rb +17 -1
  71. data/spec/message_spec.rb +21 -0
  72. data/spec/mock-service/cockpit-dbustests.rb +29 -0
  73. data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
  74. data/spec/mock-service/org.ruby.service.service +4 -0
  75. data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
  76. data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
  77. data/spec/node_spec.rb +65 -0
  78. data/spec/object_manager_spec.rb +33 -0
  79. data/spec/object_path_spec.rb +3 -0
  80. data/spec/object_server_spec.rb +138 -0
  81. data/spec/object_spec.rb +194 -0
  82. data/spec/packet_marshaller_spec.rb +41 -0
  83. data/spec/packet_unmarshaller_spec.rb +248 -0
  84. data/spec/platform_spec.rb +14 -0
  85. data/spec/property_spec.rb +214 -12
  86. data/spec/proxy_object_interface_spec.rb +35 -0
  87. data/spec/proxy_object_spec.rb +2 -0
  88. data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
  89. data/spec/raw_message_spec.rb +32 -0
  90. data/spec/server_robustness_spec.rb +20 -6
  91. data/spec/session_bus_spec.rb +36 -1
  92. data/spec/session_bus_spec_manual.rb +2 -0
  93. data/spec/signal_spec.rb +16 -3
  94. data/spec/spec_helper.rb +25 -33
  95. data/spec/thread_safety_spec.rb +55 -12
  96. data/spec/tools/dbus-launch-simple +9 -6
  97. data/spec/tools/dbus-limited-session.conf +32 -0
  98. data/spec/tools/test_env +26 -6
  99. data/spec/type_spec.rb +214 -6
  100. data/spec/value_spec.rb +16 -1
  101. data/spec/variant_spec.rb +4 -2
  102. data/spec/zzz_quit_spec.rb +16 -0
  103. metadata +92 -20
  104. data/examples/gdbus/gdbus +0 -257
  105. data/examples/gdbus/gdbus.glade +0 -98
  106. data/examples/gdbus/launch.sh +0 -4
  107. data/spec/server_spec.rb +0 -53
  108. data/spec/tools/test_server +0 -39
data/lib/dbus/auth.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of the ruby-dbus project
2
4
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
5
  #
@@ -6,262 +8,350 @@
6
8
  # License, version 2.1 as published by the Free Software Foundation.
7
9
  # See the file "COPYING" for the exact licensing terms.
8
10
 
9
- require "rbconfig"
10
-
11
11
  module DBus
12
12
  # Exception raised when authentication fails somehow.
13
- class AuthenticationFailed < Exception
13
+ class AuthenticationFailed < StandardError
14
14
  end
15
15
 
16
- # = General class for authentication.
17
- class Authenticator
18
- # Returns the name of the authenticator.
19
- def name
20
- self.class.to_s.upcase.sub(/.*::/, "")
21
- end
22
- end
16
+ # The Authentication Protocol.
17
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
18
+ #
19
+ # @api private
20
+ module Authentication
21
+ # Base class of authentication mechanisms
22
+ class Mechanism
23
+ # @!method call(challenge)
24
+ # @abstract
25
+ # Replies to server *challenge*, or sends an initial response if the challenge is `nil`.
26
+ # @param challenge [String,nil]
27
+ # @return [Array(Symbol,String)] pair [action, response], where
28
+ # - [:MechContinue, response] caller should send "DATA response" and go to :WaitingForData
29
+ # - [:MechOk, response] caller should send "DATA response" and go to :WaitingForOk
30
+ # - [:MechError, message] caller should send "ERROR message" and go to :WaitingForData
23
31
 
24
- # = Anonymous authentication class
25
- class Anonymous < Authenticator
26
- def authenticate
27
- "527562792044427573" # Hex encoded version of "Ruby DBus"
32
+ # Uppercase mechanism name, as sent to the server
33
+ # @return [String]
34
+ def name
35
+ self.class.to_s.upcase.sub(/.*::/, "")
36
+ end
28
37
  end
29
- end
30
38
 
31
- # = External authentication class
32
- #
33
- # Class for 'external' type authentication.
34
- class External < Authenticator
35
- # Performs the authentication.
36
- def authenticate
37
- # Take the user id (eg integer 1000) make a string out of it "1000", take
38
- # each character and determin hex value "1" => 0x31, "0" => 0x30. You
39
- # obtain for "1000" => 31303030 This is what the server is expecting.
40
- # Why? I dunno. How did I come to that conclusion? by looking at rbus
41
- # code. I have no idea how he found that out.
42
- Process.uid.to_s.split(//).map { |d| d.ord.to_s(16) }.join
39
+ # Anonymous authentication class.
40
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-anonymous
41
+ class Anonymous < Mechanism
42
+ def call(_challenge)
43
+ [:MechOk, "Ruby DBus"]
44
+ end
43
45
  end
44
- end
45
46
 
46
- # = Authentication class using SHA1 crypto algorithm
47
- #
48
- # Class for 'CookieSHA1' type authentication.
49
- # Implements the AUTH DBUS_COOKIE_SHA1 mechanism.
50
- class DBusCookieSHA1 < Authenticator
51
- # the autenticate method (called in stage one of authentification)
52
- def authenticate
53
- require "etc"
54
- # number of retries we have for auth
55
- @retries = 1
56
- hex_encode(Etc.getlogin).to_s # server expects it to be binary
47
+ # Class for 'external' type authentication.
48
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-external
49
+ class External < Mechanism
50
+ # Performs the authentication.
51
+ def call(_challenge)
52
+ [:MechOk, Process.uid.to_s]
53
+ end
57
54
  end
58
55
 
59
- # returns the modules name
60
- def name
61
- "DBUS_COOKIE_SHA1"
56
+ # A variant of EXTERNAL that doesn't say our UID.
57
+ # Seen busctl do this and it worked across a container boundary.
58
+ class ExternalWithoutUid < External
59
+ def name
60
+ "EXTERNAL"
61
+ end
62
+
63
+ def call(_challenge)
64
+ [:MechContinue, nil]
65
+ end
62
66
  end
63
67
 
64
- # handles the interesting crypto stuff, check the rbus-project for more info: http://rbus.rubyforge.org/
65
- def data(hexdata)
66
- require "digest/sha1"
67
- data = hex_decode(hexdata)
68
- # name of cookie file, id of cookie in file, servers random challenge
69
- context, id, s_challenge = data.split(" ")
70
- # Random client challenge
71
- c_challenge = 1.upto(s_challenge.bytesize / 2).map { rand(255).to_s }.join
72
- # Search cookie file for id
73
- path = File.join(ENV["HOME"], ".dbus-keyrings", context)
74
- DBus.logger.debug "path: #{path.inspect}"
75
- File.foreach(path) do |line|
76
- if line.index(id).zero?
77
- # Right line of file, read cookie
78
- cookie = line.split(" ")[2].chomp
79
- DBus.logger.debug "cookie: #{cookie.inspect}"
80
- # Concatenate and encrypt
81
- to_encrypt = [s_challenge, c_challenge, cookie].join(":")
82
- sha = Digest::SHA1.hexdigest(to_encrypt)
83
- # the almighty tcp server wants everything hex encoded
84
- hex_response = hex_encode("#{c_challenge} #{sha}")
85
- # Return response
86
- response = [:AuthOk, hex_response]
87
- return response
88
- end
68
+ # Implements the AUTH DBUS_COOKIE_SHA1 mechanism.
69
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha
70
+ class DBusCookieSHA1 < Mechanism
71
+ # returns the modules name
72
+ def name
73
+ "DBUS_COOKIE_SHA1"
89
74
  end
90
- # a little rescue magic
91
- unless @retries <= 0
75
+
76
+ # First we are called with nil and we reply with our username.
77
+ # Then we prove that we can read that user's cookie file.
78
+ def call(challenge)
79
+ if challenge.nil?
80
+ require "etc"
81
+ # number of retries we have for auth
82
+ @retries = 1
83
+ return [:MechContinue, Etc.getlogin]
84
+ end
85
+
86
+ require "digest/sha1"
87
+ # name of cookie file, id of cookie in file, servers random challenge
88
+ context, id, s_challenge = challenge.split(" ")
89
+ # Random client challenge
90
+ c_challenge = 1.upto(s_challenge.bytesize / 2).map { rand(255).to_s }.join
91
+ # Search cookie file for id
92
+ path = File.join(ENV["HOME"], ".dbus-keyrings", context)
93
+ DBus.logger.debug "path: #{path.inspect}"
94
+ File.foreach(path) do |line|
95
+ if line.start_with?(id)
96
+ # Right line of file, read cookie
97
+ cookie = line.split(" ")[2].chomp
98
+ DBus.logger.debug "cookie: #{cookie.inspect}"
99
+ # Concatenate and encrypt
100
+ to_encrypt = [s_challenge, c_challenge, cookie].join(":")
101
+ sha = Digest::SHA1.hexdigest(to_encrypt)
102
+ # Return response
103
+ response = [:MechOk, "#{c_challenge} #{sha}"]
104
+ return response
105
+ end
106
+ end
107
+ return if @retries <= 0
108
+
109
+ # a little rescue magic
92
110
  puts "ERROR: Could not auth, will now exit."
93
111
  puts "ERROR: Unable to locate cookie, retry in 1 second."
94
112
  @retries -= 1
95
113
  sleep 1
96
- data(hexdata)
114
+ call(challenge)
97
115
  end
98
116
  end
99
117
 
100
- # encode plain to hex
101
- def hex_encode(plain)
102
- return nil if plain.nil?
103
- plain.to_s.unpack("H*")[0]
104
- end
118
+ # Declare client state transitions, for ease of code reading.
119
+ # It is just a pair.
120
+ NextState = Struct.new(:state, :command_words)
105
121
 
106
- # decode hex to plain
107
- def hex_decode(encoded)
108
- encoded.scan(/[[:xdigit:]]{2}/).map { |h| h.hex.chr }.join
109
- end
110
- end # DBusCookieSHA1 class ends here
122
+ # Authenticates the connection before messages can be exchanged.
123
+ class Client
124
+ # @return [Boolean] have we negotiated Unix file descriptor passing
125
+ # NOTE: not implemented yet in upper layers
126
+ attr_reader :unix_fd
111
127
 
112
- # Note: this following stuff is tested with External authenticator only!
128
+ # @return [String]
129
+ attr_reader :address_uuid
113
130
 
114
- # = Authentication client class.
115
- #
116
- # Class tha performs the actional authentication.
117
- class Client
118
- # Create a new authentication client.
119
- def initialize(socket)
120
- @socket = socket
121
- @state = nil
122
- @auth_list = [External, DBusCookieSHA1, Anonymous]
123
- end
131
+ # Create a new authentication client.
132
+ # @param mechs [Array<Mechanism,Class>,nil] custom list of auth Mechanism objects or classes
133
+ def initialize(socket, mechs = nil)
134
+ @unix_fd = false
135
+ @address_uuid = nil
124
136
 
125
- # Start the authentication process.
126
- def authenticate
127
- if RbConfig::CONFIG["target_os"] =~ /freebsd/
128
- @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""])
129
- else
130
- @socket.write(0.chr)
131
- end
132
- next_authenticator
133
- @state = :Starting
134
- while @state != :Authenticated
135
- r = next_state
136
- return r if !r
137
+ @socket = socket
138
+ @state = nil
139
+ @auth_list = mechs || [
140
+ External,
141
+ DBusCookieSHA1,
142
+ ExternalWithoutUid,
143
+ Anonymous
144
+ ]
137
145
  end
138
- true
139
- end
140
146
 
141
- ##########
147
+ # Start the authentication process.
148
+ # @return [void]
149
+ # @raise [AuthenticationFailed]
150
+ def authenticate
151
+ DBus.logger.debug "Authenticating"
152
+ send_nul_byte
142
153
 
143
- private
154
+ use_next_mechanism
144
155
 
145
- ##########
156
+ @state, command = next_state_via_mechanism.to_a
157
+ send(command)
146
158
 
147
- # Send an authentication method _meth_ with arguments _args_ to the
148
- # server.
149
- def send(meth, *args)
150
- o = ([meth] + args).join(" ")
151
- @socket.write(o + "\r\n")
152
- end
159
+ loop do
160
+ DBus.logger.debug "auth STATE: #{@state}"
161
+ words = next_msg
153
162
 
154
- # Try authentication using the next authenticator.
155
- def next_authenticator
156
- raise AuthenticationFailed if @auth_list.empty?
157
- @authenticator = @auth_list.shift.new
158
- auth_msg = ["AUTH", @authenticator.name, @authenticator.authenticate]
159
- DBus.logger.debug "auth_msg: #{auth_msg.inspect}"
160
- send(auth_msg)
161
- rescue AuthenticationFailed
162
- @socket.close
163
- raise
164
- end
163
+ @state, command = next_state(words).to_a
164
+ break if [:TerminatedOk, :TerminatedError].include? @state
165
+
166
+ send(command)
167
+ end
165
168
 
166
- # Read data (a buffer) from the bus until CR LF is encountered.
167
- # Return the buffer without the CR LF characters.
168
- def next_msg
169
- data = ""
170
- crlf = "\r\n"
171
- left = 1024 # 1024 byte, no idea if it's ever getting bigger
172
- while left > 0
173
- buf = @socket.read(left > 1 ? 1 : left)
174
- break if buf.nil?
175
- left -= buf.bytesize
176
- data += buf
177
- break if data.include? crlf # crlf means line finished, the TCP socket keeps on listening, so we break
169
+ raise AuthenticationFailed, command.first if @state == :TerminatedError
170
+
171
+ send("BEGIN")
178
172
  end
179
- readline = data.chomp.split(" ")
180
- DBus.logger.debug "readline: #{readline.inspect}"
181
- readline
182
- end
183
173
 
184
- # # Read data (a buffer) from the bus until CR LF is encountered.
185
- # # Return the buffer without the CR LF characters.
186
- # def next_msg
187
- # @socket.readline.chomp.split(" ")
188
- # end
189
-
190
- # Try to reach the next state based on the current state.
191
- def next_state
192
- msg = next_msg
193
- if @state == :Starting
194
- DBus.logger.debug ":Starting msg: #{msg[0].inspect}"
195
- case msg[0]
196
- when "OK"
197
- @state = :WaitingForOk
198
- when "CONTINUE"
199
- @state = :WaitingForData
200
- when "REJECTED" # needed by tcp, unix-path/abstract doesn't get here
201
- @state = :WaitingForData
174
+ ##########
175
+
176
+ private
177
+
178
+ ##########
179
+
180
+ # The authentication protocol requires a nul byte
181
+ # that may carry credentials.
182
+ # @return [void]
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)
202
188
  end
203
189
  end
204
- DBus.logger.debug "state: #{@state}"
205
- case @state
206
- when :WaitingForData
207
- DBus.logger.debug ":WaitingForData msg: #{msg[0].inspect}"
208
- case msg[0]
209
- when "DATA"
210
- chall = msg[1]
211
- resp, chall = @authenticator.data(chall)
212
- DBus.logger.debug ":WaitingForData/DATA resp: #{resp.inspect}"
213
- case resp
214
- when :AuthContinue
215
- send("DATA", chall)
216
- @state = :WaitingForData
217
- when :AuthOk
218
- send("DATA", chall)
219
- @state = :WaitingForOk
220
- when :AuthError
221
- send("ERROR")
222
- @state = :WaitingForData
223
- end
224
- when "REJECTED"
225
- next_authenticator
226
- @state = :WaitingForData
227
- when "ERROR"
228
- send("CANCEL")
229
- @state = :WaitingForReject
230
- when "OK"
231
- send("BEGIN")
232
- @state = :Authenticated
233
- else
234
- send("ERROR")
235
- @state = :WaitingForData
190
+
191
+ # encode plain to hex
192
+ # @param plain [String,nil]
193
+ # @return [String,nil]
194
+ def hex_encode(plain)
195
+ return nil if plain.nil?
196
+
197
+ plain.unpack1("H*")
198
+ end
199
+
200
+ # decode hex to plain
201
+ # @param encoded [String,nil]
202
+ # @return [String,nil]
203
+ def hex_decode(encoded)
204
+ return nil if encoded.nil?
205
+
206
+ [encoded].pack("H*")
207
+ end
208
+
209
+ # Send a string to the socket; good place for test mocks.
210
+ def write_line(str)
211
+ DBus.logger.debug "auth_write: #{str.inspect}"
212
+ @socket.write(str)
213
+ end
214
+
215
+ # Send *words* to the server as a single CRLF terminated string.
216
+ # @param words [Array<String>,String]
217
+ def send(words)
218
+ joined = Array(words).compact.join(" ")
219
+ write_line("#{joined}\r\n")
220
+ end
221
+
222
+ # Try authentication using the next mechanism.
223
+ # @raise [AuthenticationFailed] if there are no more left
224
+ # @return [void]
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
235
+
236
+ # Read data (a buffer) from the bus until CR LF is encountered.
237
+ # Return the buffer without the CR LF characters.
238
+ # @return [Array<String>] received words
239
+ def next_msg
240
+ read_line.chomp.split(" ")
241
+ end
242
+
243
+ # Read a line from the socket; good place for test mocks.
244
+ # @return [String] CRLF (\r\n) terminated
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
236
257
  end
237
- when :WaitingForOk
238
- DBus.logger.debug ":WaitingForOk msg: #{msg[0].inspect}"
239
- case msg[0]
240
- when "OK"
241
- send("BEGIN")
242
- @state = :Authenticated
243
- when "REJECT"
244
- next_authenticator
245
- @state = :WaitingForData
246
- when "DATA", "ERROR"
247
- send("CANCEL")
248
- @state = :WaitingForReject
258
+ DBus.logger.debug "auth_read: #{data.inspect}"
259
+ data
260
+ end
261
+
262
+ # # Read data (a buffer) from the bus until CR LF is encountered.
263
+ # # Return the buffer without the CR LF characters.
264
+ # def next_msg
265
+ # @socket.readline.chomp.split(" ")
266
+ # end
267
+
268
+ # @param hex_challenge [String,nil] (nil when the server said "DATA\r\n")
269
+ # @param use_data [Boolean] say DATA instead of AUTH
270
+ # @return [NextState]
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)])
249
286
  else
250
- send("ERROR")
251
- @state = :WaitingForOk
287
+ raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \
288
+ "from our mechanism #{@mechanism.inspect}"
252
289
  end
253
- when :WaitingForReject
254
- DBus.logger.debug ":WaitingForReject msg: #{msg[0].inspect}"
255
- case msg[0]
256
- when "REJECT"
257
- next_authenticator
258
- @state = :WaitingForOk
290
+ end
291
+
292
+ # Try to reach the next state based on the current state.
293
+ # @param received_words [Array<String>]
294
+ # @return [NextState]
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
259
351
  else
260
- @socket.close
261
- return false
352
+ raise "Internal error: unhandled state #{@state.inspect}"
262
353
  end
263
354
  end
264
- true
265
- end # def next_state
266
- end # class Client
267
- end # module D-Bus
355
+ end
356
+ end
357
+ end