diff --git a/login1/dbus.go b/login1/dbus.go index 5e1e670b..803182c3 100644 --- a/login1/dbus.go +++ b/login1/dbus.go @@ -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 ( @@ -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) @@ -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) diff --git a/login1/dbus_test.go b/login1/dbus_test.go index d160f470..b570c921 100644 --- a/login1/dbus_test.go +++ b/login1/dbus_test.go @@ -15,6 +15,9 @@ package login1 import ( + "fmt" + "os/user" + "regexp" "testing" ) @@ -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) + } + } + } +}