chiark / gitweb /
udev-acl: fix memleak
[elogind.git] / extras / udev-acl / udev-acl.c
index 3eb29fe2f28951bb5ba91f527dcbd0d9472b1537..41e2536e037da002490cc15eaf331dbdbaa5a65d 100644 (file)
  * General Public License for more details:
  */
 
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
-#include <inttypes.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
+#include <acl/libacl.h>
+#include <sys/stat.h>
 #include <errno.h>
 #include <getopt.h>
-#include <sys/stat.h>
 #include <glib.h>
-#include <acl/libacl.h>
+#include <inttypes.h>
 #include <libudev.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 static int debug;
 
+enum{
+       ACTION_NONE = 0,
+       ACTION_REMOVE,
+       ACTION_ADD,
+       ACTION_CHANGE
+};
+
 static int set_facl(const char* filename, uid_t uid, int add)
 {
        int get;
@@ -38,6 +43,10 @@ static int set_facl(const char* filename, uid_t uid, int add)
        acl_permset_t permset;
        int ret;
 
+       /* don't touch ACLs for root */
+       if (uid == 0)
+               return 0;
+
        /* read current record */
        acl = acl_get_file(filename, ACL_TYPE_ACCESS);
        if (!acl)
@@ -152,44 +161,109 @@ static GSList *uids_with_local_active_session(const char *own_id)
 }
 
 /* ConsoleKit calls us with special variables */
-static int consolekit_called(const char *action, uid_t *uid, const char **own_session, int *add)
+static int consolekit_called(const char *ck_action, uid_t *uid, uid_t *uid2, const char **remove_session_id, int *action)
 {
-       int a;
-       uid_t u;
+       int a = ACTION_NONE;
+       uid_t u = 0;
+       uid_t u2 = 0;
        const char *s;
-       const char *session;
+       const char *s2;
+       const char *old_session = NULL;
 
-       if (action == NULL || strcmp(action, "session_active_changed") != 0)
+       if (ck_action == NULL || strcmp(ck_action, "seat_active_session_changed") != 0)
                return -1;
 
-       s = getenv("CK_SESSION_IS_LOCAL");
-       if (s == NULL)
-               return -1;
-       if (strcmp(s, "true") != 0)
-               return 0;
-
-       s = getenv("CK_SESSION_IS_ACTIVE");
-       if (s == NULL)
-               return -1;
-       if (strcmp(s, "true") == 0)
-               a = 1;
-       else
-               a = 0;
-
-       session = getenv("CK_SESSION_ID");
-       if (session == NULL)
+       /* We can have one of: remove, add, change, no-change */
+       s = getenv("CK_SEAT_OLD_SESSION_ID");
+       s2 = getenv("CK_SEAT_SESSION_ID");
+       if (s == NULL && s2 == NULL) {
                return -1;
+       } else if (s2 == NULL) {
+               a = ACTION_REMOVE;
+       } else if (s == NULL) {
+               a = ACTION_ADD;
+       } else {
+               a = ACTION_CHANGE;
+       }
 
-       s = getenv("CK_SESSION_USER_UID");
-       if (s == NULL)
-               return -1;
-       u = strtoul(s, NULL, 10);
-       if (u == 0)
-               return 0;
+       switch (a) {
+       case ACTION_ADD:
+               s = getenv("CK_SEAT_SESSION_USER_UID");
+               if (s == NULL)
+                       return -1;
+               u = strtoul(s, NULL, 10);
+
+               s = getenv("CK_SEAT_SESSION_IS_LOCAL");
+               if (s == NULL)
+                       return -1;
+               if (strcmp(s, "true") != 0)
+                       return 0;
+
+               break;
+       case ACTION_REMOVE:
+               s = getenv("CK_SEAT_OLD_SESSION_USER_UID");
+               if (s == NULL)
+                       return -1;
+               u = strtoul(s, NULL, 10);
+
+               s = getenv("CK_SEAT_OLD_SESSION_IS_LOCAL");
+               if (s == NULL)
+                       return -1;
+               if (strcmp(s, "true") != 0)
+                       return 0;
+
+               old_session = getenv("CK_SEAT_OLD_SESSION_ID");
+               if (old_session == NULL)
+                       return -1;
+
+               break;
+       case ACTION_CHANGE:
+               s = getenv("CK_SEAT_OLD_SESSION_USER_UID");
+               if (s == NULL)
+                       return -1;
+               u = strtoul(s, NULL, 10);
+               s = getenv("CK_SEAT_SESSION_USER_UID");
+               if (s == NULL)
+                       return -1;
+               u2 = strtoul(s, NULL, 10);
+
+               s = getenv("CK_SEAT_OLD_SESSION_IS_LOCAL");
+               s2 = getenv("CK_SEAT_SESSION_IS_LOCAL");
+               if (s == NULL || s2 == NULL)
+                       return -1;
+               /* don't process non-local session changes */
+               if (strcmp(s, "true") != 0 && strcmp(s2, "true") != 0)
+                       return 0;
+
+               if (strcmp(s, "true") == 0 && strcmp(s, "true") == 0) {
+                       /* process the change */
+                       if (u == u2) {
+                               /* special case: we noop if we are
+                                * changing between local sessions for
+                                * the same uid */
+                               a = ACTION_NONE;
+                       }
+                       old_session = getenv("CK_SEAT_OLD_SESSION_ID");
+                       if (old_session == NULL)
+                               return -1;
+               } else if (strcmp(s, "true") == 0) {
+                       /* only process the removal */
+                       a = ACTION_REMOVE;
+                       old_session = getenv("CK_SEAT_OLD_SESSION_ID");
+                       if (old_session == NULL)
+                               return -1;
+               } else if (strcmp(s2, "true") == 0) {
+                       /* only process the addition */
+                       a = ACTION_ADD;
+                       u = u2;
+               }
+               break;
+       }
 
-       *own_session = session;
+       *remove_session_id = old_session;
        *uid = u;
-       *add = a;
+       *uid2 = u2;
+       *action = a;
        return 0;
 }
 
