ruby-dbus 0.18.1 → 0.20.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.
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