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.
- checksums.yaml +4 -4
- data/NEWS.md +288 -3
- data/README.md +3 -5
- data/Rakefile +27 -12
- data/VERSION +1 -1
- data/doc/Reference.md +109 -10
- data/examples/doc/_extract_examples +7 -0
- data/examples/no-bus/pulseaudio.rb +50 -0
- data/examples/no-introspect/nm-test.rb +2 -0
- data/examples/no-introspect/tracker-test.rb +3 -1
- data/examples/rhythmbox/playpause.rb +2 -1
- data/examples/service/call_service.rb +2 -1
- data/examples/service/complex-property.rb +21 -0
- data/examples/service/service_newapi.rb +3 -3
- data/examples/simple/call_introspect.rb +1 -0
- data/examples/simple/get_id.rb +6 -3
- data/examples/simple/properties.rb +2 -0
- data/examples/utils/listnames.rb +10 -1
- data/examples/utils/notify.rb +1 -0
- data/lib/dbus/api_options.rb +9 -0
- data/lib/dbus/auth.rb +307 -217
- data/lib/dbus/bus.rb +155 -588
- data/lib/dbus/bus_name.rb +12 -8
- data/lib/dbus/connection.rb +363 -0
- data/lib/dbus/core_ext/class/attribute.rb +1 -1
- data/lib/dbus/data.rb +821 -0
- data/lib/dbus/emits_changed_signal.rb +83 -0
- data/lib/dbus/error.rb +4 -2
- data/lib/dbus/introspect.rb +133 -31
- data/lib/dbus/logger.rb +6 -3
- data/lib/dbus/main.rb +66 -0
- data/lib/dbus/marshall.rb +247 -296
- data/lib/dbus/matchrule.rb +16 -16
- data/lib/dbus/message.rb +66 -57
- data/lib/dbus/message_queue.rb +40 -25
- data/lib/dbus/node_tree.rb +105 -0
- data/lib/dbus/object.rb +442 -33
- data/lib/dbus/object_manager.rb +61 -0
- data/lib/dbus/object_path.rb +11 -6
- data/lib/dbus/object_server.rb +149 -0
- data/lib/dbus/org.freedesktop.DBus.xml +97 -0
- data/lib/dbus/platform.rb +26 -0
- data/lib/dbus/proxy_object.rb +30 -11
- data/lib/dbus/proxy_object_factory.rb +13 -7
- data/lib/dbus/proxy_object_interface.rb +63 -30
- data/lib/dbus/proxy_service.rb +107 -0
- data/lib/dbus/raw_message.rb +91 -0
- data/lib/dbus/type.rb +319 -86
- data/lib/dbus/xml.rb +40 -19
- data/lib/dbus.rb +28 -17
- data/ruby-dbus.gemspec +13 -6
- data/spec/async_spec.rb +2 -0
- data/spec/auth_spec.rb +225 -0
- data/spec/binding_spec.rb +2 -0
- data/spec/bus_and_xml_backend_spec.rb +5 -1
- data/spec/bus_connection_spec.rb +93 -0
- data/spec/bus_name_spec.rb +3 -1
- data/spec/bus_spec.rb +2 -0
- data/spec/byte_array_spec.rb +2 -0
- data/spec/client_robustness_spec.rb +4 -2
- data/spec/connection_spec.rb +37 -0
- data/spec/coverage_helper.rb +39 -0
- data/spec/data/marshall.yaml +1667 -0
- data/spec/data_spec.rb +673 -0
- data/spec/dbus_spec.rb +22 -0
- data/spec/emits_changed_signal_spec.rb +58 -0
- data/spec/err_msg_spec.rb +2 -0
- data/spec/introspect_xml_parser_spec.rb +2 -0
- data/spec/introspection_spec.rb +2 -0
- data/spec/main_loop_spec.rb +17 -1
- data/spec/message_spec.rb +21 -0
- data/spec/mock-service/cockpit-dbustests.rb +29 -0
- data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
- data/spec/mock-service/org.ruby.service.service +4 -0
- data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
- data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
- data/spec/node_spec.rb +65 -0
- data/spec/object_manager_spec.rb +33 -0
- data/spec/object_path_spec.rb +3 -0
- data/spec/object_server_spec.rb +138 -0
- data/spec/object_spec.rb +194 -0
- data/spec/packet_marshaller_spec.rb +41 -0
- data/spec/packet_unmarshaller_spec.rb +248 -0
- data/spec/platform_spec.rb +14 -0
- data/spec/property_spec.rb +214 -12
- data/spec/proxy_object_interface_spec.rb +35 -0
- data/spec/proxy_object_spec.rb +2 -0
- data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
- data/spec/raw_message_spec.rb +32 -0
- data/spec/server_robustness_spec.rb +20 -6
- data/spec/session_bus_spec.rb +36 -1
- data/spec/session_bus_spec_manual.rb +2 -0
- data/spec/signal_spec.rb +16 -3
- data/spec/spec_helper.rb +25 -33
- data/spec/thread_safety_spec.rb +55 -12
- data/spec/tools/dbus-launch-simple +9 -6
- data/spec/tools/dbus-limited-session.conf +32 -0
- data/spec/tools/test_env +26 -6
- data/spec/type_spec.rb +214 -6
- data/spec/value_spec.rb +16 -1
- data/spec/variant_spec.rb +4 -2
- data/spec/zzz_quit_spec.rb +16 -0
- metadata +92 -20
- data/examples/gdbus/gdbus +0 -257
- data/examples/gdbus/gdbus.glade +0 -98
- data/examples/gdbus/launch.sh +0 -4
- data/spec/server_spec.rb +0 -53
- 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 <
|
|
13
|
+
class AuthenticationFailed < StandardError
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
114
|
+
call(challenge)
|
|
97
115
|
end
|
|
98
116
|
end
|
|
99
117
|
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
128
|
+
# @return [String]
|
|
129
|
+
attr_reader :address_uuid
|
|
113
130
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
154
|
+
use_next_mechanism
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
@state, command = next_state_via_mechanism.to_a
|
|
157
|
+
send(command)
|
|
146
158
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
251
|
-
|
|
287
|
+
raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \
|
|
288
|
+
"from our mechanism #{@mechanism.inspect}"
|
|
252
289
|
end
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
@
|
|
261
|
-
return false
|
|
352
|
+
raise "Internal error: unhandled state #{@state.inspect}"
|
|
262
353
|
end
|
|
263
354
|
end
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
end # module D-Bus
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|