blob: 6d2c99bcb18927b9335f222a81773698caa07f01 [file] [log] [blame]
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package login1 provides integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/
package login1
import (
"fmt"
"os"
"strconv"
"github.com/godbus/dbus"
)
const (
dbusInterface = "org.freedesktop.login1.Manager"
dbusPath = "/org/freedesktop/login1"
)
// Conn is a connection to systemds dbus endpoint.
type Conn struct {
conn *dbus.Conn
object dbus.BusObject
}
// New establishes a connection to the system bus and authenticates.
func New() (*Conn, error) {
c := new(Conn)
if err := c.initConnection(); err != nil {
return nil, err
}
return c, nil
}
// Close closes the dbus connection
func (c *Conn) Close() {
if c == nil {
return
}
if c.conn != nil {
c.conn.Close()
}
}
func (c *Conn) initConnection() error {
var err error
c.conn, err = dbus.SystemBusPrivate()
if err != nil {
return err
}
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.conn.Auth(methods)
if err != nil {
c.conn.Close()
return err
}
err = c.conn.Hello()
if err != nil {
c.conn.Close()
return err
}
c.object = c.conn.Object("org.freedesktop.login1", dbus.ObjectPath(dbusPath))
return nil
}
// Session object definition.
type Session struct {
ID string
UID uint32
User string
Seat string
Path dbus.ObjectPath
}
// User object definition.
type User struct {
UID uint32
Name string
Path dbus.ObjectPath
}
func (s Session) toInterface() []interface{} {
return []interface{}{s.ID, s.UID, s.User, s.Seat, s.Path}
}
func sessionFromInterfaces(session []interface{}) (*Session, error) {
if len(session) < 5 {
return nil, fmt.Errorf("invalid number of session fields: %d", len(session))
}
id, ok := session[0].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 0 to string")
}
uid, ok := session[1].(uint32)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 1 to uint32")
}
user, ok := session[2].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 2 to string")
}
seat, ok := session[3].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 2 to string")
}
path, ok := session[4].(dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 4 to ObjectPath")
}
ret := Session{ID: id, UID: uid, User: user, Seat: seat, Path: path}
return &ret, nil
}
func userFromInterfaces(user []interface{}) (*User, error) {
if len(user) < 3 {
return nil, fmt.Errorf("invalid number of user fields: %d", len(user))
}
uid, ok := user[0].(uint32)
if !ok {
return nil, fmt.Errorf("failed to typecast user field 0 to uint32")
}
name, ok := user[1].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 1 to string")
}
path, ok := user[2].(dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("failed to typecast user field 2 to ObjectPath")
}
ret := User{UID: uid, Name: name, Path: path}
return &ret, nil
}
// GetSession may be used to get the session object path for the session with the specified ID.
func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) {
var out interface{}
if err := c.object.Call(dbusInterface+".GetSession", 0, id).Store(&out); err != nil {
return "", err
}
ret, ok := out.(dbus.ObjectPath)
if !ok {
return "", fmt.Errorf("failed to typecast session to ObjectPath")
}
return ret, nil
}
// ListSessions returns an array with all current sessions.
func (c *Conn) ListSessions() ([]Session, error) {
out := [][]interface{}{}
if err := c.object.Call(dbusInterface+".ListSessions", 0).Store(&out); err != nil {
return nil, err
}
ret := []Session{}
for _, el := range out {
session, err := sessionFromInterfaces(el)
if err != nil {
return nil, err
}
ret = append(ret, *session)
}
return ret, nil
}
// ListUsers returns an array with all currently logged in users.
func (c *Conn) ListUsers() ([]User, error) {
out := [][]interface{}{}
if err := c.object.Call(dbusInterface+".ListUsers", 0).Store(&out); err != nil {
return nil, err
}
ret := []User{}
for _, el := range out {
user, err := userFromInterfaces(el)
if err != nil {
return nil, err
}
ret = append(ret, *user)
}
return ret, nil
}
// LockSession asks the session with the specified ID to activate the screen lock.
func (c *Conn) LockSession(id string) {
c.object.Call(dbusInterface+".LockSession", 0, id)
}
// LockSessions asks all sessions to activate the screen locks. This may be used to lock any access to the machine in one action.
func (c *Conn) LockSessions() {
c.object.Call(dbusInterface+".LockSessions", 0)
}
// TerminateSession forcibly terminate one specific session.
func (c *Conn) TerminateSession(id string) {
c.object.Call(dbusInterface+".TerminateSession", 0, id)
}
// TerminateUser forcibly terminates all processes of a user.
func (c *Conn) TerminateUser(uid uint32) {
c.object.Call(dbusInterface+".TerminateUser", 0, uid)
}
// Reboot asks logind for a reboot optionally asking for auth.
func (c *Conn) Reboot(askForAuth bool) {
c.object.Call(dbusInterface+".Reboot", 0, askForAuth)
}
// Inhibit takes inhibition lock in logind.
func (c *Conn) Inhibit(what, who, why, mode string) (*os.File, error) {
var fd dbus.UnixFD
err := c.object.Call(dbusInterface+".Inhibit", 0, what, who, why, mode).Store(&fd)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), "inhibit"), nil
}
// Subscribe to signals on the logind dbus
func (c *Conn) Subscribe(members ...string) chan *dbus.Signal {
for _, member := range members {
c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
fmt.Sprintf("type='signal',interface='org.freedesktop.login1.Manager',member='%s'", member))
}
ch := make(chan *dbus.Signal, 10)
c.conn.Signal(ch)
return ch
}
// PowerOff asks logind for a power off optionally asking for auth.
func (c *Conn) PowerOff(askForAuth bool) {
c.object.Call(dbusInterface+".PowerOff", 0, askForAuth)
}