Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 143 additions & 2 deletions login1/dbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/
// Package login1 provides integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/
package login1

import (
Expand All @@ -34,7 +34,7 @@ type Conn struct {
object dbus.BusObject
}

// New() establishes a connection to the system bus and authenticates.
// New establishes a connection to the system bus and authenticates.
func New() (*Conn, error) {
c := new(Conn)

Expand Down Expand Up @@ -74,6 +74,147 @@ func (c *Conn) initConnection() error {
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)
Expand Down
61 changes: 61 additions & 0 deletions login1/dbus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package login1

import (
"fmt"
"os/user"
"regexp"
"testing"
)

Expand All @@ -26,3 +29,61 @@ func TestNew(t *testing.T) {
t.Fatal(err)
}
}

func TestListSessions(t *testing.T) {
c, err := New()
if err != nil {
t.Fatal(err)
}

sessions, err := c.ListSessions()
if err != nil {
t.Fatal(err)
}

if len(sessions) > 0 {
for _, s := range sessions {
lookup, err := user.Lookup(s.User)
if err != nil {
t.Fatal(err)
}
if fmt.Sprint(s.UID) != lookup.Uid {
t.Fatalf("expected uid '%d' but got '%s'", s.UID, lookup.Uid)
}

validPath := regexp.MustCompile(`/org/freedesktop/login1/session/_[0-9]+`)
if !validPath.MatchString(fmt.Sprint(s.Path)) {
t.Fatalf("invalid session path: %s", s.Path)
}
}
}
}

func TestListUsers(t *testing.T) {
c, err := New()
if err != nil {
t.Fatal(err)
}

users, err := c.ListUsers()
if err != nil {
t.Fatal(err)
}

if len(users) > 0 {
for _, u := range users {
lookup, err := user.Lookup(u.Name)
if err != nil {
t.Fatal(err)
}
if fmt.Sprint(u.UID) != lookup.Uid {
t.Fatalf("expected uid '%d' but got '%s'", u.UID, lookup.Uid)
}

validPath := regexp.MustCompile(`/org/freedesktop/login1/user/_[0-9]+`)
if !validPath.MatchString(fmt.Sprint(u.Path)) {
t.Fatalf("invalid user path: %s", u.Path)
}
}
}
}