ruby-dbus 0.19.0 → 0.21.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 +24 -4
- data/VERSION +1 -1
- data/examples/simple/get_id.rb +4 -2
- data/lib/dbus/auth.rb +305 -218
- data/lib/dbus/message.rb +14 -10
- data/lib/dbus/message_queue.rb +25 -16
- data/lib/dbus.rb +15 -12
- data/spec/auth_spec.rb +225 -0
- data/spec/proxy_object_interface_spec.rb +35 -0
- data/spec/raw_message_spec.rb +32 -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 +9 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 922468208faa7a9a13f3c40c0b7ffd22a5a9a6b00de2e30e05e6ae18c84662d5
|
|
4
|
+
data.tar.gz: 9386581c654bad7f970763c1c67f8984e95654d6f49673a1c3452c2cc59ced0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67e6e4058de8f6e9e0eddc404f53b5219487d711c165dd607ec1138c18f089de560f6467bf6960a28dc0c29b6df2a4a5eee4156e2c2fab68c1dada5ce9383f91
|
|
7
|
+
data.tar.gz: 364d914924b85f11354fdf8ed3f44f446091422014a92c5c7f0ecc3175cc28236d6a6b0cfec233321657a35554eb24be8692ab83c41a65eb9a75a7eef5dd7a5b
|
data/NEWS.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## Ruby D-Bus 0.21.0 - 2023-04-08
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
* Respect env RUBY_DBUS_ENDIANNESS=B (or =l) for outgoing messages.
|
|
9
|
+
|
|
10
|
+
Bug fixes:
|
|
11
|
+
* Reduce socket buffer allocations ([#129][]).
|
|
12
|
+
* Message#marshall speedup: don't marshall the body twice.
|
|
13
|
+
|
|
14
|
+
[#129]: https://github.com/mvidner/ruby-dbus/pull/129
|
|
15
|
+
|
|
16
|
+
## Ruby D-Bus 0.20.0 - 2023-03-21
|
|
17
|
+
|
|
18
|
+
Features:
|
|
19
|
+
* For EXTERNAL authentication, try also without the user id, to work with
|
|
20
|
+
containers ([#126][]).
|
|
21
|
+
* Thread safety, as long as the non-main threads only send signals.
|
|
22
|
+
|
|
23
|
+
[#126]: https://github.com/mvidner/ruby-dbus/issues/126
|
|
24
|
+
|
|
5
25
|
## Ruby D-Bus 0.19.0 - 2023-01-18
|
|
6
26
|
|
|
7
27
|
API:
|
|
@@ -73,7 +93,7 @@ API:
|
|
|
73
93
|
when declaring properties ([#117][]).
|
|
74
94
|
|
|
75
95
|
[#115]: https://github.com/mvidner/ruby-dbus/issues/115
|
|
76
|
-
[#117]: https://github.com/mvidner/ruby-dbus/
|
|
96
|
+
[#117]: https://github.com/mvidner/ruby-dbus/pull/117
|
|
77
97
|
|
|
78
98
|
## Ruby D-Bus 0.18.0.beta7 - 2022-05-29
|
|
79
99
|
|
|
@@ -441,7 +461,7 @@ Bug fixes:
|
|
|
441
461
|
* Handle more ways which tell us that a bus connection has died.
|
|
442
462
|
|
|
443
463
|
[#3]: https://github.com/mvidner/ruby-dbus/issue/3
|
|
444
|
-
[bsc#617350]: https://bugzilla.
|
|
464
|
+
[bsc#617350]: https://bugzilla.suse.com/show_bug.cgi?id=617350
|
|
445
465
|
|
|
446
466
|
## Ruby D-Bus 0.3.0 - 2010-03-28
|
|
447
467
|
|
|
@@ -509,8 +529,8 @@ Bug fixes:
|
|
|
509
529
|
* Fixed an endless sleep in DBus::Main.run ([bsc#537401][]).
|
|
510
530
|
* Added details to PacketMarshaller exceptions ([bsc#538050][]).
|
|
511
531
|
|
|
512
|
-
[bsc#537401]: https://bugzilla.
|
|
513
|
-
[bsc#538050]: https://bugzilla.
|
|
532
|
+
[bsc#537401]: https://bugzilla.suse.com/show_bug.cgi?id=537401
|
|
533
|
+
[bsc#538050]: https://bugzilla.suse.com/show_bug.cgi?id=538050
|
|
514
534
|
|
|
515
535
|
## Ruby D-Bus "I'm not dead" 0.2.9 - 2009-08-26
|
|
516
536
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.21.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.rb
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
# License, version 2.1 as published by the Free Software Foundation.
|
|
11
11
|
# See the file "COPYING" for the exact licensing terms.
|
|
12
12
|
|
|
13
|
+
require_relative "raw_message"
|
|
14
|
+
|
|
13
15
|
# = D-Bus main module
|
|
14
16
|
#
|
|
15
17
|
# Module containing all the D-Bus modules and classes.
|
|
@@ -144,6 +146,10 @@ module DBus
|
|
|
144
146
|
@params << [type, val]
|
|
145
147
|
end
|
|
146
148
|
|
|
149
|
+
# "l" or "B"
|
|
150
|
+
ENDIANNESS_CHAR = ENV.fetch("RUBY_DBUS_ENDIANNESS", HOST_END)
|
|
151
|
+
ENDIANNESS = RawMessage.endianness(ENDIANNESS_CHAR)
|
|
152
|
+
|
|
147
153
|
# FIXME: what are these? a message element constant enumeration?
|
|
148
154
|
# See method below, in a message, you have and array of optional parameters
|
|
149
155
|
# that come with an index, to determine their meaning. The values are in
|
|
@@ -165,14 +171,14 @@ module DBus
|
|
|
165
171
|
raise InvalidDestinationName
|
|
166
172
|
end
|
|
167
173
|
|
|
168
|
-
|
|
169
|
-
@params.each do |
|
|
170
|
-
|
|
174
|
+
params_marshaller = PacketMarshaller.new(endianness: ENDIANNESS)
|
|
175
|
+
@params.each do |type, value|
|
|
176
|
+
params_marshaller.append(type, value)
|
|
171
177
|
end
|
|
172
|
-
@body_length =
|
|
178
|
+
@body_length = params_marshaller.packet.bytesize
|
|
173
179
|
|
|
174
|
-
marshaller = PacketMarshaller.new
|
|
175
|
-
marshaller.append(Type::BYTE,
|
|
180
|
+
marshaller = PacketMarshaller.new(endianness: ENDIANNESS)
|
|
181
|
+
marshaller.append(Type::BYTE, ENDIANNESS_CHAR.ord)
|
|
176
182
|
marshaller.append(Type::BYTE, @message_type)
|
|
177
183
|
marshaller.append(Type::BYTE, @flags)
|
|
178
184
|
marshaller.append(Type::BYTE, @protocol)
|
|
@@ -191,10 +197,8 @@ module DBus
|
|
|
191
197
|
marshaller.append("a(yv)", headers)
|
|
192
198
|
|
|
193
199
|
marshaller.align(8)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
end
|
|
197
|
-
marshaller.packet
|
|
200
|
+
|
|
201
|
+
marshaller.packet + params_marshaller.packet
|
|
198
202
|
end
|
|
199
203
|
|
|
200
204
|
# Unmarshall a packet contained in the buffer _buf_ and set the
|
data/lib/dbus/message_queue.rb
CHANGED
|
@@ -18,10 +18,17 @@ module DBus
|
|
|
18
18
|
# The socket that is used to connect with the bus.
|
|
19
19
|
attr_reader :socket
|
|
20
20
|
|
|
21
|
+
# The buffer size for messages.
|
|
22
|
+
MSG_BUF_SIZE = 4096
|
|
23
|
+
|
|
21
24
|
def initialize(address)
|
|
25
|
+
DBus.logger.debug "MessageQueue: #{address}"
|
|
22
26
|
@address = address
|
|
23
27
|
@buffer = ""
|
|
28
|
+
# Reduce allocations by using a single buffer for our socket
|
|
29
|
+
@read_buffer = String.new(capacity: MSG_BUF_SIZE)
|
|
24
30
|
@is_tcp = false
|
|
31
|
+
@mutex = Mutex.new
|
|
25
32
|
connect
|
|
26
33
|
end
|
|
27
34
|
|
|
@@ -32,23 +39,28 @@ module DBus
|
|
|
32
39
|
# @raise EOFError
|
|
33
40
|
# @todo failure modes
|
|
34
41
|
def pop(blocking: true)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# FIXME: this is not enough, the R/W test deadlocks on shared connections
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
buffer_from_socket_nonblock
|
|
45
|
+
message = message_from_buffer_nonblock
|
|
46
|
+
if blocking
|
|
47
|
+
# we can block
|
|
48
|
+
while message.nil?
|
|
49
|
+
r, _d, _d = IO.select([@socket])
|
|
50
|
+
if r && r[0] == @socket
|
|
51
|
+
buffer_from_socket_nonblock
|
|
52
|
+
message = message_from_buffer_nonblock
|
|
53
|
+
end
|
|
44
54
|
end
|
|
45
55
|
end
|
|
56
|
+
message
|
|
46
57
|
end
|
|
47
|
-
message
|
|
48
58
|
end
|
|
49
59
|
|
|
50
60
|
def push(message)
|
|
51
|
-
@
|
|
61
|
+
@mutex.synchronize do
|
|
62
|
+
@socket.write(message.marshall)
|
|
63
|
+
end
|
|
52
64
|
end
|
|
53
65
|
alias << push
|
|
54
66
|
|
|
@@ -129,7 +141,7 @@ module DBus
|
|
|
129
141
|
|
|
130
142
|
# Initialize the connection to the bus.
|
|
131
143
|
def init_connection
|
|
132
|
-
client = Client.new(@socket)
|
|
144
|
+
client = Authentication::Client.new(@socket)
|
|
133
145
|
client.authenticate
|
|
134
146
|
end
|
|
135
147
|
|
|
@@ -150,15 +162,12 @@ module DBus
|
|
|
150
162
|
ret
|
|
151
163
|
end
|
|
152
164
|
|
|
153
|
-
# The buffer size for messages.
|
|
154
|
-
MSG_BUF_SIZE = 4096
|
|
155
|
-
|
|
156
165
|
# Fill (append) the buffer from data that might be available on the
|
|
157
166
|
# socket.
|
|
158
167
|
# @return [void]
|
|
159
168
|
# @raise EOFError
|
|
160
169
|
def buffer_from_socket_nonblock
|
|
161
|
-
@buffer += @socket.read_nonblock(MSG_BUF_SIZE)
|
|
170
|
+
@buffer += @socket.read_nonblock(MSG_BUF_SIZE, @read_buffer)
|
|
162
171
|
rescue EOFError
|
|
163
172
|
raise # the caller expects it
|
|
164
173
|
rescue Errno::EAGAIN
|
data/lib/dbus.rb
CHANGED
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
# License, version 2.1 as published by the Free Software Foundation.
|
|
11
11
|
# See the file "COPYING" for the exact licensing terms.
|
|
12
12
|
|
|
13
|
+
module DBus
|
|
14
|
+
# Byte signifying big endianness.
|
|
15
|
+
BIG_END = "B"
|
|
16
|
+
# Byte signifying little endianness.
|
|
17
|
+
LIL_END = "l"
|
|
18
|
+
|
|
19
|
+
# Byte signifying the host's endianness.
|
|
20
|
+
HOST_END = if [0x01020304].pack("L").unpack1("V") == 0x01020304
|
|
21
|
+
LIL_END
|
|
22
|
+
else
|
|
23
|
+
BIG_END
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
# ^ That's because dbus/message needs HOST_END early
|
|
27
|
+
|
|
13
28
|
require_relative "dbus/api_options"
|
|
14
29
|
require_relative "dbus/auth"
|
|
15
30
|
require_relative "dbus/bus"
|
|
@@ -41,18 +56,6 @@ module DBus
|
|
|
41
56
|
# Default socket name for the system bus.
|
|
42
57
|
SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"
|
|
43
58
|
|
|
44
|
-
# Byte signifying big endianness.
|
|
45
|
-
BIG_END = "B"
|
|
46
|
-
# Byte signifying little endianness.
|
|
47
|
-
LIL_END = "l"
|
|
48
|
-
|
|
49
|
-
# Byte signifying the host's endianness.
|
|
50
|
-
HOST_END = if [0x01020304].pack("L").unpack1("V") == 0x01020304
|
|
51
|
-
LIL_END
|
|
52
|
-
else
|
|
53
|
-
BIG_END
|
|
54
|
-
end
|
|
55
|
-
|
|
56
59
|
# Comparing symbols is faster than strings
|
|
57
60
|
# @return [:little,:big]
|
|
58
61
|
HOST_ENDIANNESS = RawMessage.endianness(HOST_END)
|
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
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env rspec
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "spec_helper"
|
|
5
|
+
require "dbus"
|
|
6
|
+
|
|
7
|
+
# Pedantic full coverage test.
|
|
8
|
+
# The happy paths are covered via calling classes
|
|
9
|
+
describe DBus::RawMessage do
|
|
10
|
+
describe ".endianness" do
|
|
11
|
+
it "returns :little for 'l'" do
|
|
12
|
+
expect(described_class.endianness("l")).to eq :little
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "returns :big for 'B'" do
|
|
16
|
+
expect(described_class.endianness("B")).to eq :big
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "raises for other strings" do
|
|
20
|
+
expect { described_class.endianness("m") }
|
|
21
|
+
.to raise_error(DBus::InvalidPacketException, /Incorrect endianness/)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "#align" do
|
|
26
|
+
it "raises for values other than 1 2 4 8" do
|
|
27
|
+
subject = described_class.new("l")
|
|
28
|
+
expect { subject.align(3) }.to raise_error(ArgumentError)
|
|
29
|
+
expect { subject.align(16) }.to raise_error(ArgumentError)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
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.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ruby DBus Team
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-
|
|
11
|
+
date: 2023-04-08 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,7 +187,9 @@ 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
|
|
192
|
+
- spec/raw_message_spec.rb
|
|
190
193
|
- spec/server_robustness_spec.rb
|
|
191
194
|
- spec/server_spec.rb
|
|
192
195
|
- spec/service_newapi.rb
|
|
@@ -208,7 +211,7 @@ homepage: https://github.com/mvidner/ruby-dbus
|
|
|
208
211
|
licenses:
|
|
209
212
|
- LGPL-2.1
|
|
210
213
|
metadata: {}
|
|
211
|
-
post_install_message:
|
|
214
|
+
post_install_message:
|
|
212
215
|
rdoc_options: []
|
|
213
216
|
require_paths:
|
|
214
217
|
- lib
|
|
@@ -223,9 +226,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
223
226
|
- !ruby/object:Gem::Version
|
|
224
227
|
version: '0'
|
|
225
228
|
requirements: []
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
signing_key:
|
|
229
|
+
rubygems_version: 3.3.0.dev
|
|
230
|
+
signing_key:
|
|
229
231
|
specification_version: 4
|
|
230
232
|
summary: Ruby module for interaction with D-Bus
|
|
231
233
|
test_files: []
|