chiark / gitweb /
udev-acl: use a tag instead of a property to mark devices
[elogind.git] / extras / udev-acl / udev-acl.c
1 /*
2  * Copyright (C) 2009 Kay Sievers <kay.sievers@vrfy.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details:
13  */
14
15 #include <stdio.h>
16 #include <errno.h>
17 #include <string.h>
18 #include <inttypes.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <getopt.h>
25 #include <sys/stat.h>
26 #include <glib.h>
27 #include <acl/libacl.h>
28 #include <libudev.h>
29
30 static int debug;
31
32 enum{
33         ACTION_NONE = 0,
34         ACTION_REMOVE,
35         ACTION_ADD,
36         ACTION_CHANGE
37 };
38
39 static int set_facl(const char* filename, uid_t uid, int add)
40 {
41         int get;
42         acl_t acl;
43         acl_entry_t entry = NULL;
44         acl_entry_t e;
45         acl_permset_t permset;
46         int ret;
47
48         /* read current record */
49         acl = acl_get_file(filename, ACL_TYPE_ACCESS);
50         if (!acl)
51                 return -1;
52
53         /* locate ACL_USER entry for uid */
54         get = acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
55         while (get == 1) {
56                 acl_tag_t t;
57
58                 acl_get_tag_type(e, &t);
59                 if (t == ACL_USER) {
60                         uid_t *u;
61
62                         u = (uid_t*)acl_get_qualifier(e);
63                         if (u == NULL) {
64                                 ret = -1;
65                                 goto out;
66                         }
67                         if (*u == uid) {
68                                 entry = e;
69                                 acl_free(u);
70                                 break;
71                         }
72                         acl_free(u);
73                 }
74
75                 get = acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
76         }
77
78         /* remove ACL_USER entry for uid */
79         if (!add) {
80                 if (entry == NULL) {
81                         ret = 0;
82                         goto out;
83                 }
84                 acl_delete_entry(acl, entry);
85                 goto update;
86         }
87
88         /* create ACL_USER entry for uid */
89         if (entry == NULL) {
90                 ret = acl_create_entry(&acl, &entry);
91                 if (ret != 0)
92                         goto out;
93                 acl_set_tag_type(entry, ACL_USER);
94                 acl_set_qualifier(entry, &uid);
95         }
96
97         /* add permissions for uid */
98         acl_get_permset(entry, &permset);
99         acl_add_perm(permset, ACL_READ|ACL_WRITE);
100 update:
101         /* update record */
102         if (debug)
103                 printf("%c%u %s\n", add ? '+' : '-', uid, filename);
104         acl_calc_mask(&acl);
105         ret = acl_set_file(filename, ACL_TYPE_ACCESS, acl);
106         if (ret != 0)
107                 goto out;
108 out:
109         acl_free(acl);
110         return ret;
111 }
112
113 /* check if a given uid is listed */
114 static int uid_in_list(GSList *list, uid_t uid)
115 {
116         GSList *l;
117
118         for (l = list; l != NULL; l = g_slist_next(l))
119                 if (uid == GPOINTER_TO_UINT(l->data))
120                         return 1;
121         return 0;
122 }
123
124 /* return list of current uids of local active sessions */
125 static GSList *uids_with_local_active_session(const char *own_id)
126 {
127         GSList *list = NULL;
128         GKeyFile *keyfile;
129
130         keyfile = g_key_file_new();
131         if (g_key_file_load_from_file(keyfile, "/var/run/ConsoleKit/database", 0, NULL)) {
132                 gchar **groups;
133
134                 groups = g_key_file_get_groups(keyfile, NULL);
135                 if (groups != NULL) {
136                         int i;
137
138                         for (i = 0; groups[i] != NULL; i++) {
139                                 uid_t u;
140
141                                 if (!g_str_has_prefix(groups[i], "Session "))
142                                         continue;
143                                 if (own_id != NULL &&g_str_has_suffix(groups[i], own_id))
144                                         continue;
145                                 if (!g_key_file_get_boolean(keyfile, groups[i], "is_local", NULL))
146                                         continue;
147                                 if (!g_key_file_get_boolean(keyfile, groups[i], "is_active", NULL))
148                                         continue;
149                                 u = g_key_file_get_integer(keyfile, groups[i], "uid", NULL);
150                                 if (u > 0 && !uid_in_list(list, u))
151                                         list = g_slist_prepend(list, GUINT_TO_POINTER(u));
152                         }
153                         g_strfreev(groups);
154                 }
155         }
156         g_key_file_free(keyfile);
157
158         return list;
159 }
160
161 /* ConsoleKit calls us with special variables */
162 static int consolekit_called(const char *ck_action, uid_t *uid, uid_t *uid2, const char **remove_session_id, int *action)
163 {
164         int a = ACTION_NONE;
165         uid_t u = 0;
166         uid_t u2 = 0;
167         const char *s;
168         const char *s2;
169         const char *old_session = NULL;
170
171         if (ck_action == NULL || strcmp(ck_action, "seat_active_session_changed") != 0)
172                 return -1;
173
174         /* We can have one of: remove, add, change, no-change */
175         s = getenv("CK_SEAT_OLD_SESSION_ID");
176         s2 = getenv("CK_SEAT_SESSION_ID");
177         if (s == NULL && s2 == NULL) {
178                 return -1;
179         } else if (s2 == NULL) {
180                 a = ACTION_REMOVE;
181         } else if (s == NULL) {
182                 a = ACTION_ADD;
183         } else {
184                 a = ACTION_CHANGE;
185         }
186
187         switch (a) {
188         case ACTION_ADD:
189                 s = getenv("CK_SEAT_SESSION_USER_UID");
190                 if (s == NULL)
191                         return -1;
192                 u = strtoul(s, NULL, 10);
193                 if (u == 0)
194                         return 0;
195
196                 s = getenv("CK_SEAT_SESSION_IS_LOCAL");
197                 if (s == NULL)
198                         return -1;
199                 if (strcmp(s, "true") != 0)
200                         return 0;
201
202                 break;
203         case ACTION_REMOVE:
204                 s = getenv("CK_SEAT_OLD_SESSION_USER_UID");
205                 if (s == NULL)
206                         return -1;
207                 u = strtoul(s, NULL, 10);
208                 if (u == 0)
209                         return 0;
210
211                 s = getenv("CK_SEAT_OLD_SESSION_IS_LOCAL");
212                 if (s == NULL)
213                         return -1;
214                 if (strcmp(s, "true") != 0)
215                         return 0;
216
217                 old_session = getenv("CK_SEAT_OLD_SESSION_ID");
218                 if (old_session == NULL)
219                         return -1;
220
221                 break;
222         case ACTION_CHANGE:
223                 s = getenv("CK_SEAT_OLD_SESSION_USER_UID");
224                 if (s == NULL)
225                         return -1;
226                 u = strtoul(s, NULL, 10);
227                 if (u == 0)
228                         return 0;
229                 s = getenv("CK_SEAT_SESSION_USER_UID");
230                 if (s == NULL)
231                         return -1;
232                 u2 = strtoul(s, NULL, 10);
233                 if (u2 == 0)
234                         return 0;
235
236                 s = getenv("CK_SEAT_OLD_SESSION_IS_LOCAL");
237                 s2 = getenv("CK_SEAT_SESSION_IS_LOCAL");
238                 if (s == NULL || s2 == NULL)
239                         return -1;
240                 /* don't process non-local session changes */
241                 if (strcmp(s, "true") != 0 && strcmp(s2, "true") != 0)
242                         return 0;
243
244                 if (strcmp(s, "true") == 0 && strcmp(s, "true") == 0) {
245                         /* process the change */
246                         if (u == u2) {
247                                 /* special case: we noop if we are
248                                  * changing between local sessions for
249                                  * the same uid */
250                                 a = ACTION_NONE;
251                         }
252                         old_session = getenv("CK_SEAT_OLD_SESSION_ID");
253                         if (old_session == NULL)
254                                 return -1;
255                 } else if (strcmp(s, "true") == 0) {
256                         /* only process the removal */
257                         a = ACTION_REMOVE;
258                         old_session = getenv("CK_SEAT_OLD_SESSION_ID");
259                         if (old_session == NULL)
260                                 return -1;
261                 } else if (strcmp(s2, "true") == 0) {
262                         /* only process the addition */
263                         a = ACTION_ADD;
264                         u = u2;
265                 }
266
267                 break;
268         case ACTION_NONE:
269                 break;
270         default:
271                 g_assert_not_reached ();
272                 break;
273         }
274
275         *remove_session_id = old_session;
276         *uid = u;
277         *uid2 = u2;
278         *action = a;
279         return 0;
280 }
281
282 /* add or remove a ACL for a given uid from all matching devices */
283 static void apply_acl_to_devices(uid_t uid, int add)
284 {
285         struct udev *udev;
286         struct udev_enumerate *enumerate;
287         struct udev_list_entry *list_entry;
288
289         /* iterate over all devices tagged with ACL_SET */
290         udev = udev_new();
291         enumerate = udev_enumerate_new(udev);
292         udev_enumerate_add_match_tag(enumerate, "udev-acl");
293         udev_enumerate_scan_devices(enumerate);
294         udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) {
295                 struct udev_device *device;
296                 const char *node;
297
298                 device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate),
299                                                       udev_list_entry_get_name(list_entry));
300                 if (device == NULL)
301                         continue;
302                 node = udev_device_get_devnode(device);
303                 if (node == NULL)
304                         continue;
305                 set_facl(node, uid, add);
306                 udev_device_unref(device);
307         }
308         udev_enumerate_unref(enumerate);
309         udev_unref(udev);
310 }
311
312 static void
313 remove_uid (uid_t uid, const char *remove_session_id)
314 {
315         /*
316          * Remove ACL for given uid from all matching devices
317          * when there is currently no local active session.
318          */
319         GSList *list;
320
321         list = uids_with_local_active_session(remove_session_id);
322         if (!uid_in_list(list, uid))
323                 apply_acl_to_devices(uid, 0);
324         g_slist_free(list);
325 }
326
327 int main (int argc, char* argv[])
328 {
329         static const struct option options[] = {
330                 { "action", required_argument, NULL, 'a' },
331                 { "device", required_argument, NULL, 'D' },
332                 { "user", required_argument, NULL, 'u' },
333                 { "debug", no_argument, NULL, 'd' },
334                 { "help", no_argument, NULL, 'h' },
335                 {}
336         };
337         int action = -1;
338         const char *device = NULL;
339         uid_t uid = 0;
340         uid_t uid2 = 0;
341         const char* remove_session_id = NULL;
342         int rc = 0;
343
344         /* valgrind is more important to us than a slice allocator */
345         g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, 1);
346
347         while (1) {
348                 int option;
349
350                 option = getopt_long(argc, argv, "+a:D:u:dh", options, NULL);
351                 if (option == -1)
352                         break;
353
354                 switch (option) {
355                 case 'a':
356                         if (strcmp(optarg, "remove") == 0)
357                                 action = ACTION_REMOVE;
358                         else
359                                 action = ACTION_ADD;
360                         break;
361                 case 'D':
362                         device = optarg;
363                         break;
364                 case 'u':
365                         uid = strtoul(optarg, NULL, 10);
366                         break;
367                 case 'd':
368                         debug = 1;
369                         break;
370                 case 'h':
371                         printf("Usage: udev-acl --action=ACTION [--device=DEVICEFILE] [--user=UID]\n\n");
372                 default:
373                         goto out;
374                 }
375         }
376
377         if (action < 0 && device == NULL && uid == 0)
378                 consolekit_called(argv[optind], &uid, &uid2, &remove_session_id, &action);
379
380         if (action < 0) {
381                 fprintf(stderr, "missing action\n\n");
382                 rc = 2;
383                 goto out;
384         }
385
386         if (device != NULL && uid != 0) {
387                 fprintf(stderr, "only one option, --device=DEVICEFILE or --user=UID expected\n\n");
388                 rc = 3;
389                 goto out;
390         }
391
392         if (uid != 0) {
393                 switch (action) {
394                 case ACTION_ADD:
395                         /* Add ACL for given uid to all matching devices. */
396                         apply_acl_to_devices(uid, 1);
397                         break;
398                 case ACTION_REMOVE:
399                         remove_uid(uid, remove_session_id);
400                         break;
401                 case ACTION_CHANGE:
402                         remove_uid(uid, remove_session_id);
403                         apply_acl_to_devices(uid2, 1);
404                         break;
405                 case ACTION_NONE:
406                         goto out;
407                         break;
408                 default:
409                         g_assert_not_reached();
410                         break;
411                 }
412         } else if (device != NULL) {
413                 /*
414                  * Add ACLs for all current session uids to a given device.
415                  *
416                  * Or remove ACLs for uids which do not have any current local
417                  * active session. Remove is not really interesting, because in
418                  * most cases the device node is removed anyway.
419                  */
420                 GSList *list;
421                 GSList *l;
422
423                 list = uids_with_local_active_session(NULL);
424                 for (l = list; l != NULL; l = g_slist_next(l)) {
425                         uid_t u;
426
427                         u = GPOINTER_TO_UINT(l->data);
428                         if (action == ACTION_ADD || !uid_in_list(list, u))
429                                 set_facl(device, u, action == ACTION_ADD);
430                 }
431                 g_slist_free(list);
432         } else {
433                 fprintf(stderr, "--device=DEVICEFILE or --user=UID expected\n\n");
434                 rc = 3;
435         }
436 out:
437         return rc;
438 }