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