...

Source file src/github.com/coreos/go-systemd/v22/dbus/dbus.go

Documentation: github.com/coreos/go-systemd/v22/dbus

     1  // Copyright 2015 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Integration with the systemd D-Bus API.  See http://www.freedesktop.org/wiki/Software/systemd/dbus/
    16  package dbus
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"fmt"
    22  	"os"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  
    27  	"github.com/godbus/dbus/v5"
    28  )
    29  
    30  const (
    31  	alpha        = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
    32  	num          = `0123456789`
    33  	alphanum     = alpha + num
    34  	signalBuffer = 100
    35  )
    36  
    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  }
    44  
    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)
    63  }
    64  
    65  // pathBusUnescape is the inverse of PathBusEscape.
    66  func pathBusUnescape(path string) string {
    67  	if path == "_" {
    68  		return ""
    69  	}
    70  	n := []byte{}
    71  	for i := 0; i < len(path); i++ {
    72  		c := path[i]
    73  		if c == '_' && i+2 < len(path) {
    74  			res, err := hex.DecodeString(path[i+1 : i+3])
    75  			if err == nil {
    76  				n = append(n, res...)
    77  			}
    78  			i += 2
    79  		} else {
    80  			n = append(n, c)
    81  		}
    82  	}
    83  	return string(n)
    84  }
    85  
    86  // Conn is a connection to systemd's dbus endpoint.
    87  type Conn struct {
    88  	// sysconn/sysobj are only used to call dbus methods
    89  	sysconn *dbus.Conn
    90  	sysobj  dbus.BusObject
    91  
    92  	// sigconn/sigobj are only used to receive dbus signals
    93  	sigconn *dbus.Conn
    94  	sigobj  dbus.BusObject
    95  
    96  	jobListener struct {
    97  		jobs map[dbus.ObjectPath]chan<- string
    98  		sync.Mutex
    99  	}
   100  	subStateSubscriber struct {
   101  		updateCh chan<- *SubStateUpdate
   102  		errCh    chan<- error
   103  		sync.Mutex
   104  		ignore      map[dbus.ObjectPath]int64
   105  		cleanIgnore int64
   106  	}
   107  	propertiesSubscriber struct {
   108  		updateCh chan<- *PropertiesUpdate
   109  		errCh    chan<- error
   110  		sync.Mutex
   111  	}
   112  }
   113  
   114  // Deprecated: use NewWithContext instead.
   115  func New() (*Conn, error) {
   116  	return NewWithContext(context.Background())
   117  }
   118  
   119  // NewWithContext establishes a connection to any available bus and authenticates.
   120  // Callers should call Close() when done with the connection.
   121  func NewWithContext(ctx context.Context) (*Conn, error) {
   122  	conn, err := NewSystemConnectionContext(ctx)
   123  	if err != nil && os.Geteuid() == 0 {
   124  		return NewSystemdConnectionContext(ctx)
   125  	}
   126  	return conn, err
   127  }
   128  
   129  // Deprecated: use NewSystemConnectionContext instead.
   130  func NewSystemConnection() (*Conn, error) {
   131  	return NewSystemConnectionContext(context.Background())
   132  }
   133  
   134  // NewSystemConnectionContext establishes a connection to the system bus and authenticates.
   135  // Callers should call Close() when done with the connection.
   136  func NewSystemConnectionContext(ctx context.Context) (*Conn, error) {
   137  	return NewConnection(func() (*dbus.Conn, error) {
   138  		return dbusAuthHelloConnection(ctx, dbus.SystemBusPrivate)
   139  	})
   140  }
   141  
   142  // Deprecated: use NewUserConnectionContext instead.
   143  func NewUserConnection() (*Conn, error) {
   144  	return NewUserConnectionContext(context.Background())
   145  }
   146  
   147  // NewUserConnectionContext establishes a connection to the session bus and
   148  // authenticates. This can be used to connect to systemd user instances.
   149  // Callers should call Close() when done with the connection.
   150  func NewUserConnectionContext(ctx context.Context) (*Conn, error) {
   151  	return NewConnection(func() (*dbus.Conn, error) {
   152  		return dbusAuthHelloConnection(ctx, dbus.SessionBusPrivate)
   153  	})
   154  }
   155  
   156  // Deprecated: use NewSystemdConnectionContext instead.
   157  func NewSystemdConnection() (*Conn, error) {
   158  	return NewSystemdConnectionContext(context.Background())
   159  }
   160  
   161  // NewSystemdConnectionContext establishes a private, direct connection to systemd.
   162  // This can be used for communicating with systemd without a dbus daemon.
   163  // Callers should call Close() when done with the connection.
   164  func NewSystemdConnectionContext(ctx context.Context) (*Conn, error) {
   165  	return NewConnection(func() (*dbus.Conn, error) {
   166  		// We skip Hello when talking directly to systemd.
   167  		return dbusAuthConnection(ctx, func(opts ...dbus.ConnOption) (*dbus.Conn, error) {
   168  			return dbus.Dial("unix:path=/run/systemd/private", opts...)
   169  		})
   170  	})
   171  }
   172  
   173  // Close closes an established connection.
   174  func (c *Conn) Close() {
   175  	c.sysconn.Close()
   176  	c.sigconn.Close()
   177  }
   178  
   179  // Connected returns whether conn is connected
   180  func (c *Conn) Connected() bool {
   181  	return c.sysconn.Connected() && c.sigconn.Connected()
   182  }
   183  
   184  // NewConnection establishes a connection to a bus using a caller-supplied function.
   185  // This allows connecting to remote buses through a user-supplied mechanism.
   186  // The supplied function may be called multiple times, and should return independent connections.
   187  // The returned connection must be fully initialised: the org.freedesktop.DBus.Hello call must have succeeded,
   188  // and any authentication should be handled by the function.
   189  func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) {
   190  	sysconn, err := dialBus()
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	sigconn, err := dialBus()
   196  	if err != nil {
   197  		sysconn.Close()
   198  		return nil, err
   199  	}
   200  
   201  	c := &Conn{
   202  		sysconn: sysconn,
   203  		sysobj:  systemdObject(sysconn),
   204  		sigconn: sigconn,
   205  		sigobj:  systemdObject(sigconn),
   206  	}
   207  
   208  	c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64)
   209  	c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
   210  
   211  	// Setup the listeners on jobs so that we can get completions
   212  	c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
   213  		"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
   214  
   215  	c.dispatch()
   216  	return c, nil
   217  }
   218  
   219  // GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager
   220  // interface. The value is returned in its string representation, as defined at
   221  // https://developer.gnome.org/glib/unstable/gvariant-text.html.
   222  func (c *Conn) GetManagerProperty(prop string) (string, error) {
   223  	variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop)
   224  	if err != nil {
   225  		return "", err
   226  	}
   227  	return variant.String(), nil
   228  }
   229  
   230  func dbusAuthConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
   231  	conn, err := createBus(dbus.WithContext(ctx))
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	// Only use EXTERNAL method, and hardcode the uid (not username)
   237  	// to avoid a username lookup (which requires a dynamically linked
   238  	// libc)
   239  	methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
   240  
   241  	err = conn.Auth(methods)
   242  	if err != nil {
   243  		conn.Close()
   244  		return nil, err
   245  	}
   246  
   247  	return conn, nil
   248  }
   249  
   250  func dbusAuthHelloConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
   251  	conn, err := dbusAuthConnection(ctx, createBus)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	if err = conn.Hello(); err != nil {
   257  		conn.Close()
   258  		return nil, err
   259  	}
   260  
   261  	return conn, nil
   262  }
   263  
   264  func systemdObject(conn *dbus.Conn) dbus.BusObject {
   265  	return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
   266  }
   267  

View as plain text