Skip to content

Commit a729065

Browse files
committed
dbus: properly escape unit names when creating object paths
This draws from http://cgit.freedesktop.org/systemd/systemd/tree/src/shared/bus-label.c#n32
1 parent 75041d0 commit a729065

File tree

3 files changed

+84
-18
lines changed

3 files changed

+84
-18
lines changed

dbus/dbus.go

+32-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ limitations under the License.
1818
package dbus
1919

2020
import (
21+
"fmt"
2122
"os"
2223
"strconv"
2324
"strings"
@@ -26,16 +27,39 @@ import (
2627
"github.com/godbus/dbus"
2728
)
2829

29-
const signalBuffer = 100
30+
const (
31+
alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
32+
num = `0123456789`
33+
alphanum = alpha + num
34+
signalBuffer = 100
35+
)
3036

31-
// ObjectPath creates a dbus.ObjectPath using the rules that systemd uses for
32-
// serializing special characters.
33-
func ObjectPath(path string) dbus.ObjectPath {
34-
path = strings.Replace(path, ".", "_2e", -1)
35-
path = strings.Replace(path, "-", "_2d", -1)
36-
path = strings.Replace(path, "@", "_40", -1)
37+
// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
38+
func needsEscape(i int, b byte) bool {
39+
// Escape everything that is not a-z-A-Z-0-9
40+
// Also escape 0-9 if it's the first character
41+
return strings.IndexByte(alphanum, b) == -1 ||
42+
(i == 0 && strings.IndexByte(num, b) != -1)
43+
}
3744

38-
return dbus.ObjectPath(path)
45+
// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
46+
// rules that systemd uses for serializing special characters.
47+
func PathBusEscape(path string) string {
48+
// Special case the empty string
49+
if len(path) == 0 {
50+
return "_"
51+
}
52+
n := []byte{}
53+
for i := 0; i < len(path); i++ {
54+
c := path[i]
55+
if needsEscape(i, c) {
56+
e := fmt.Sprintf("_%x", c)
57+
n = append(n, []byte(e)...)
58+
} else {
59+
n = append(n, c)
60+
}
61+
}
62+
return string(n)
3963
}
4064

4165
// Conn is a connection to systemd's dbus endpoint.

dbus/dbus_test.go

+46-8
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,53 @@ import (
2020
"testing"
2121
)
2222

23-
// TestObjectPath ensures path encoding of the systemd rules works.
24-
func TestObjectPath(t *testing.T) {
25-
input := "/silly-path/to@a/unit..service"
26-
output := ObjectPath(input)
27-
expected := "/silly_2dpath/to_40a/unit_2e_2eservice"
28-
29-
if string(output) != expected {
30-
t.Fatalf("Output '%s' did not match expected '%s'", output, expected)
23+
func TestNeedsEscape(t *testing.T) {
24+
// Anything not 0-9a-zA-Z should always be escaped
25+
for want, vals := range map[bool][]byte{
26+
false: []byte{'a', 'b', 'z', 'A', 'Q', '1', '4', '9'},
27+
true: []byte{'#', '%', '$', '!', '.', '_', '-', '%', '\\'},
28+
} {
29+
for i := 1; i < 10; i++ {
30+
for _, b := range vals {
31+
got := needsEscape(i, b)
32+
if got != want {
33+
t.Errorf("needsEscape(%d, %c) returned %t, want %t", i, b, got, want)
34+
}
35+
}
36+
}
3137
}
38+
39+
// 0-9 in position 0 should be escaped
40+
for want, vals := range map[bool][]byte{
41+
false: []byte{'A', 'a', 'e', 'x', 'Q', 'Z'},
42+
true: []byte{'0', '4', '5', '9'},
43+
} {
44+
for _, b := range vals {
45+
got := needsEscape(0, b)
46+
if got != want {
47+
t.Errorf("needsEscape(0, %c) returned %t, want %t", b, got, want)
48+
}
49+
}
50+
}
51+
52+
}
53+
54+
func TestPathBusEscape(t *testing.T) {
55+
for in, want := range map[string]string{
56+
"": "_",
57+
"foo.service": "foo_2eservice",
58+
"foobar": "foobar",
59+
"woof@woof.service": "woof_40woof_2eservice",
60+
"0123456": "_30123456",
61+
"account_db.service": "account_5fdb_2eservice",
62+
"got-dashes": "got_2ddashes",
63+
} {
64+
got := PathBusEscape(in)
65+
if got != want {
66+
t.Errorf("bad result for PathBusEscape(%s): got %q, want %q", in, got, want)
67+
}
68+
}
69+
3270
}
3371

3472
// TestNew ensures that New() works without errors.

dbus/methods.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]inte
149149
var err error
150150
var props map[string]dbus.Variant
151151

152-
path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit)
152+
path := unitPath(unit)
153153
if !path.IsValid() {
154154
return nil, errors.New("invalid unit name: " + unit)
155155
}
@@ -177,7 +177,7 @@ func (c *Conn) getProperty(unit string, dbusInterface string, propertyName strin
177177
var err error
178178
var prop dbus.Variant
179179

180-
path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit)
180+
path := unitPath(unit)
181181
if !path.IsValid() {
182182
return nil, errors.New("invalid unit name: " + unit)
183183
}
@@ -400,3 +400,7 @@ type DisableUnitFileChange struct {
400400
func (c *Conn) Reload() error {
401401
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
402402
}
403+
404+
func unitPath(name string) dbus.ObjectPath {
405+
return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
406+
}

0 commit comments

Comments
 (0)