ruby-dbus 0.19.0 → 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 +4 -4
- data/NEWS.md +9 -0
- data/VERSION +1 -1
- data/examples/simple/get_id.rb +4 -2
- data/lib/dbus/auth.rb +305 -218
- data/lib/dbus/message_queue.rb +19 -12
- data/spec/auth_spec.rb +225 -0
- data/spec/proxy_object_interface_spec.rb +35 -0
- data/spec/service_newapi.rb +6 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/thread_safety_spec.rb +55 -12
- data/spec/tools/dbus-limited-session.conf +14 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d52907f5f6be32813466ae280cb99ff605d4195ca84396e97a90a7568fb579ec
|
4
|
+
data.tar.gz: 01bf87805f19878641a424f5c9ba21fcff637dcb6d4bbcdc230dda3713df8c80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8009bcca0c66ddb5d5e9ca76a2fbc06b0408437ac3f5135d1378362f58520cbcb20c6d625d242e7c19ca1f679b4ab7b4e584e5f9d6c376eea90f57372f7f27aa
|
7
|
+
data.tar.gz: 0d8388654ae2ad5aeca7cf0eb6cec2fb3eb463ac43b85eb7a83bfbd32a011d65749b1a1195fcc4ae0f8c6a5591e49c0176a09c442c67a166ce2432bb30d704f7
|
data/NEWS.md
CHANGED
@@ -2,6 +2,15 @@
|
|
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
|
+
|
5
14
|
## Ruby D-Bus 0.19.0 - 2023-01-18
|
6
15
|
|
7
16
|
API:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.20.0
|
data/examples/simple/get_id.rb
CHANGED
@@ -6,7 +6,9 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
6
6
|
|
7
7
|
require "dbus"
|
8
8
|
|
9
|
-
|
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
|
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 <
|
15
|
+
class AuthenticationFailed < StandardError
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
#
|
62
|
-
|
63
|
-
|
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
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
#
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
#
|
103
|
-
|
104
|
-
|
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
|
-
|
107
|
-
|
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
|
-
|
110
|
-
|
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
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
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
|
-
|
151
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
256
|
-
|
289
|
+
raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \
|
290
|
+
"from our mechanism #{@mechanism.inspect}"
|
257
291
|
end
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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
|
-
@
|
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
|
data/lib/dbus/message_queue.rb
CHANGED
@@ -19,9 +19,11 @@ module DBus
|
|
19
19
|
attr_reader :socket
|
20
20
|
|
21
21
|
def initialize(address)
|
22
|
+
DBus.logger.debug "MessageQueue: #{address}"
|
22
23
|
@address = address
|
23
24
|
@buffer = ""
|
24
25
|
@is_tcp = false
|
26
|
+
@mutex = Mutex.new
|
25
27
|
connect
|
26
28
|
end
|
27
29
|
|
@@ -32,23 +34,28 @@ module DBus
|
|
32
34
|
# @raise EOFError
|
33
35
|
# @todo failure modes
|
34
36
|
def pop(blocking: true)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
# FIXME: this is not enough, the R/W test deadlocks on shared connections
|
38
|
+
@mutex.synchronize do
|
39
|
+
buffer_from_socket_nonblock
|
40
|
+
message = message_from_buffer_nonblock
|
41
|
+
if blocking
|
42
|
+
# we can block
|
43
|
+
while message.nil?
|
44
|
+
r, _d, _d = IO.select([@socket])
|
45
|
+
if r && r[0] == @socket
|
46
|
+
buffer_from_socket_nonblock
|
47
|
+
message = message_from_buffer_nonblock
|
48
|
+
end
|
44
49
|
end
|
45
50
|
end
|
51
|
+
message
|
46
52
|
end
|
47
|
-
message
|
48
53
|
end
|
49
54
|
|
50
55
|
def push(message)
|
51
|
-
@
|
56
|
+
@mutex.synchronize do
|
57
|
+
@socket.write(message.marshall)
|
58
|
+
end
|
52
59
|
end
|
53
60
|
alias << push
|
54
61
|
|
@@ -129,7 +136,7 @@ module DBus
|
|
129
136
|
|
130
137
|
# Initialize the connection to the bus.
|
131
138
|
def init_connection
|
132
|
-
client = Client.new(@socket)
|
139
|
+
client = Authentication::Client.new(@socket)
|
133
140
|
client.authenticate
|
134
141
|
end
|
135
142
|
|
data/spec/auth_spec.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "spec_helper"
|
5
|
+
require "dbus"
|
6
|
+
|
7
|
+
describe DBus::Authentication::Client do
|
8
|
+
let(:socket) { instance_double("Socket") }
|
9
|
+
let(:subject) { described_class.new(socket) }
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
allow(Process).to receive(:uid).and_return(999)
|
13
|
+
allow(subject).to receive(:send_nul_byte)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#next_state" do
|
17
|
+
it "raises when I forget to handle a state" do
|
18
|
+
subject.instance_variable_set(:@state, :Denmark)
|
19
|
+
expect { subject.__send__(:next_state, []) }.to raise_error(RuntimeError, /unhandled state :Denmark/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def expect_protocol(pairs)
|
24
|
+
pairs.each do |we_say, server_says|
|
25
|
+
expect(subject).to receive(:write_line).with(we_say)
|
26
|
+
next if server_says.nil?
|
27
|
+
|
28
|
+
expect(subject).to receive(:read_line).and_return(server_says)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with ANONYMOUS" do
|
33
|
+
let(:subject) { described_class.new(socket, [DBus::Authentication::Anonymous]) }
|
34
|
+
|
35
|
+
it "authentication passes" do
|
36
|
+
expect_protocol [
|
37
|
+
["AUTH ANONYMOUS 527562792044427573\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
38
|
+
["NEGOTIATE_UNIX_FD\r\n", "ERROR not for anonymous\r\n"],
|
39
|
+
["BEGIN\r\n"]
|
40
|
+
]
|
41
|
+
|
42
|
+
expect { subject.authenticate }.to_not raise_error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with EXTERNAL" do
|
47
|
+
let(:subject) { described_class.new(socket, [DBus::Authentication::External]) }
|
48
|
+
|
49
|
+
it "authentication passes, and address_uuid is set" do
|
50
|
+
expect_protocol [
|
51
|
+
["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
52
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
53
|
+
["BEGIN\r\n"]
|
54
|
+
]
|
55
|
+
|
56
|
+
expect { subject.authenticate }.to_not raise_error
|
57
|
+
expect(subject.address_uuid).to eq "ffffffffffffffffffffffffffffffff"
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when the server says superfluous things before an OK" do
|
61
|
+
it "authentication passes" do
|
62
|
+
expect_protocol [
|
63
|
+
["AUTH EXTERNAL 393939\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
|
64
|
+
["ERROR\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
65
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
66
|
+
["BEGIN\r\n"]
|
67
|
+
]
|
68
|
+
|
69
|
+
expect { subject.authenticate }.to_not raise_error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when the server messes up NEGOTIATE_UNIX_FD" do
|
74
|
+
it "authentication fails orderly" do
|
75
|
+
expect_protocol [
|
76
|
+
["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
77
|
+
["NEGOTIATE_UNIX_FD\r\n", "I_DONT_NEGOTIATE_WITH_TENORISTS\r\n"]
|
78
|
+
]
|
79
|
+
|
80
|
+
allow(socket).to receive(:close) # want to get rid of this
|
81
|
+
# TODO: quote the server error message?
|
82
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown server reply/)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the server replies with ERROR" do
|
87
|
+
it "authentication fails orderly" do
|
88
|
+
expect_protocol [
|
89
|
+
["AUTH EXTERNAL 393939\r\n", "ERROR something failed\r\n"],
|
90
|
+
["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
|
91
|
+
]
|
92
|
+
|
93
|
+
allow(socket).to receive(:close) # want to get rid of this
|
94
|
+
# TODO: quote the server error message?
|
95
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "with EXTERNAL without uid" do
|
101
|
+
let(:subject) do
|
102
|
+
described_class.new(socket, [DBus::Authentication::External, DBus::Authentication::ExternalWithoutUid])
|
103
|
+
end
|
104
|
+
|
105
|
+
it "authentication passes" do
|
106
|
+
expect_protocol [
|
107
|
+
["AUTH EXTERNAL 393939\r\n", "REJECTED EXTERNAL\r\n"],
|
108
|
+
# this succeeds when we connect to a privileged container,
|
109
|
+
# where outside-non-root becomes inside-root
|
110
|
+
["AUTH EXTERNAL\r\n", "DATA\r\n"],
|
111
|
+
["DATA\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
112
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
113
|
+
["BEGIN\r\n"]
|
114
|
+
]
|
115
|
+
|
116
|
+
expect { subject.authenticate }.to_not raise_error
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "with a rejected mechanism and then EXTERNAL" do
|
121
|
+
let(:rejected_mechanism) do
|
122
|
+
double("Mechanism", name: "WIMP", call: [:MechContinue, "I expect to be rejected"])
|
123
|
+
end
|
124
|
+
|
125
|
+
let(:subject) { described_class.new(socket, [rejected_mechanism, DBus::Authentication::External]) }
|
126
|
+
|
127
|
+
it "authentication eventually passes" do
|
128
|
+
expect_protocol [
|
129
|
+
[/^AUTH WIMP .*\r\n/, "REJECTED EXTERNAL\r\n"],
|
130
|
+
["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
131
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
132
|
+
["BEGIN\r\n"]
|
133
|
+
]
|
134
|
+
|
135
|
+
expect { subject.authenticate }.to_not raise_error
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "with a DATA-using mechanism" do
|
140
|
+
let(:mechanism) do
|
141
|
+
double("Mechanism", name: "CHALLENGE_ME", call: [:MechContinue, "1"])
|
142
|
+
end
|
143
|
+
|
144
|
+
# try it twice to test calling #use_next_mechanism
|
145
|
+
let(:subject) { described_class.new(socket, [mechanism, mechanism]) }
|
146
|
+
|
147
|
+
it "authentication fails orderly when the server says ERROR" do
|
148
|
+
expect_protocol [
|
149
|
+
["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
|
150
|
+
["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"],
|
151
|
+
["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
|
152
|
+
["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
|
153
|
+
]
|
154
|
+
|
155
|
+
allow(socket).to receive(:close) # want to get rid of this
|
156
|
+
# TODO: quote the server error message?
|
157
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "authentication fails orderly when the server says ERROR and then changes its mind" do
|
161
|
+
expect_protocol [
|
162
|
+
["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
|
163
|
+
["CANCEL\r\n", "I_CHANGED_MY_MIND please come back\r\n"]
|
164
|
+
]
|
165
|
+
|
166
|
+
allow(socket).to receive(:close) # want to get rid of this
|
167
|
+
# TODO: quote the server error message?
|
168
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown.*MIND.*REJECTED/)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "authentication passes when the server says superfluous things before DATA" do
|
172
|
+
expect_protocol [
|
173
|
+
["AUTH CHALLENGE_ME 31\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
|
174
|
+
["ERROR\r\n", "DATA\r\n"],
|
175
|
+
["DATA 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
176
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
177
|
+
["BEGIN\r\n"]
|
178
|
+
]
|
179
|
+
|
180
|
+
expect { subject.authenticate }.to_not raise_error
|
181
|
+
end
|
182
|
+
|
183
|
+
it "authentication passes when the server decides not to need the DATA" do
|
184
|
+
expect_protocol [
|
185
|
+
["AUTH CHALLENGE_ME 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
186
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
187
|
+
["BEGIN\r\n"]
|
188
|
+
]
|
189
|
+
|
190
|
+
expect { subject.authenticate }.to_not raise_error
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "with a mechanism returning :MechError" do
|
195
|
+
let(:fallible_mechanism) do
|
196
|
+
double(name: "FALLIBLE", call: [:MechError, "not my best day"])
|
197
|
+
end
|
198
|
+
|
199
|
+
let(:subject) { described_class.new(socket, [fallible_mechanism]) }
|
200
|
+
|
201
|
+
it "authentication fails orderly" do
|
202
|
+
expect_protocol [
|
203
|
+
["ERROR not my best day\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
|
204
|
+
]
|
205
|
+
|
206
|
+
allow(socket).to receive(:close) # want to get rid of thise
|
207
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context "with a badly implemented mechanism" do
|
212
|
+
let(:buggy_mechanism) do
|
213
|
+
double(name: "buggy", call: [:smurf, nil])
|
214
|
+
end
|
215
|
+
|
216
|
+
let(:subject) { described_class.new(socket, [buggy_mechanism]) }
|
217
|
+
|
218
|
+
it "authentication fails before protoxol is exchanged" do
|
219
|
+
expect(subject).to_not receive(:write_line)
|
220
|
+
expect(subject).to_not receive(:read_line)
|
221
|
+
|
222
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /smurf/)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "spec_helper"
|
5
|
+
require "dbus"
|
6
|
+
|
7
|
+
describe DBus::ProxyObjectInterface do
|
8
|
+
# TODO: tag tests that need a service, eg "needs-service"
|
9
|
+
# TODO: maybe remove this and rely on a packaged tool
|
10
|
+
around(:each) do |example|
|
11
|
+
with_private_bus do
|
12
|
+
with_service_by_activation(&example)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:bus) { DBus::ASessionBus.new }
|
17
|
+
|
18
|
+
context "when calling org.ruby.service" do
|
19
|
+
let(:svc) { bus["org.ruby.service"] }
|
20
|
+
|
21
|
+
# This is white box testing, knowing the implementation
|
22
|
+
# A better way would be structuring it according to the D-Bus Spec
|
23
|
+
# Or testing the service side doing the right thing? (What if our bugs cancel out)
|
24
|
+
describe "#define_method_from_descriptor" do
|
25
|
+
it "can call a method with multiple OUT arguments" do
|
26
|
+
obj = svc["/org/ruby/MyInstance"]
|
27
|
+
ifc = obj["org.ruby.SampleInterface"]
|
28
|
+
|
29
|
+
even, odd = ifc.EvenOdd([3, 1, 4, 1, 5, 9, 2, 6])
|
30
|
+
expect(even).to eq [4, 2, 6]
|
31
|
+
expect(odd).to eq [3, 1, 1, 5, 9]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/spec/service_newapi.rb
CHANGED
@@ -102,6 +102,12 @@ class Test < DBus::Object
|
|
102
102
|
[coords]
|
103
103
|
end
|
104
104
|
|
105
|
+
# Two OUT arguments
|
106
|
+
dbus_method :EvenOdd, "in numbers:ai, out even:ai, out odd:ai" do |numbers|
|
107
|
+
even, odd = numbers.partition(&:even?)
|
108
|
+
[even, odd]
|
109
|
+
end
|
110
|
+
|
105
111
|
# Properties:
|
106
112
|
# ReadMe:string, returns "READ ME" at first, then what WriteMe received
|
107
113
|
# WriteMe:string
|
data/spec/spec_helper.rb
CHANGED
@@ -31,7 +31,7 @@ if coverage
|
|
31
31
|
c.single_report_path = "coverage/lcov.info"
|
32
32
|
end
|
33
33
|
|
34
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
34
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [
|
35
35
|
SimpleCov::Formatter::HTMLFormatter,
|
36
36
|
SimpleCov::Formatter::LcovFormatter
|
37
37
|
]
|
data/spec/thread_safety_spec.rb
CHANGED
@@ -5,28 +5,71 @@
|
|
5
5
|
require_relative "spec_helper"
|
6
6
|
require "dbus"
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
class TestSignalRace < DBus::Object
|
9
|
+
dbus_interface "org.ruby.ServerTest" do
|
10
|
+
dbus_signal :signal_without_arguments
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Run *count* threads all doing *body*, wait for their finish
|
15
|
+
def race_threads(count, &body)
|
16
|
+
jobs = count.times.map do |j|
|
17
|
+
Thread.new do
|
18
|
+
Thread.current.abort_on_exception = true
|
19
|
+
|
20
|
+
body.call(j)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
jobs.each(&:join)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Repeat *count* times: { random sleep, *body* }, printing progress
|
27
|
+
def repeat_with_jitter(count, &body)
|
28
|
+
count.times do |i|
|
29
|
+
sleep 0.1 * rand
|
30
|
+
print "#{i} "
|
31
|
+
$stdout.flush
|
15
32
|
|
33
|
+
body.call
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "thread safety" do
|
38
|
+
context "R/W: when the threads call methods with return values" do
|
39
|
+
it "it works with separate bus connections" do
|
40
|
+
race_threads(5) do |_j|
|
16
41
|
# use separate connections to avoid races
|
17
42
|
bus = DBus::ASessionBus.new
|
18
43
|
svc = bus.service("org.ruby.service")
|
19
44
|
obj = svc.object("/org/ruby/MyInstance")
|
20
45
|
obj.default_iface = "org.ruby.SampleInterface"
|
21
46
|
|
22
|
-
10
|
23
|
-
print "#{i} "
|
24
|
-
$stdout.flush
|
47
|
+
repeat_with_jitter(10) do
|
25
48
|
expect(obj.the_answer[0]).to eq(42)
|
26
|
-
sleep 0.1 * rand
|
27
49
|
end
|
28
50
|
end
|
51
|
+
puts
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "W/O: when the threads only send signals" do
|
56
|
+
it "it works with a shared separate bus connection" do
|
57
|
+
race_threads(5) do |j|
|
58
|
+
# shared connection
|
59
|
+
bus = DBus::SessionBus.instance
|
60
|
+
# hackish: we do not actually request the name
|
61
|
+
svc = DBus::Service.new("org.ruby.server-test#{j}", bus)
|
62
|
+
|
63
|
+
obj = TestSignalRace.new "/org/ruby/Foo"
|
64
|
+
svc.export obj
|
65
|
+
|
66
|
+
repeat_with_jitter(10) do
|
67
|
+
obj.signal_without_arguments
|
68
|
+
end
|
69
|
+
|
70
|
+
svc.unexport(obj)
|
71
|
+
end
|
72
|
+
puts
|
29
73
|
end
|
30
|
-
jobs.each(&:join)
|
31
74
|
end
|
32
75
|
end
|
@@ -7,6 +7,20 @@
|
|
7
7
|
<!-- Our well-known bus type, don't change this -->
|
8
8
|
<type>session</type>
|
9
9
|
|
10
|
+
<!-- Authentication:
|
11
|
+
This was useful during refactoring, but meanwhile RSpec mocking has
|
12
|
+
replaced it. -->
|
13
|
+
<!-- Explicitly list all known authentication mechanisms,
|
14
|
+
their order is not important.
|
15
|
+
By default the daemon allows all but this lets me disable some. -->
|
16
|
+
<auth>EXTERNAL</auth>
|
17
|
+
<auth>DBUS_COOKIE_SHA1</auth>
|
18
|
+
<auth>ANONYMOUS</auth>
|
19
|
+
<!-- Insecure, other users could call us and exploit debug APIs/bugs -->
|
20
|
+
<!--
|
21
|
+
<allow_anonymous/>
|
22
|
+
-->
|
23
|
+
|
10
24
|
<listen>unix:tmpdir=/tmp</listen>
|
11
25
|
<listen>tcp:host=127.0.0.1</listen>
|
12
26
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-dbus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ruby DBus Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rexml
|
@@ -165,6 +165,7 @@ files:
|
|
165
165
|
- lib/dbus/xml.rb
|
166
166
|
- ruby-dbus.gemspec
|
167
167
|
- spec/async_spec.rb
|
168
|
+
- spec/auth_spec.rb
|
168
169
|
- spec/binding_spec.rb
|
169
170
|
- spec/bus_and_xml_backend_spec.rb
|
170
171
|
- spec/bus_driver_spec.rb
|
@@ -186,6 +187,7 @@ files:
|
|
186
187
|
- spec/packet_marshaller_spec.rb
|
187
188
|
- spec/packet_unmarshaller_spec.rb
|
188
189
|
- spec/property_spec.rb
|
190
|
+
- spec/proxy_object_interface_spec.rb
|
189
191
|
- spec/proxy_object_spec.rb
|
190
192
|
- spec/server_robustness_spec.rb
|
191
193
|
- spec/server_spec.rb
|
@@ -223,8 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
223
225
|
- !ruby/object:Gem::Version
|
224
226
|
version: '0'
|
225
227
|
requirements: []
|
226
|
-
|
227
|
-
rubygems_version: 2.7.6.3
|
228
|
+
rubygems_version: 3.3.0.dev
|
228
229
|
signing_key:
|
229
230
|
specification_version: 4
|
230
231
|
summary: Ruby module for interaction with D-Bus
|