chiark / gitweb /
autogen.sh: enable git pre-commit
[elogind.git] / src / 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                         udev_device_unref(device);
293                         continue;
294                 }
295                 set_facl(node, uid, add);
296                 udev_device_unref(device);
297         }
298         udev_enumerate_unref(enumerate);
299         udev_unref(udev);
300 }
301
302 static void
303 remove_uid (uid_t uid, const char *remove_session_id)
304 {
305         /*
306          * Remove ACL for given uid from all matching devices
307          * when there is currently no local active session.
308          */
309         GSList *list;
310
311         list = uids_with_local_active_session(remove_session_id);
312         if (!uid_in_list(list, uid))
313                 apply_acl_to_devices(uid, 0);
314         g_slist_free(list);
315 }
316
317 int main (int argc, char* argv[])
318 {
319         static const struct option options[] = {
320                 { "action", required_argument, NULL, 'a' },
321                 { "device", required_argument, NULL, 'D' },
322                 { "user", required_argument, NULL, 'u' },
323                 { "debug", no_argument, NULL, 'd' },
324                 { "help", no_argument, NULL, 'h' },
325                 {}
326         };
327         int action = -1;
328         const char *device = NULL;
329         bool uid_given = false;
330         uid_t uid = 0;
331         uid_t uid2 = 0;
332         const char* remove_session_id = NULL;
333         int rc = 0;
334
335         /* valgrind is more important to us than a slice allocator */
336         g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, 1);
337
338         while (1) {
339                 int option;
340
341                 option = getopt_long(argc, argv, "+a:D:u:dh", options, NULL);
342                 if (option == -1)
343                         break;
344
345                 switch (option) {
346                 case 'a':
347                         if (strcmp(optarg, "remove") == 0)
348                                 action = ACTION_REMOVE;
349                         else
350                                 action = ACTION_ADD;
351                         break;
352                 case 'D':
353                         device = optarg;
354                         break;
355                 case 'u':
356                         uid_given = true;
357                         uid = strtoul(optarg, NULL, 10);
358                         break;
359                 case 'd':
360                         debug = 1;
361                         break;
362                 case 'h':
363                         printf("Usage: udev-acl --action=ACTION [--device=DEVICEFILE] [--user=UID]\n\n");
364                         goto out;
365                 }
366         }
367
368         if (action < 0 && device == NULL && !uid_given)
369                 if (!consolekit_called(argv[optind], &uid, &uid2, &remove_session_id, &action))
370                         uid_given = true;
371
372         if (action < 0) {
373                 fprintf(stderr, "missing action\n\n");
374                 rc = 2;
375                 goto out;
376         }
377
378         if (device != NULL && uid_given) {
379                 fprintf(stderr, "only one option, --device=DEVICEFILE or --user=UID expected\n\n");
380                 rc = 3;
381                 goto out;
382         }
383
384         if (uid_given) {
385                 switch (action) {
386                 case ACTION_ADD:
387                         /* Add ACL for given uid to all matching devices. */
388                         apply_acl_to_devices(uid, 1);
389                         break;
390                 case ACTION_REMOVE:
391                         remove_uid(uid, remove_session_id);
392                         break;
393                 case ACTION_CHANGE:
394                         remove_uid(uid, remove_session_id);
395                         apply_acl_to_devices(uid2, 1);
396                         break;
397                 case ACTION_NONE:
398                         goto out;
399                         break;
400                 default:
401                         g_assert_not_reached();
402                         break;
403                 }
404         } else if (device != NULL) {
405                 /*
406                  * Add ACLs for all current session uids to a given device.
407                  *
408                  * Or remove ACLs for uids which do not have any current local
409                  * active session. Remove is not really interesting, because in
410                  * most cases the device node is removed anyway.
411                  */
412                 GSList *list;
413                 GSList *l;
414
415                 list = uids_with_local_active_session(NULL);
416                 for (l = list; l != NULL; l = g_slist_next(l)) {
417                         uid_t u;
418
419                         u = GPOINTER_TO_UINT(l->data);
420                         if (action == ACTION_ADD || !uid_in_list(list, u))
421                                 set_facl(device, u, action == ACTION_ADD);
422                 }
423                 g_slist_free(list);
424         } else {
425                 fprintf(stderr, "--device=DEVICEFILE or --user=UID expected\n\n");
426                 rc = 3;
427         }
428 out:
429         return rc;
430 }