chiark / gitweb /
14dfc31749d339525147abb5a431f72c5543e9e2
[elogind.git] / src / libelogind / sd-id128 / sd-id128.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2011 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23
24 #include "sd-id128.h"
25
26 #include "fd-util.h"
27 #include "hexdecoct.h"
28 #include "id128-util.h"
29 #include "io-util.h"
30 #include "macro.h"
31 #include "random-util.h"
32 #include "util.h"
33
34 _public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) {
35         unsigned n;
36
37         assert_return(s, NULL);
38
39         for (n = 0; n < 16; n++) {
40                 s[n*2] = hexchar(id.bytes[n] >> 4);
41                 s[n*2+1] = hexchar(id.bytes[n] & 0xF);
42         }
43
44         s[32] = 0;
45
46         return s;
47 }
48
49 _public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
50         unsigned n, i;
51         sd_id128_t t;
52         bool is_guid = false;
53
54         assert_return(s, -EINVAL);
55
56         for (n = 0, i = 0; n < 16;) {
57                 int a, b;
58
59                 if (s[i] == '-') {
60                         /* Is this a GUID? Then be nice, and skip over
61                          * the dashes */
62
63                         if (i == 8)
64                                 is_guid = true;
65                         else if (i == 13 || i == 18 || i == 23) {
66                                 if (!is_guid)
67                                         return -EINVAL;
68                         } else
69                                 return -EINVAL;
70
71                         i++;
72                         continue;
73                 }
74
75                 a = unhexchar(s[i++]);
76                 if (a < 0)
77                         return -EINVAL;
78
79                 b = unhexchar(s[i++]);
80                 if (b < 0)
81                         return -EINVAL;
82
83                 t.bytes[n++] = (a << 4) | b;
84         }
85
86         if (i != (is_guid ? 36 : 32))
87                 return -EINVAL;
88
89         if (s[i] != 0)
90                 return -EINVAL;
91
92         if (ret)
93                 *ret = t;
94         return 0;
95 }
96
97 _public_ int sd_id128_get_machine(sd_id128_t *ret) {
98         static thread_local sd_id128_t saved_machine_id = {};
99         int r;
100
101         assert_return(ret, -EINVAL);
102
103         if (sd_id128_is_null(saved_machine_id)) {
104                 r = id128_read("/etc/machine-id", ID128_PLAIN, &saved_machine_id);
105                 if (r < 0)
106                         return r;
107
108                 if (sd_id128_is_null(saved_machine_id))
109                         return -EINVAL;
110         }
111
112         *ret = saved_machine_id;
113         return 0;
114 }
115
116 _public_ int sd_id128_get_boot(sd_id128_t *ret) {
117         static thread_local sd_id128_t saved_boot_id = {};
118         int r;
119
120         assert_return(ret, -EINVAL);
121
122         if (sd_id128_is_null(saved_boot_id)) {
123                 r = id128_read("/proc/sys/kernel/random/boot_id", ID128_UUID, &saved_boot_id);
124                 if (r < 0)
125                         return r;
126         }
127
128         *ret = saved_boot_id;
129         return 0;
130 }
131
132 static int get_invocation_from_keyring(sd_id128_t *ret) {
133
134         _cleanup_free_ char *description = NULL;
135         char *d, *p, *g, *u, *e;
136         unsigned long perms;
137         key_serial_t key;
138         size_t sz = 256;
139         uid_t uid;
140         gid_t gid;
141         int r, c;
142
143 #define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \
144                                     KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH))
145
146         assert(ret);
147
148         key = request_key("user", "invocation_id", NULL, 0);
149         if (key == -1) {
150                 /* Keyring support not available? No invocation key stored? */
151                 if (IN_SET(errno, ENOSYS, ENOKEY))
152                         return 0;
153
154                 return -errno;
155         }
156
157         for (;;) {
158                 description = new(char, sz);
159                 if (!description)
160                         return -ENOMEM;
161
162                 c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0);
163                 if (c < 0)
164                         return -errno;
165
166                 if ((size_t) c <= sz)
167                         break;
168
169                 sz = c;
170                 free(description);
171         }
172
173         /* The kernel returns a final NUL in the string, verify that. */
174         assert(description[c-1] == 0);
175
176         /* Chop off the final description string */
177         d = strrchr(description, ';');
178         if (!d)
179                 return -EIO;
180         *d = 0;
181
182         /* Look for the permissions */
183         p = strrchr(description, ';');
184         if (!p)
185                 return -EIO;
186
187         errno = 0;
188         perms = strtoul(p + 1, &e, 16);
189         if (errno > 0)
190                 return -errno;
191         if (e == p + 1) /* Read at least one character */
192                 return -EIO;
193         if (e != d) /* Must reached the end */
194                 return -EIO;
195
196         if ((perms & ~MAX_PERMS) != 0)
197                 return -EPERM;
198
199         *p = 0;
200
201         /* Look for the group ID */
202         g = strrchr(description, ';');
203         if (!g)
204                 return -EIO;
205         r = parse_gid(g + 1, &gid);
206         if (r < 0)
207                 return r;
208         if (gid != 0)
209                 return -EPERM;
210         *g = 0;
211
212         /* Look for the user ID */
213         u = strrchr(description, ';');
214         if (!u)
215                 return -EIO;
216         r = parse_uid(u + 1, &uid);
217         if (r < 0)
218                 return r;
219         if (uid != 0)
220                 return -EPERM;
221
222         c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0);
223         if (c < 0)
224                 return -errno;
225         if (c != sizeof(sd_id128_t))
226                 return -EIO;
227
228         return 1;
229 }
230
231 _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
232         static thread_local sd_id128_t saved_invocation_id = {};
233         int r;
234
235         assert_return(ret, -EINVAL);
236
237         if (sd_id128_is_null(saved_invocation_id)) {
238
239                 /* We first try to read the invocation ID from the kernel keyring. This has the benefit that it is not
240                  * fakeable by unprivileged code. If the information is not available in the keyring, we use
241                  * $INVOCATION_ID but ignore the data if our process was called by less privileged code
242                  * (i.e. secure_getenv() instead of getenv()).
243                  *
244                  * The kernel keyring is only relevant for system services (as for user services we don't store the
245                  * invocation ID in the keyring, as there'd be no trust benefit in that). The environment variable is
246                  * primarily relevant for user services, and sufficiently safe as no privilege boundary is involved. */
247
248                 r = get_invocation_from_keyring(&saved_invocation_id);
249                 if (r < 0)
250                         return r;
251
252                 if (r == 0) {
253                         const char *e;
254
255                         e = secure_getenv("INVOCATION_ID");
256                         if (!e)
257                                 return -ENXIO;
258
259                         r = sd_id128_from_string(e, &saved_invocation_id);
260                         if (r < 0)
261                                 return r;
262                 }
263         }
264
265         *ret = saved_invocation_id;
266         return 0;
267 }
268
269 static sd_id128_t make_v4_uuid(sd_id128_t id) {
270         /* Stolen from generate_random_uuid() of drivers/char/random.c
271          * in the kernel sources */
272
273         /* Set UUID version to 4 --- truly random generation */
274         id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
275
276         /* Set the UUID variant to DCE */
277         id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
278
279         return id;
280 }
281
282 _public_ int sd_id128_randomize(sd_id128_t *ret) {
283         sd_id128_t t;
284         int r;
285
286         assert_return(ret, -EINVAL);
287
288         r = dev_urandom(&t, sizeof(t));
289         if (r < 0)
290                 return r;
291
292         /* Turn this into a valid v4 UUID, to be nice. Note that we
293          * only guarantee this for newly generated UUIDs, not for
294          * pre-existing ones. */
295
296         *ret = make_v4_uuid(t);
297         return 0;
298 }
299
300 _public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
301         _cleanup_(khash_unrefp) khash *h = NULL;
302         sd_id128_t m, result;
303         const void *p;
304         int r;
305
306         assert_return(ret, -EINVAL);
307
308         r = sd_id128_get_machine(&m);
309         if (r < 0)
310                 return r;
311
312         r = khash_new_with_key(&h, "hmac(sha256)", &m, sizeof(m));
313         if (r < 0)
314                 return r;
315
316         r = khash_put(h, &app_id, sizeof(app_id));
317         if (r < 0)
318                 return r;
319
320         r = khash_digest_data(h, &p);
321         if (r < 0)
322                 return r;
323
324         /* We chop off the trailing 16 bytes */
325         memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
326
327         *ret = make_v4_uuid(result);
328         return 0;
329 }