@@ -203,7 +277,7 @@ static void apply_acl_to_devices(uid_t uid, int add)
        /* iterate over all devices tagged with ACL_SET */
        udev = udev_new();
        enumerate = udev_enumerate_new(udev);
-       udev_enumerate_add_match_property(enumerate, "ACL_MANAGE", "*");
+       udev_enumerate_add_match_tag(enumerate, "udev-acl");
        udev_enumerate_scan_devices(enumerate);
        udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) {
                struct udev_device *device;
@@ -214,8 +288,10 @@ static void apply_acl_to_devices(uid_t uid, int add)
                if (device == NULL)
                        continue;
                node = udev_device_get_devnode(device);
-               if (node == NULL)
+               if (node == NULL) {
+                       udev_device_unref(device);
                        continue;
+               }
                set_facl(node, uid, add);
                udev_device_unref(device);
        }
@@ -223,6 +299,21 @@ static void apply_acl_to_devices(uid_t uid, int add)
        udev_unref(udev);
 }
 
+static void
+remove_uid (uid_t uid, const char *remove_session_id)
+{
+       /*
+        * Remove ACL for given uid from all matching devices
+        * when there is currently no local active session.
+        */
+       GSList *list;
+
+       list = uids_with_local_active_session(remove_session_id);
+       if (!uid_in_list(list, uid))
+               apply_acl_to_devices(uid, 0);
+       g_slist_free(list);
+}
+
 int main (int argc, char* argv[])
 {
        static const struct option options[] = {
@@ -233,10 +324,12 @@ int main (int argc, char* argv[])
                { "help", no_argument, NULL, 'h' },
                {}
        };
-       int add = -1;
+       int action = -1;
        const char *device = NULL;
+       bool uid_given = false;
        uid_t uid = 0;
-       const char* own_session = NULL;
+       uid_t uid2 = 0;
+       const char* remove_session_id = NULL;
        int rc = 0;
 
        /* valgrind is more important to us than a slice allocator */
@@ -251,17 +344,16 @@ int main (int argc, char* argv[])
 
                switch (option) {
                case 'a':
-                       if (strcmp(optarg, "add") == 0 || strcmp(optarg, "change") == 0)
-                               add = 1;
-                       else if (strcmp(optarg, "remove") == 0)
-                               add = 0;
+                       if (strcmp(optarg, "remove") == 0)
+                               action = ACTION_REMOVE;
                        else
-                               goto out;
+                               action = ACTION_ADD;
                        break;
                case 'D':
                        device = optarg;
                        break;
                case 'u':
+                       uid_given = true;
                        uid = strtoul(optarg, NULL, 10);
                        break;
                case 'd':
@@ -269,41 +361,45 @@ int main (int argc, char* argv[])
                        break;
                case 'h':
                        printf("Usage: udev-acl --action=ACTION [--device=DEVICEFILE] [--user=UID]\n\n");
-               default:
                        goto out;
                }
        }
 
-       if (add < 0 && device == NULL && uid == 0)
-               consolekit_called(argv[optind], &uid, &own_session, &add);
+       if (action < 0 && device == NULL && !uid_given)
+               if (!consolekit_called(argv[optind], &uid, &uid2, &remove_session_id, &action))
+                       uid_given = true;
 
-       if (add < 0) {
+       if (action < 0) {
                fprintf(stderr, "missing action\n\n");
                rc = 2;
                goto out;
        }
 
-       if (device != NULL && uid != 0) {
+       if (device != NULL && uid_given) {
                fprintf(stderr, "only one option, --device=DEVICEFILE or --user=UID expected\n\n");
                rc = 3;
                goto out;
        }
 
-       if (uid != 0) {
-               if (add) {
+       if (uid_given) {
+               switch (action) {
+               case ACTION_ADD:
                        /* Add ACL for given uid to all matching devices. */
                        apply_acl_to_devices(uid, 1);
-               } else {
-                       /*
-                        * Remove ACL for given uid from all matching devices
-                        * when there is currently no local active session.
-                        */
-                       GSList *list;
-
-                       list = uids_with_local_active_session(own_session);
-                       if (!uid_in_list(list, uid))
-                               apply_acl_to_devices(uid, 0);
-                       g_slist_free(list);
+                       break;
+               case ACTION_REMOVE:
+                       remove_uid(uid, remove_session_id);
+                       break;
+               case ACTION_CHANGE:
+                       remove_uid(uid, remove_session_id);
+                       apply_acl_to_devices(uid2, 1);
+                       break;
+               case ACTION_NONE:
+                       goto out;
+                       break;
+               default:
+                       g_assert_not_reached();
+                       break;
                }
        } else if (device != NULL) {
                /*
@@ -321,8 +417,8 @@ int main (int argc, char* argv[])
                        uid_t u;
 
                        u = GPOINTER_TO_UINT(l->data);
-                       if (add || !uid_in_list(list, u))
-                               set_facl(device, u, add);
+                       if (action == ACTION_ADD || !uid_in_list(list, u))
+                               set_facl(device, u, action == ACTION_ADD);
                }
                g_slist_free(list);
        } else {