chiark / gitweb /
Merge pull request #3 from elogind/dev_v226-r1
[elogind.git] / src / login / logind-acl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24
25 #include "util.h"
26 #include "formats-util.h"
27 #include "acl-util.h"
28 #include "set.h"
29 #include "logind-acl.h"
30 #include "udev-util.h"
31
32 static int flush_acl(acl_t acl) {
33         acl_entry_t i;
34         int found;
35         bool changed = false;
36
37         assert(acl);
38
39         for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
40              found > 0;
41              found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
42
43                 acl_tag_t tag;
44
45                 if (acl_get_tag_type(i, &tag) < 0)
46                         return -errno;
47
48                 if (tag != ACL_USER)
49                         continue;
50
51                 if (acl_delete_entry(acl, i) < 0)
52                         return -errno;
53
54                 changed = true;
55         }
56
57         if (found < 0)
58                 return -errno;
59
60         return changed;
61 }
62
63 int devnode_acl(const char *path,
64                 bool flush,
65                 bool del, uid_t old_uid,
66                 bool add, uid_t new_uid) {
67
68         acl_t acl;
69         int r = 0;
70         bool changed = false;
71
72         assert(path);
73
74         acl = acl_get_file(path, ACL_TYPE_ACCESS);
75         if (!acl)
76                 return -errno;
77
78         if (flush) {
79
80                 r = flush_acl(acl);
81                 if (r < 0)
82                         goto finish;
83                 if (r > 0)
84                         changed = true;
85
86         } else if (del && old_uid > 0) {
87                 acl_entry_t entry;
88
89                 r = acl_find_uid(acl, old_uid, &entry);
90                 if (r < 0)
91                         goto finish;
92
93                 if (r > 0) {
94                         if (acl_delete_entry(acl, entry) < 0) {
95                                 r = -errno;
96                                 goto finish;
97                         }
98
99                         changed = true;
100                 }
101         }
102
103         if (add && new_uid > 0) {
104                 acl_entry_t entry;
105                 acl_permset_t permset;
106                 int rd, wt;
107
108                 r = acl_find_uid(acl, new_uid, &entry);
109                 if (r < 0)
110                         goto finish;
111
112                 if (r == 0) {
113                         if (acl_create_entry(&acl, &entry) < 0) {
114                                 r = -errno;
115                                 goto finish;
116                         }
117
118                         if (acl_set_tag_type(entry, ACL_USER) < 0 ||
119                             acl_set_qualifier(entry, &new_uid) < 0) {
120                                 r = -errno;
121                                 goto finish;
122                         }
123                 }
124
125                 if (acl_get_permset(entry, &permset) < 0) {
126                         r = -errno;
127                         goto finish;
128                 }
129
130                 rd = acl_get_perm(permset, ACL_READ);
131                 if (rd < 0) {
132                         r = -errno;
133                         goto finish;
134                 }
135
136                 wt = acl_get_perm(permset, ACL_WRITE);
137                 if (wt < 0) {
138                         r = -errno;
139                         goto finish;
140                 }
141
142                 if (!rd || !wt) {
143
144                         if (acl_add_perm(permset, ACL_READ|ACL_WRITE) < 0) {
145                                 r = -errno;
146                                 goto finish;
147                         }
148
149                         changed = true;
150                 }
151         }
152
153         if (!changed)
154                 goto finish;
155
156         if (acl_calc_mask(&acl) < 0) {
157                 r = -errno;
158                 goto finish;
159         }
160
161         if (acl_set_file(path, ACL_TYPE_ACCESS, acl) < 0) {
162                 r = -errno;
163                 goto finish;
164         }
165
166         r = 0;
167
168 finish:
169         acl_free(acl);
170
171         return r;
172 }
173
174 int devnode_acl_all(struct udev *udev,
175                     const char *seat,
176                     bool flush,
177                     bool del, uid_t old_uid,
178                     bool add, uid_t new_uid) {
179
180         _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
181         struct udev_list_entry *item = NULL, *first = NULL;
182         _cleanup_set_free_free_ Set *nodes = NULL;
183         _cleanup_closedir_ DIR *dir = NULL;
184         struct dirent *dent;
185         Iterator i;
186         char *n;
187         int r;
188
189         assert(udev);
190
191         nodes = set_new(&string_hash_ops);
192         if (!nodes)
193                 return -ENOMEM;
194
195         e = udev_enumerate_new(udev);
196         if (!e)
197                 return -ENOMEM;
198
199         if (isempty(seat))
200                 seat = "seat0";
201
202         /* We can only match by one tag in libudev. We choose
203          * "uaccess" for that. If we could match for two tags here we
204          * could add the seat name as second match tag, but this would
205          * be hardly optimizable in libudev, and hence checking the
206          * second tag manually in our loop is a good solution. */
207         r = udev_enumerate_add_match_tag(e, "uaccess");
208         if (r < 0)
209                 return r;
210
211         r = udev_enumerate_add_match_is_initialized(e);
212         if (r < 0)
213                 return r;
214
215         r = udev_enumerate_scan_devices(e);
216         if (r < 0)
217                 return r;
218
219         first = udev_enumerate_get_list_entry(e);
220         udev_list_entry_foreach(item, first) {
221                 _cleanup_udev_device_unref_ struct udev_device *d = NULL;
222                 const char *node, *sn;
223
224                 d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
225                 if (!d)
226                         return -ENOMEM;
227
228                 sn = udev_device_get_property_value(d, "ID_SEAT");
229                 if (isempty(sn))
230                         sn = "seat0";
231
232                 if (!streq(seat, sn))
233                         continue;
234
235                 node = udev_device_get_devnode(d);
236                 /* In case people mistag devices with nodes, we need to ignore this */
237                 if (!node)
238                         continue;
239
240                 n = strdup(node);
241                 if (!n)
242                         return -ENOMEM;
243
244                 log_debug("Found udev node %s for seat %s", n, seat);
245                 r = set_consume(nodes, n);
246                 if (r < 0)
247                         return r;
248         }
249
250         /* udev exports "dead" device nodes to allow module on-demand loading,
251          * these devices are not known to the kernel at this moment */
252         dir = opendir("/run/udev/static_node-tags/uaccess");
253         if (dir) {
254                 FOREACH_DIRENT(dent, dir, return -errno) {
255                         _cleanup_free_ char *unescaped_devname = NULL;
256
257                         if (cunescape(dent->d_name, UNESCAPE_RELAX, &unescaped_devname) < 0)
258                                 return -ENOMEM;
259
260                         n = strappend("/dev/", unescaped_devname);
261                         if (!n)
262                                 return -ENOMEM;
263
264                         log_debug("Found static node %s for seat %s", n, seat);
265                         r = set_consume(nodes, n);
266                         if (r == -EEXIST)
267                                 continue;
268                         if (r < 0)
269                                 return r;
270                 }
271         }
272
273         r = 0;
274         SET_FOREACH(n, nodes, i) {
275                 int k;
276
277                 log_debug("Changing ACLs at %s for seat %s (uid "UID_FMT"→"UID_FMT"%s%s)",
278                           n, seat, old_uid, new_uid,
279                           del ? " del" : "", add ? " add" : "");
280
281                 k = devnode_acl(n, flush, del, old_uid, add, new_uid);
282                 if (k == -ENOENT)
283                         log_debug("Device %s disappeared while setting ACLs", n);
284                 else if (k < 0 && r == 0)
285                         r = k;
286         }
287
288         return r;
289 }