chiark / gitweb /
Also merge into the top-level Makefile.am the simpler extras.
[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 static int set_facl(const char* filename, uid_t uid, int add)
33 {
34         int get;
35         acl_t acl;
36         acl_entry_t entry = NULL;
37         acl_entry_t e;
38         acl_permset_t permset;
39         int ret;
40
41         /* read current record */
42         acl = acl_get_file(filename, ACL_TYPE_ACCESS);
43         if (!acl)
44                 return -1;
45
46         /* locate ACL_USER entry for uid */
47         get = acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
48         while (get == 1) {
49                 acl_tag_t t;
50
51                 acl_get_tag_type(e, &t);
52                 if (t == ACL_USER) {
53                         uid_t *u;
54
55                         u = (uid_t*)acl_get_qualifier(e);
56                         if (u == NULL) {
57                                 ret = -1;
58                                 goto out;
59                         }
60                         if (*u == uid) {
61                                 entry = e;
62                                 acl_free(u);
63                                 break;
64                         }
65                         acl_free(u);
66                 }
67
68                 get = acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
69         }
70
71         /* remove ACL_USER entry for uid */
72         if (!add) {
73                 if (entry == NULL) {
74                         ret = 0;
75                         goto out;
76                 }
77                 acl_delete_entry(acl, entry);
78                 goto update;
79         }
80
81         /* create ACL_USER entry for uid */
82         if (entry == NULL) {
83                 ret = acl_create_entry(&acl, &entry);
84                 if (ret != 0)
85                         goto out;
86                 acl_set_tag_type(entry, ACL_USER);
87                 acl_set_qualifier(entry, &uid);
88         }
89
90         /* add permissions for uid */
91         acl_get_permset(entry, &permset);
92         acl_add_perm(permset, ACL_READ|ACL_WRITE);
93 update:
94         /* update record */
95         if (debug)
96                 printf("%c%u %s\n", add ? '+' : '-', uid, filename);
97         acl_calc_mask(&acl);
98         ret = acl_set_file(filename, ACL_TYPE_ACCESS, acl);
99         if (ret != 0)
100                 goto out;
101 out:
102         acl_free(acl);
103         return ret;
104 }
105
106 /* check if a given uid is listed */
107 static int uid_in_list(GSList *list, uid_t uid)
108 {
109         GSList *l;
110
111         for (l = list; l != NULL; l = g_slist_next(l))
112                 if (uid == GPOINTER_TO_UINT(l->data))
113                         return 1;
114         return 0;
115 }
116
117 /* return list of current uids of local active sessions */
118 static GSList *uids_with_local_active_session(const char *own_id)
119 {
120         GSList *list = NULL;
121         GKeyFile *keyfile;
122
123         keyfile = g_key_file_new();
124         if (g_key_file_load_from_file(keyfile, "/var/run/ConsoleKit/database", 0, NULL)) {
125                 gchar **groups;
126
127                 groups = g_key_file_get_groups(keyfile, NULL);
128                 if (groups != NULL) {
129                         int i;
130
131                         for (i = 0; groups[i] != NULL; i++) {
132                                 uid_t u;
133
134                                 if (!g_str_has_prefix(groups[i], "Session "))
135                                         continue;
136                                 if (own_id != NULL &&g_str_has_suffix(groups[i], own_id))
137                                         continue;
138                                 if (!g_key_file_get_boolean(keyfile, groups[i], "is_local", NULL))
139                                         continue;
140                                 if (!g_key_file_get_boolean(keyfile, groups[i], "is_active", NULL))
141                                         continue;
142                                 u = g_key_file_get_integer(keyfile, groups[i], "uid", NULL);
143                                 if (u > 0 && !uid_in_list(list, u))
144                                         list = g_slist_prepend(list, GUINT_TO_POINTER(u));
145                         }
146                         g_strfreev(groups);
147                 }
148         }
149         g_key_file_free(keyfile);
150
151         return list;
152 }
153
154 /* ConsoleKit calls us with special variables */
155 static int consolekit_called(const char *action, uid_t *uid, const char **own_session, int *add)
156 {
157         int a;
158         uid_t u;
159         const char *s;
160         const char *session;
161
162         if (action == NULL || strcmp(action, "session_active_changed") != 0)
163                 return -1;
164
165         s = getenv("CK_SESSION_IS_LOCAL");
166         if (s == NULL)
167                 return -1;
168         if (strcmp(s, "true") != 0)
169                 return 0;
170
171         s = getenv("CK_SESSION_IS_ACTIVE");
172         if (s == NULL)
173                 return -1;
174         if (strcmp(s, "true") == 0)
175                 a = 1;
176         else
177                 a = 0;
178
179         session = getenv("CK_SESSION_ID");
180         if (session == NULL)
181                 return -1;
182
183         s = getenv("CK_SESSION_USER_UID");
184         if (s == NULL)
185                 return -1;
186         u = strtoul(s, NULL, 10);
187         if (u == 0)
188                 return 0;
189
190         *own_session = session;
191         *uid = u;
192         *add = a;
193         return 0;
194 }
195
196 /* add or remove a ACL for a given uid from all matching devices */
197 static void apply_acl_to_devices(uid_t uid, int add)
198 {
199         struct udev *udev;
200         struct udev_enumerate *enumerate;
201         struct udev_list_entry *list_entry;
202
203         /* iterate over all devices tagged with ACL_SET */
204         udev = udev_new();
205         enumerate = udev_enumerate_new(udev);
206         udev_enumerate_add_match_property(enumerate, "ACL_MANAGE", "*");
207         udev_enumerate_scan_devices(enumerate);
208         udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) {
209                 struct udev_device *device;
210                 const char *node;
211
212                 device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate),
213                                                       udev_list_entry_get_name(list_entry));
214                 if (device == NULL)
215                         continue;
216                 node = udev_device_get_devnode(device);
217                 if (node == NULL)
218                         continue;
219                 set_facl(node, uid, add);
220                 udev_device_unref(device);
221         }
222         udev_enumerate_unref(enumerate);
223         udev_unref(udev);
224 }
225
226 int main (int argc, char* argv[])
227 {
228         static const struct option options[] = {
229                 { "action", required_argument, NULL, 'a' },
230                 { "device", required_argument, NULL, 'D' },
231                 { "user", required_argument, NULL, 'u' },
232                 { "debug", no_argument, NULL, 'd' },
233                 { "help", no_argument, NULL, 'h' },
234                 {}
235         };
236         int add = -1;
237         const char *device = NULL;
238         uid_t uid = 0;
239         const char* own_session = NULL;
240         int rc = 0;
241
242         /* valgrind is more important to us than a slice allocator */
243         g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, 1);
244
245         while (1) {
246                 int option;
247
248                 option = getopt_long(argc, argv, "+a:D:u:dh", options, NULL);
249                 if (option == -1)
250                         break;
251
252                 switch (option) {
253                 case 'a':
254                         if (strcmp(optarg, "add") == 0 || strcmp(optarg, "change") == 0)
255                                 add = 1;
256                         else if (strcmp(optarg, "remove") == 0)
257                                 add = 0;
258                         else
259                                 goto out;
260                         break;
261                 case 'D':
262                         device = optarg;
263                         break;
264                 case 'u':
265                         uid = strtoul(optarg, NULL, 10);
266                         break;
267                 case 'd':
268                         debug = 1;
269                         break;
270                 case 'h':
271                         printf("Usage: udev-acl --action=ACTION [--device=DEVICEFILE] [--user=UID]\n\n");
272                 default:
273                         goto out;
274                 }
275         }
276
277         if (add < 0 && device == NULL && uid == 0)
278                 consolekit_called(argv[optind], &uid, &own_session, &add);
279
280         if (add < 0) {
281                 fprintf(stderr, "missing action\n\n");
282                 rc = 2;
283                 goto out;
284         }
285
286         if (device != NULL && uid != 0) {
287                 fprintf(stderr, "only one option, --device=DEVICEFILE or --user=UID expected\n\n");
288                 rc = 3;
289                 goto out;
290         }
291
292         if (uid != 0) {
293                 if (add) {
294                         /* Add ACL for given uid to all matching devices. */
295                         apply_acl_to_devices(uid, 1);
296                 } else {
297                         /*
298                          * Remove ACL for given uid from all matching devices
299                          * when there is currently no local active session.
300                          */
301                         GSList *list;
302
303                         list = uids_with_local_active_session(own_session);
304                         if (!uid_in_list(list, uid))
305                                 apply_acl_to_devices(uid, 0);
306                         g_slist_free(list);
307                 }
308         } else if (device != NULL) {
309                 /*
310                  * Add ACLs for all current session uids to a given device.
311                  *
312                  * Or remove ACLs for uids which do not have any current local
313                  * active session. Remove is not really interesting, because in
314                  * most cases the device node is removed anyway.
315                  */
316                 GSList *list;
317                 GSList *l;
318
319                 list = uids_with_local_active_session(NULL);
320                 for (l = list; l != NULL; l = g_slist_next(l)) {
321                         uid_t u;
322
323                         u = GPOINTER_TO_UINT(l->data);
324                         if (add || !uid_in_list(list, u))
325                                 set_facl(device, u, add);
326                 }
327                 g_slist_free(list);
328         } else {
329                 fprintf(stderr, "--device=DEVICEFILE or --user=UID expected\n\n");
330                 rc = 3;
331         }
332 out:
333         return rc;
334 }