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