chiark / gitweb /
logind: implement ACL management
authorLennart Poettering <lennart@poettering.net>
Tue, 24 May 2011 02:20:35 +0000 (04:20 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Jun 2011 17:29:44 +0000 (19:29 +0200)
.gitignore
Makefile.am
configure.ac
src/logind-acl.c [new file with mode: 0644]
src/logind-acl.h [new file with mode: 0644]
src/logind-seat.c
src/logind-seat.h
src/logind-session.c
src/logind.c
src/logind.h
src/uaccess.c [new file with mode: 0644]

index c2820b0..7bd22c5 100644 (file)
@@ -1,3 +1,4 @@
+systemd-uaccess
 systemd-logind
 systemd-hostnamed
 systemd-binfmt
index 79ccbbe..f455f21 100644 (file)
@@ -159,7 +159,8 @@ rootlibexec_PROGRAMS = \
        systemd-detect-virt \
        systemd-sysctl \
         systemd-hostnamed \
-        systemd-logind
+        systemd-logind \
+        systemd-uaccess
 
 if ENABLE_BINFMT
 rootlibexec_PROGRAMS += \
@@ -813,6 +814,7 @@ systemd_logind_SOURCES = \
         src/logind-seat.c \
         src/logind-session.c \
         src/logind-user.c \
+        src/logind-acl.c \
         src/dbus-common.c \
         src/dbus-loop.c \
         src/cgroup-util.c
@@ -820,13 +822,29 @@ systemd_logind_SOURCES = \
 systemd_logind_CFLAGS = \
        $(AM_CFLAGS) \
        $(DBUS_CFLAGS) \
-        $(UDEV_CFLAGS)
+        $(UDEV_CFLAGS) \
+        $(ACL_CFLAGS)
 
 systemd_logind_LDADD = \
        libsystemd-basic.la \
        libsystemd-daemon.la \
        $(DBUS_LIBS) \
-        $(UDEV_LIBS)
+        $(UDEV_LIBS) \
+        $(ACL_LIBS)
+
+systemd_uaccess_SOURCES = \
+       src/uaccess.c \
+        src/logind-acl.c
+
+systemd_uaccess_CFLAGS = \
+       $(AM_CFLAGS) \
+        $(UDEV_CFLAGS) \
+        $(ACL_CFLAGS)
+
+systemd_uaccess_LDADD = \
+       libsystemd-basic.la \
+        $(UDEV_LIBS) \
+        $(ACL_LIBS)
 
 systemd_shutdown_SOURCES = \
        src/mount-setup.c \
index eb5fb6a..0dd185b 100644 (file)
@@ -193,6 +193,43 @@ fi
 AC_SUBST(PAM_LIBS)
 AM_CONDITIONAL([HAVE_PAM], [test "x$have_pam" != xno])
 
+AC_ARG_ENABLE([acl],
+        AS_HELP_STRING([--disable-acl],[Disable optional ACL support]),
+                [case "${enableval}" in
+                        yes) have_acl=yes ;;
+                        no) have_acl=no ;;
+                        *) AC_MSG_ERROR(bad value ${enableval} for --disable-acl) ;;
+                esac],
+                [have_acl=auto])
+
+if test "x${have_acl}" != xno ; then
+        AC_CHECK_HEADERS(
+                [sys/acl.h acl/libacl.h],
+                [have_acl=yes],
+                [if test "x$have_acl" = xyes ; then
+                        AC_MSG_ERROR([*** ACL headers not found.])
+                fi])
+
+        AC_CHECK_LIB(
+                [acl],
+                [acl_get_file],
+                [have_acl=yes],
+                [if test "x$have_acl" = xyes ; then
+                        AC_MSG_ERROR([*** libacl not found.])
+                fi])
+
+        if test "x$have_acl" = xyes ; then
+                ACL_LIBS="-lacl"
+                AC_DEFINE(HAVE_ACL, 1, [ACL available])
+        else
+                have_acl=no
+        fi
+else
+        ACL_LIBS=
+fi
+AC_SUBST(ACL_LIBS)
+AM_CONDITIONAL([HAVE_ACL], [test "x$have_acl" != xno])
+
 AC_ARG_ENABLE([audit],
         AS_HELP_STRING([--disable-audit],[Disable optional AUDIT support]),
                 [case "${enableval}" in
@@ -496,6 +533,7 @@ echo "
         PAM:                     ${have_pam}
         AUDIT:                   ${have_audit}
         SELinux:                 ${have_selinux}
+        ACL:                     ${have_acl}
         binfmt:                  ${have_binfmt}
         prefix:                  ${prefix}
         root dir:                ${with_rootdir}
diff --git a/src/logind-acl.c b/src/logind-acl.c
new file mode 100644 (file)
index 0000000..3df104f
--- /dev/null
@@ -0,0 +1,282 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2011 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <sys/acl.h>
+#include <acl/libacl.h>
+#include <errno.h>
+#include <string.h>
+
+#include "logind-acl.h"
+#include "util.h"
+
+static int find_acl(acl_t acl, uid_t uid, acl_entry_t *entry) {
+        acl_entry_t i;
+        int found;
+
+        assert(acl);
+        assert(entry);
+
+        for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+             found > 0;
+             found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+
+                acl_tag_t tag;
+                uid_t *u;
+                bool b;
+
+                if (acl_get_tag_type(i, &tag) < 0)
+                        return -errno;
+
+                if (tag != ACL_USER)
+                        continue;
+
+                u = acl_get_qualifier(i);
+                if (!u)
+                        return -errno;
+
+                b = *u == uid;
+                free(u);
+
+                if (b) {
+                        *entry = i;
+                        return 1;
+                }
+        }
+
+        if (found < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int flush_acl(acl_t acl) {
+        acl_entry_t i;
+        int found;
+        bool changed = false;
+
+        assert(acl);
+
+        for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+             found > 0;
+             found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+
+                acl_tag_t tag;
+
+                if (acl_get_tag_type(i, &tag) < 0)
+                        return -errno;
+
+                if (tag != ACL_USER)
+                        continue;
+
+                if (acl_delete_entry(acl, i) < 0)
+                        return -errno;
+
+                changed = true;
+        }
+
+        if (found < 0)
+                return -errno;
+
+        return changed;
+}
+
+int devnode_acl(const char *path,
+                bool flush,
+                bool del, uid_t old_uid,
+                bool add, uid_t new_uid) {
+
+        acl_t acl;
+        int r;
+        bool changed = false;
+
+        assert(path);
+
+        acl = acl_get_file(path, ACL_TYPE_ACCESS);
+        if (!acl)
+                return -errno;
+
+        if (flush) {
+
+                r = flush_acl(acl);
+                if (r < 0)
+                        goto finish;
+                if (r > 0)
+                        changed = true;
+
+        } else if (del && old_uid > 0) {
+                acl_entry_t entry;
+
+                r = find_acl(acl, old_uid, &entry);
+                if (r < 0)
+                        goto finish;
+
+                if (r > 0) {
+                        if (acl_delete_entry(acl, entry) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        changed = true;
+                }
+        }
+
+        if (add && new_uid > 0) {
+                acl_entry_t entry;
+                acl_permset_t permset;
+                int rd, wt;
+
+                r = find_acl(acl, new_uid, &entry);
+                if (r < 0)
+                        goto finish;
+
+                if (r == 0) {
+                        if (acl_create_entry(&acl, &entry) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        if (acl_set_tag_type(entry, ACL_USER) < 0 ||
+                            acl_set_qualifier(entry, &new_uid) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+                }
+
+                if (acl_get_permset(entry, &permset) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                rd = acl_get_perm(permset, ACL_READ);
+                if (rd < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                wt = acl_get_perm(permset, ACL_WRITE);
+                if (wt < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (!rd || !wt) {
+
+                        if (acl_add_perm(permset, ACL_READ|ACL_WRITE) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        changed = true;
+                }
+        }
+
+        if (!changed)
+                goto finish;
+
+        if (acl_calc_mask(&acl) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (acl_set_file(path, ACL_TYPE_ACCESS, acl) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        acl_free(acl);
+
+        return r;
+}
+
+int devnode_acl_all(struct udev *udev,
+                    const char *seat,
+                    bool flush,
+                    bool del, uid_t old_uid,
+                    bool add, uid_t new_uid) {
+
+        struct udev_list_entry *item = NULL, *first = NULL;
+        struct udev_enumerate *e;
+        int r;
+
+        assert(udev);
+
+        if (!seat)
+                seat = "seat0";
+
+        e = udev_enumerate_new(udev);
+        if (!e)
+                return -ENOMEM;
+
+        r = udev_enumerate_add_match_tag(e, "uaccess");
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_add_match_tag(e, seat);
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_scan_devices(e);
+        if (r < 0)
+                goto finish;
+
+        first = udev_enumerate_get_list_entry(e);
+        udev_list_entry_foreach(item, first) {
+                struct udev_device *d;
+                const char *node, *sn;
+
+                d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
+                if (!d) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                sn = udev_device_get_property_value(d, "SEAT");
+                if (!sn)
+                        sn = "seat0";
+
+                if (!streq(seat, sn)) {
+                        udev_device_unref(d);
+                        continue;
+                }
+
+                node = udev_device_get_devnode(d);
+                udev_device_unref(d);
+
+                if (!node) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                r = devnode_acl(node, flush, del, old_uid, add, new_uid);
+                if (r < 0)
+                        goto finish;
+        }
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        return r;
+}
diff --git a/src/logind-acl.h b/src/logind-acl.h
new file mode 100644 (file)
index 0000000..9c88a80
--- /dev/null
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologindaclhfoo
+#define foologindaclhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2011 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <libudev.h>
+
+int devnode_acl(const char *path,
+                bool flush,
+                bool del, uid_t old_uid,
+                bool add, uid_t new_uid);
+
+int devnode_acl_all(struct udev *udev,
+                    const char *seat,
+                    bool flush,
+                    bool del, uid_t old_uid,
+                    bool add, uid_t new_uid);
+
+#endif
index 3154900..743ded6 100644 (file)
 #include <fcntl.h>
 #include <sys/ioctl.h>
 #include <linux/vt.h>
+#include <string.h>
 
 #include "logind-seat.h"
+#include "logind-acl.h"
 #include "util.h"
 
 Seat *seat_new(Manager *m, const char *id) {
@@ -179,40 +181,32 @@ int seat_preallocate_vts(Seat *s) {
         return r;
 }
 
-int seat_apply_acls(Seat *s) {
-        assert(s);
-
-
-        return 0;
-}
-
-static int vt_is_busy(int vtnr) {
-        struct vt_stat vt_stat;
-        int r = 0, fd;
-
-        assert(vtnr >= 1);
+int seat_apply_acls(Seat *s, Session *old_active) {
+        int r;
 
-        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
+        assert(s);
 
-        if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
-                r = -errno;
-        else
-                r = !!(vt_stat.v_state & (1 << vtnr));
+        r = devnode_acl_all(s->manager->udev,
+                            s->id,
+                            false,
+                            !!old_active, old_active ? old_active->user->uid : 0,
+                            !!s->active, s->active ? s->active->user->uid : 0);
 
-        close_nointr_nofail(fd);
+        if (r < 0)
+                log_error("Failed to apply ACLs: %s", strerror(-r));
 
         return r;
 }
 
-void seat_active_vt_changed(Seat *s, int vtnr) {
+int seat_active_vt_changed(Seat *s, int vtnr) {
         Session *i;
+        Session *old_active;
 
         assert(s);
         assert(vtnr >= 1);
         assert(s->manager->vtconsole == s);
 
+        old_active = s->active;
         s->active = NULL;
 
         LIST_FOREACH(sessions_by_seat, i, s->sessions)
@@ -221,10 +215,13 @@ void seat_active_vt_changed(Seat *s, int vtnr) {
                         break;
                 }
 
-        seat_apply_acls(s);
+        if (old_active == s->active)
+                return 0;
+
+        seat_apply_acls(s, old_active);
+        manager_spawn_autovt(s->manager, vtnr);
 
-        if (vt_is_busy(vtnr) == 0)
-                manager_spawn_autovt(s->manager, vtnr);
+        return 0;
 }
 
 int seat_stop(Seat *s) {
index 2fe7949..4b6b340 100644 (file)
@@ -45,8 +45,8 @@ struct Seat {
 Seat *seat_new(Manager *m, const char *id);
 void seat_free(Seat *s);
 int seat_preallocate_vts(Seat *s);
-void seat_active_vt_changed(Seat *s, int vtnr);
-int seat_apply_acls(Seat *s);
+int seat_active_vt_changed(Seat *s, int vtnr);
+int seat_apply_acls(Seat *s, Session *old_active);
 int seat_stop(Seat *s);
 int seat_save(Seat *s);
 int seat_load(Seat *s);
index 7bdf487..c10f5e6 100644 (file)
@@ -174,6 +174,7 @@ int session_load(Session *s) {
 
 int session_activate(Session *s) {
         int r;
+        Session *old_active;
 
         assert(s);
 
@@ -192,9 +193,13 @@ int session_activate(Session *s) {
         if (r < 0)
                 return r;
 
+        old_active = s->seat->active;
         s->seat->active = s;
 
-        return seat_apply_acls(s->seat);
+        seat_apply_acls(s->seat, old_active);
+        manager_spawn_autovt(s->manager, s->vtnr);
+
+        return 0;
 }
 
 bool x11_display_is_local(const char *display) {
index 0c26aad..7e9b706 100644 (file)
@@ -26,6 +26,8 @@
 #include <string.h>
 #include <unistd.h>
 #include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
 
 #include "logind.h"
 #include "dbus-common.h"
@@ -290,7 +292,7 @@ int manager_enumerate_devices(Manager *m) {
 
         e = udev_enumerate_new(m->udev);
         if (!e)
-                return -ENOMEM;
+                goto finish;
 
         if (udev_enumerate_add_match_subsystem(e, "graphics") < 0)
                 goto finish;
@@ -627,9 +629,37 @@ int manager_dispatch_console(Manager *m) {
         return 0;
 }
 
+static int vt_is_busy(int vtnr) {
+        struct vt_stat vt_stat;
+        int r = 0, fd;
+
+        assert(vtnr >= 1);
+
+        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
+                r = -errno;
+        else
+                r = !!(vt_stat.v_state & (1 << vtnr));
+
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
 int manager_spawn_autovt(Manager *m, int vtnr) {
+        int r;
+
         assert(m);
 
+        r = vt_is_busy(vtnr);
+        if (r != 0)
+                return r;
+
+        /* ... */
+
         return 0;
 }
 
@@ -849,7 +879,7 @@ int manager_run(Manager *m) {
 
 int main(int argc, char *argv[]) {
         Manager *m = NULL;
-        int r = 0;
+        int r;
 
         log_set_target(LOG_TARGET_AUTO);
         log_parse_environment();
index 0d3bd89..be5dab7 100644 (file)
  *
  * recreate VTs when disallocated
  * udev rules
+ * PAM rewrite
  * spawn user systemd
+ * dbus API
+ *
  * non-local X11 server
- * udev-acl
  * reboot/shutdown halt management
- * PAM rewrite
  */
 
 typedef struct Manager Manager;
diff --git a/src/uaccess.c b/src/uaccess.c
new file mode 100644 (file)
index 0000000..e55ab51
--- /dev/null
@@ -0,0 +1,87 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2011 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+
+#include "logind-acl.h"
+#include "util.h"
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+        int r;
+        const char *path, *seat;
+        char *p, *active_uid = NULL;
+        unsigned long ul;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        if (argc != 2) {
+                log_error("This program expects two argument.");
+                r = -EINVAL;
+                goto finish;
+        }
+
+        path = argv[1];
+        seat = argv[2];
+
+        p = strappend("/run/systemd/seat/", seat);
+        if (!p) {
+                log_error("Out of memory.");
+                goto finish;
+        }
+
+        r = parse_env_file(p, NEWLINE,
+                           "ACTIVE_UID", &active_uid,
+                           NULL);
+        free(p);
+
+        if (r < 0) {
+                if (errno == ENOENT) {
+                        r = 0;
+                        goto finish;
+                }
+
+                log_error("Failed to read seat data for %s: %s", seat, strerror(-r));
+                goto finish;
+        }
+
+        r = safe_atolu(active_uid, &ul);
+        if (r < 0) {
+                log_error("Failed to parse active UID value %s: %s", active_uid, strerror(-r));
+                goto finish;
+        }
+
+        r = devnode_acl(path, true, false, 0, true, (uid_t) ul);
+        if (r < 0) {
+                log_error("Failed to apply ACL on %s: %s", path, strerror(-r));
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        free(active_uid);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}