ruby-dbus 0.18.1 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07f1f2fd26d13923b2601c9c3f29ccf57afd42c0348b937bceea0804eece6d66
4
- data.tar.gz: 5ad80792234951e7ed422b6eba16e7f6e55b41f24a116ef40afaa7f32cdeef74
3
+ metadata.gz: d52907f5f6be32813466ae280cb99ff605d4195ca84396e97a90a7568fb579ec
4
+ data.tar.gz: 01bf87805f19878641a424f5c9ba21fcff637dcb6d4bbcdc230dda3713df8c80
5
5
  SHA512:
6
- metadata.gz: acbe18b2d9695f8959ef6d51a3871e989600291076d6eef870a9215768ed6c7afd599d3af2741e13a733fe2f5cc9f75fdf075a130cafd0a9144b4ca00bd80dc9
7
- data.tar.gz: 0363375dd8c4465aa2650649d0bd62a4f95431b78178edb624a84f5326e395476e0c54e4755b27fedc86caf94b7c059abbdc15af28832417ec1d3e4770ea9ae0
6
+ metadata.gz: 8009bcca0c66ddb5d5e9ca76a2fbc06b0408437ac3f5135d1378362f58520cbcb20c6d625d242e7c19ca1f679b4ab7b4e584e5f9d6c376eea90f57372f7f27aa
7
+ data.tar.gz: 0d8388654ae2ad5aeca7cf0eb6cec2fb3eb463ac43b85eb7a83bfbd32a011d65749b1a1195fcc4ae0f8c6a5591e49c0176a09c442c67a166ce2432bb30d704f7
data/NEWS.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Ruby D-Bus 0.20.0 - 2023-03-21
6
+
7
+ Features:
8
+ * For EXTERNAL authentication, try also without the user id, to work with
9
+ containers ([#126][]).
10
+ * Thread safety, as long as the non-main threads only send signals.
11
+
12
+ [#126]: https://github.com/mvidner/ruby-dbus/issues/126
13
+
14
+ ## Ruby D-Bus 0.19.0 - 2023-01-18
15
+
16
+ API:
17
+ * Added a ObjectManager mix-in to implement the service-side
18
+ [ObjectManager][objmgr] interface.
19
+
20
+ Bug fixes:
21
+ * dbus_attr_accessor and friends validate the signature ([#120][]).
22
+ * Declare the Introspectable interface in exported objects([#99][]).
23
+ * Do reply with an error when calling a nonexisting object
24
+ with an existing path prefix([#121][]).
25
+
26
+ [#120]: https://github.com/mvidner/ruby-dbus/issues/120
27
+ [#99]: https://github.com/mvidner/ruby-dbus/issues/99
28
+ [#121]: https://github.com/mvidner/ruby-dbus/issues/121
29
+ [objmgr]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
30
+
5
31
  ## Ruby D-Bus 0.18.1 - 2022-07-13
6
32
 
7
33
  No changes since 0.18.0.beta8.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.1
1
+ 0.20.0
@@ -6,7 +6,9 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
6
6
 
7
7
  require "dbus"
8
8
 
9
- bus = DBus::SystemBus.instance
9
+ busname = ARGV.fetch(0, "system")
10
+ bus = busname == "session" ? DBus::SessionBus.instance : DBus::SystemBus.instance
11
+
10
12
  driver_svc = bus["org.freedesktop.DBus"]
11
13
  # p driver_svc
12
14
  driver_obj = driver_svc["/"]
@@ -15,4 +17,4 @@ driver_ifc = driver_obj["org.freedesktop.DBus"]
15
17
  # p driver_ifc
16
18
 
17
19
  bus_id = driver_ifc.GetId
18
- puts "The system bus id is #{bus_id}"
20
+ puts "The #{busname} bus id is #{bus_id}"
data/lib/dbus/auth.rb CHANGED
@@ -12,261 +12,348 @@ require "rbconfig"
12
12
 
13
13
  module DBus
14
14
  # Exception raised when authentication fails somehow.
15
- class AuthenticationFailed < Exception
15
+ class AuthenticationFailed < StandardError
16
16
  end
17
17
 
18
- # = General class for authentication.
19
- class Authenticator
20
- # Returns the name of the authenticator.
21
- def name
22
- self.class.to_s.upcase.sub(/.*::/, "")
23
- end
24
- end
18
+ # The Authentication Protocol.
19
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
20
+ #
21
+ # @api private
22
+ module Authentication
23
+ # Base class of authentication mechanisms
24
+ class Mechanism
25
+ # @!method call(challenge)
26
+ # @abstract
27
+ # Replies to server *challenge*, or sends an initial response if the challenge is `nil`.
28
+ # @param challenge [String,nil]
29
+ # @return [Array(Symbol,String)] pair [action, response], where
30
+ # - [:MechContinue, response] caller should send "DATA response" and go to :WaitingForData
31
+ # - [:MechOk, response] caller should send "DATA response" and go to :WaitingForOk
32
+ # - [:MechError, message] caller should send "ERROR message" and go to :WaitingForData
25
33
 
26
- # = Anonymous authentication class
27
- class Anonymous < Authenticator
28
- def authenticate
29
- "527562792044427573" # Hex encoded version of "Ruby DBus"
34
+ # Uppercase mechanism name, as sent to the server
35
+ # @return [String]
36
+ def name
37
+ self.class.to_s.upcase.sub(/.*::/, "")
38
+ end
30
39
  end
31
- end
32
40
 
33
- # = External authentication class
34
- #
35
- # Class for 'external' type authentication.
36
- class External < Authenticator
37
- # Performs the authentication.
38
- def authenticate
39
- # Take the user id (eg integer 1000) make a string out of it "1000", take
40
- # each character and determin hex value "1" => 0x31, "0" => 0x30. You
41
- # obtain for "1000" => 31303030 This is what the server is expecting.
42
- # Why? I dunno. How did I come to that conclusion? by looking at rbus
43
- # code. I have no idea how he found that out.
44
- Process.uid.to_s.split(//).map { |d| d.ord.to_s(16) }.join
41
+ # Anonymous authentication class.
42
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-anonymous
43
+ class Anonymous < Mechanism
44
+ def call(_challenge)
45
+ [:MechOk, "Ruby DBus"]
46
+ end
45
47
  end
46
- end
47
48
 
48
- # = Authentication class using SHA1 crypto algorithm
49
- #
50
- # Class for 'CookieSHA1' type authentication.
51
- # Implements the AUTH DBUS_COOKIE_SHA1 mechanism.
52
- class DBusCookieSHA1 < Authenticator
53
- # the autenticate method (called in stage one of authentification)
54
- def authenticate
55
- require "etc"
56
- # number of retries we have for auth
57
- @retries = 1
58
- hex_encode(Etc.getlogin).to_s # server expects it to be binary
49
+ # Class for 'external' type authentication.
50
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-external
51
+ class External < Mechanism
52
+ # Performs the authentication.
53
+ def call(_challenge)
54
+ [:MechOk, Process.uid.to_s]
55
+ end
59
56
  end
60
57
 
61
- # returns the modules name
62
- def name
63
- "DBUS_COOKIE_SHA1"
58
+ # A variant of EXTERNAL that doesn't say our UID.
59
+ # Seen busctl do this and it worked across a container boundary.
60
+ class ExternalWithoutUid < External
61
+ def name
62
+ "EXTERNAL"
63
+ end
64
+
65
+ def call(_challenge)
66
+ [:MechContinue, nil]
67
+ end
64
68
  end
65
69
 
66
- # handles the interesting crypto stuff, check the rbus-project for more info: http://rbus.rubyforge.org/
67
- def data(hexdata)
68
- require "digest/sha1"
69
- data = hex_decode(hexdata)
70
- # name of cookie file, id of cookie in file, servers random challenge
71
- context, id, s_challenge = data.split(" ")
72
- # Random client challenge
73
- c_challenge = 1.upto(s_challenge.bytesize / 2).map { rand(255).to_s }.join
74
- # Search cookie file for id
75
- path = File.join(ENV["HOME"], ".dbus-keyrings", context)
76
- DBus.logger.debug "path: #{path.inspect}"
77
- File.foreach(path) do |line|
78
- if line.start_with?(id)
79
- # Right line of file, read cookie
80
- cookie = line.split(" ")[2].chomp
81
- DBus.logger.debug "cookie: #{cookie.inspect}"
82
- # Concatenate and encrypt
83
- to_encrypt = [s_challenge, c_challenge, cookie].join(":")
84
- sha = Digest::SHA1.hexdigest(to_encrypt)
85
- # the almighty tcp server wants everything hex encoded
86
- hex_response = hex_encode("#{c_challenge} #{sha}")
87
- # Return response
88
- response = [:AuthOk, hex_response]
89
- return response
70
+ # Implements the AUTH DBUS_COOKIE_SHA1 mechanism.
71
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha
72
+ class DBusCookieSHA1 < Mechanism
73
+ # returns the modules name
74
+ def name
75
+ "DBUS_COOKIE_SHA1"
76
+ end
77
+
78
+ # First we are called with nil and we reply with our username.
79
+ # Then we prove that we can read that user's cookie file.
80
+ def call(challenge)
81
+ if challenge.nil?
82
+ require "etc"
83
+ # number of retries we have for auth
84
+ @retries = 1
85
+ return [:MechContinue, Etc.getlogin]
86
+ end
87
+
88
+ require "digest/sha1"
89
+ # name of cookie file, id of cookie in file, servers random challenge
90
+ context, id, s_challenge = challenge.split(" ")
91
+ # Random client challenge
92
+ c_challenge = 1.upto(s_challenge.bytesize / 2).map { rand(255).to_s }.join
93
+ # Search cookie file for id
94
+ path = File.join(ENV["HOME"], ".dbus-keyrings", context)
95
+ DBus.logger.debug "path: #{path.inspect}"
96
+ File.foreach(path) do |line|
97
+ if line.start_with?(id)
98
+ # Right line of file, read cookie
99
+ cookie = line.split(" ")[2].chomp
100
+ DBus.logger.debug "cookie: #{cookie.inspect}"
101
+ # Concatenate and encrypt
102
+ to_encrypt = [s_challenge, c_challenge, cookie].join(":")
103
+ sha = Digest::SHA1.hexdigest(to_encrypt)
104
+ # Return response
105
+ response = [:MechOk, "#{c_challenge} #{sha}"]
106
+ return response
107
+ end
90
108
  end
109
+ return if @retries <= 0
110
+
111
+ # a little rescue magic
112
+ puts "ERROR: Could not auth, will now exit."
113
+ puts "ERROR: Unable to locate cookie, retry in 1 second."
114
+ @retries -= 1
115
+ sleep 1
116
+ call(challenge)
91
117
  end
92
- return if @retries <= 0
93
-
94
- # a little rescue magic
95
- puts "ERROR: Could not auth, will now exit."
96
- puts "ERROR: Unable to locate cookie, retry in 1 second."
97
- @retries -= 1
98
- sleep 1
99
- data(hexdata)
100
118
  end
101
119
 
102
- # encode plain to hex
103
- def hex_encode(plain)
104
- return nil if plain.nil?
120
+ # Declare client state transitions, for ease of code reading.
121
+ # It is just a pair.
122
+ NextState = Struct.new(:state, :command_words)
105
123
 
106
- plain.to_s.unpack1("H*")
107
- end
124
+ # Authenticates the connection before messages can be exchanged.
125
+ class Client
126
+ # @return [Boolean] have we negotiated Unix file descriptor passing
127
+ # NOTE: not implemented yet in upper layers
128
+ attr_reader :unix_fd
108
129
 
109
- # decode hex to plain
110
- def hex_decode(encoded)
111
- encoded.scan(/[[:xdigit:]]{2}/).map { |h| h.hex.chr }.join
112
- end
113
- end
130
+ # @return [String]
131
+ attr_reader :address_uuid
114
132
 
115
- # Note: this following stuff is tested with External authenticator only!
133
+ # Create a new authentication client.
134
+ # @param mechs [Array<Mechanism,Class>,nil] custom list of auth Mechanism objects or classes
135
+ def initialize(socket, mechs = nil)
136
+ @unix_fd = false
137
+ @address_uuid = nil
116
138
 
117
- # = Authentication client class.
118
- #
119
- # Class tha performs the actional authentication.
120
- class Client
121
- # Create a new authentication client.
122
- def initialize(socket)
123
- @socket = socket
124
- @state = nil
125
- @auth_list = [External, DBusCookieSHA1, Anonymous]
126
- end
139
+ @socket = socket
140
+ @state = nil
141
+ @auth_list = mechs || [
142
+ External,
143
+ DBusCookieSHA1,
144
+ ExternalWithoutUid,
145
+ Anonymous
146
+ ]
147
+ end
148
+
149
+ # Start the authentication process.
150
+ # @return [void]
151
+ # @raise [AuthenticationFailed]
152
+ def authenticate
153
+ DBus.logger.debug "Authenticating"
154
+ send_nul_byte
127
155
 
128
- # Start the authentication process.
129
- def authenticate
130
- if RbConfig::CONFIG["target_os"] =~ /freebsd/
131
- @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""])
132
- else
133
- @socket.write(0.chr)
156
+ use_next_mechanism
157
+
158
+ @state, command = next_state_via_mechanism.to_a
159
+ send(command)
160
+
161
+ loop do
162
+ DBus.logger.debug "auth STATE: #{@state}"
163
+ words = next_msg
164
+
165
+ @state, command = next_state(words).to_a
166
+ break if [:TerminatedOk, :TerminatedError].include? @state
167
+
168
+ send(command)
169
+ end
170
+
171
+ raise AuthenticationFailed, command.first if @state == :TerminatedError
172
+
173
+ send("BEGIN")
134
174
  end
135
- next_authenticator
136
- @state = :Starting
137
- while @state != :Authenticated
138
- r = next_state
139
- return r if !r
175
+
176
+ ##########
177
+
178
+ private
179
+
180
+ ##########
181
+
182
+ # The authentication protocol requires a nul byte
183
+ # that may carry credentials.
184
+ # @return [void]
185
+ def send_nul_byte
186
+ if RbConfig::CONFIG["target_os"] =~ /freebsd/
187
+ @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""])
188
+ else
189
+ @socket.write(0.chr)
190
+ end
140
191
  end
141
- true
142
- end
143
192
 
144
- ##########
193
+ # encode plain to hex
194
+ # @param plain [String,nil]
195
+ # @return [String,nil]
196
+ def hex_encode(plain)
197
+ return nil if plain.nil?
145
198
 
146
- private
199
+ plain.unpack1("H*")
200
+ end
147
201
 
148
- ##########
202
+ # decode hex to plain
203
+ # @param encoded [String,nil]
204
+ # @return [String,nil]
205
+ def hex_decode(encoded)
206
+ return nil if encoded.nil?
149
207
 
150
- # Send an authentication method _meth_ with arguments _args_ to the
151
- # server.
152
- def send(meth, *args)
153
- o = ([meth] + args).join(" ")
154
- @socket.write("#{o}\r\n")
155
- end
208
+ [encoded].pack("H*")
209
+ end
156
210
 
157
- # Try authentication using the next authenticator.
158
- def next_authenticator
159
- raise AuthenticationFailed if @auth_list.empty?
160
-
161
- @authenticator = @auth_list.shift.new
162
- auth_msg = ["AUTH", @authenticator.name, @authenticator.authenticate]
163
- DBus.logger.debug "auth_msg: #{auth_msg.inspect}"
164
- send(auth_msg)
165
- rescue AuthenticationFailed
166
- @socket.close
167
- raise
168
- end
211
+ # Send a string to the socket; good place for test mocks.
212
+ def write_line(str)
213
+ DBus.logger.debug "auth_write: #{str.inspect}"
214
+ @socket.write(str)
215
+ end
169
216
 
170
- # Read data (a buffer) from the bus until CR LF is encountered.
171
- # Return the buffer without the CR LF characters.
172
- def next_msg
173
- data = ""
174
- crlf = "\r\n"
175
- left = 1024 # 1024 byte, no idea if it's ever getting bigger
176
- while left.positive?
177
- buf = @socket.read(left > 1 ? 1 : left)
178
- break if buf.nil?
179
-
180
- left -= buf.bytesize
181
- data += buf
182
- break if data.include? crlf # crlf means line finished, the TCP socket keeps on listening, so we break
217
+ # Send *words* to the server as a single CRLF terminated string.
218
+ # @param words [Array<String>,String]
219
+ def send(words)
220
+ joined = Array(words).compact.join(" ")
221
+ write_line("#{joined}\r\n")
183
222
  end
184
- readline = data.chomp.split(" ")
185
- DBus.logger.debug "readline: #{readline.inspect}"
186
- readline
187
- end
188
223
 
189
- # # Read data (a buffer) from the bus until CR LF is encountered.
190
- # # Return the buffer without the CR LF characters.
191
- # def next_msg
192
- # @socket.readline.chomp.split(" ")
193
- # end
194
-
195
- # Try to reach the next state based on the current state.
196
- def next_state
197
- msg = next_msg
198
- if @state == :Starting
199
- DBus.logger.debug ":Starting msg: #{msg[0].inspect}"
200
- case msg[0]
201
- when "OK"
202
- @state = :WaitingForOk
203
- when "CONTINUE"
204
- @state = :WaitingForData
205
- when "REJECTED" # needed by tcp, unix-path/abstract doesn't get here
206
- @state = :WaitingForData
207
- end
224
+ # Try authentication using the next mechanism.
225
+ # @raise [AuthenticationFailed] if there are no more left
226
+ # @return [void]
227
+ def use_next_mechanism
228
+ raise AuthenticationFailed, "Authentication mechanisms exhausted" if @auth_list.empty?
229
+
230
+ @mechanism = @auth_list.shift
231
+ @mechanism = @mechanism.new if @mechanism.is_a? Class
232
+ rescue AuthenticationFailed
233
+ # TODO: make this caller's responsibility
234
+ @socket.close
235
+ raise
208
236
  end
209
- DBus.logger.debug "state: #{@state}"
210
- case @state
211
- when :WaitingForData
212
- DBus.logger.debug ":WaitingForData msg: #{msg[0].inspect}"
213
- case msg[0]
214
- when "DATA"
215
- chall = msg[1]
216
- resp, chall = @authenticator.data(chall)
217
- DBus.logger.debug ":WaitingForData/DATA resp: #{resp.inspect}"
218
- case resp
219
- when :AuthContinue
220
- send("DATA", chall)
221
- @state = :WaitingForData
222
- when :AuthOk
223
- send("DATA", chall)
224
- @state = :WaitingForOk
225
- when :AuthError
226
- send("ERROR")
227
- @state = :WaitingForData
228
- end
229
- when "REJECTED"
230
- next_authenticator
231
- @state = :WaitingForData
232
- when "ERROR"
233
- send("CANCEL")
234
- @state = :WaitingForReject
235
- when "OK"
236
- send("BEGIN")
237
- @state = :Authenticated
238
- else
239
- send("ERROR")
240
- @state = :WaitingForData
237
+
238
+ # Read data (a buffer) from the bus until CR LF is encountered.
239
+ # Return the buffer without the CR LF characters.
240
+ # @return [Array<String>] received words
241
+ def next_msg
242
+ read_line.chomp.split(" ")
243
+ end
244
+
245
+ # Read a line from the socket; good place for test mocks.
246
+ # @return [String] CRLF (\r\n) terminated
247
+ def read_line
248
+ # TODO: probably can simply call @socket.readline
249
+ data = ""
250
+ crlf = "\r\n"
251
+ left = 1024 # 1024 byte, no idea if it's ever getting bigger
252
+ while left.positive?
253
+ buf = @socket.read(left > 1 ? 1 : left)
254
+ break if buf.nil?
255
+
256
+ left -= buf.bytesize
257
+ data += buf
258
+ break if data.include? crlf # crlf means line finished, the TCP socket keeps on listening, so we break
241
259
  end
242
- when :WaitingForOk
243
- DBus.logger.debug ":WaitingForOk msg: #{msg[0].inspect}"
244
- case msg[0]
245
- when "OK"
246
- send("BEGIN")
247
- @state = :Authenticated
248
- when "REJECT"
249
- next_authenticator
250
- @state = :WaitingForData
251
- when "DATA", "ERROR"
252
- send("CANCEL")
253
- @state = :WaitingForReject
260
+ DBus.logger.debug "auth_read: #{data.inspect}"
261
+ data
262
+ end
263
+
264
+ # # Read data (a buffer) from the bus until CR LF is encountered.
265
+ # # Return the buffer without the CR LF characters.
266
+ # def next_msg
267
+ # @socket.readline.chomp.split(" ")
268
+ # end
269
+
270
+ # @param hex_challenge [String,nil] (nil when the server said "DATA\r\n")
271
+ # @param use_data [Boolean] say DATA instead of AUTH
272
+ # @return [NextState]
273
+ def next_state_via_mechanism(hex_challenge = nil, use_data: false)
274
+ challenge = hex_decode(hex_challenge)
275
+
276
+ action, response = @mechanism.call(challenge)
277
+ DBus.logger.debug "auth mechanism action: #{action.inspect}"
278
+
279
+ command = use_data ? ["DATA"] : ["AUTH", @mechanism.name]
280
+
281
+ case action
282
+ when :MechError
283
+ NextState.new(:WaitingForData, ["ERROR", response])
284
+ when :MechContinue
285
+ NextState.new(:WaitingForData, command + [hex_encode(response)])
286
+ when :MechOk
287
+ NextState.new(:WaitingForOk, command + [hex_encode(response)])
254
288
  else
255
- send("ERROR")
256
- @state = :WaitingForOk
289
+ raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \
290
+ "from our mechanism #{@mechanism.inspect}"
257
291
  end
258
- when :WaitingForReject
259
- DBus.logger.debug ":WaitingForReject msg: #{msg[0].inspect}"
260
- case msg[0]
261
- when "REJECT"
262
- next_authenticator
263
- @state = :WaitingForOk
292
+ end
293
+
294
+ # Try to reach the next state based on the current state.
295
+ # @param received_words [Array<String>]
296
+ # @return [NextState]
297
+ def next_state(received_words)
298
+ msg = received_words
299
+
300
+ case @state
301
+ when :WaitingForData
302
+ case msg[0]
303
+ when "DATA"
304
+ next_state_via_mechanism(msg[1], use_data: true)
305
+ when "REJECTED"
306
+ use_next_mechanism
307
+ next_state_via_mechanism
308
+ when "ERROR"
309
+ NextState.new(:WaitingForReject, ["CANCEL"])
310
+ when "OK"
311
+ @address_uuid = msg[1]
312
+ # NextState.new(:TerminatedOk, [])
313
+ NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"])
314
+ else
315
+ NextState.new(:WaitingForData, ["ERROR"])
316
+ end
317
+ when :WaitingForOk
318
+ case msg[0]
319
+ when "OK"
320
+ @address_uuid = msg[1]
321
+ # NextState.new(:TerminatedOk, [])
322
+ NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"])
323
+ when "REJECTED"
324
+ use_next_mechanism
325
+ next_state_via_mechanism
326
+ when "DATA", "ERROR"
327
+ NextState.new(:WaitingForReject, ["CANCEL"])
328
+ else
329
+ # we don't understand server's response but still wait for a successful auth completion
330
+ NextState.new(:WaitingForOk, ["ERROR"])
331
+ end
332
+ when :WaitingForReject
333
+ case msg[0]
334
+ when "REJECTED"
335
+ use_next_mechanism
336
+ next_state_via_mechanism
337
+ else
338
+ # TODO: spec says to close socket, clarify
339
+ NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} when expecting REJECTED"])
340
+ end
341
+ when :WaitingForAgreeUnixFD
342
+ case msg[0]
343
+ when "AGREE_UNIX_FD"
344
+ @unix_fd = true
345
+ NextState.new(:TerminatedOk, [])
346
+ when "ERROR"
347
+ @unix_fd = false
348
+ NextState.new(:TerminatedOk, [])
349
+ else
350
+ # TODO: spec says to close socket, clarify
351
+ NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} to NEGOTIATE_UNIX_FD"])
352
+ end
264
353
  else
265
- @socket.close
266
- return false
354
+ raise "Internal error: unhandled state #{@state.inspect}"
267
355
  end
268
356
  end
269
- true
270
357
  end
271
358
  end
272
359
  end