1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <stdbool.h>
5 #include <stddef.h>
6 #include <stdlib.h>
7 #include <sys/utsname.h>
8 
9 #include "sd-id128.h"
10 
11 #include "alloc-util.h"
12 #include "architecture.h"
13 #include "chase-symlinks.h"
14 #include "fd-util.h"
15 #include "format-util.h"
16 #include "fs-util.h"
17 #include "hostname-util.h"
18 #include "id128-util.h"
19 #include "macro.h"
20 #include "os-util.h"
21 #include "path-lookup.h"
22 #include "path-util.h"
23 #include "specifier.h"
24 #include "string-util.h"
25 #include "strv.h"
26 #include "user-util.h"
27 
28 /*
29  * Generic infrastructure for replacing %x style specifiers in
30  * strings. Will call a callback for each replacement.
31  */
32 
33 /* Any ASCII character or digit: our pool of potential specifiers,
34  * and "%" used for escaping. */
35 #define POSSIBLE_SPECIFIERS ALPHANUMERICAL "%"
36 
specifier_printf(const char * text,size_t max_length,const Specifier table[],const char * root,const void * userdata,char ** ret)37 int specifier_printf(const char *text, size_t max_length, const Specifier table[], const char *root, const void *userdata, char **ret) {
38         _cleanup_free_ char *result = NULL;
39         bool percent = false;
40         size_t l;
41         char *t;
42         int r;
43 
44         assert(ret);
45         assert(text);
46         assert(table);
47 
48         l = strlen(text);
49         if (!GREEDY_REALLOC(result, l + 1))
50                 return -ENOMEM;
51         t = result;
52 
53         for (const char *f = text; *f != '\0'; f++, l--) {
54                 if (percent) {
55                         percent = false;
56 
57                         if (*f == '%')
58                                 *(t++) = '%';
59                         else {
60                                 const Specifier *i;
61 
62                                 for (i = table; i->specifier; i++)
63                                         if (i->specifier == *f)
64                                                 break;
65 
66                                 if (i->lookup) {
67                                         _cleanup_free_ char *w = NULL;
68                                         size_t k, j;
69 
70                                         r = i->lookup(i->specifier, i->data, root, userdata, &w);
71                                         if (r < 0)
72                                                 return r;
73                                         if (isempty(w))
74                                                 continue;
75 
76                                         j = t - result;
77                                         k = strlen(w);
78 
79                                         if (!GREEDY_REALLOC(result, j + k + l + 1))
80                                                 return -ENOMEM;
81                                         memcpy(result + j, w, k);
82                                         t = result + j + k;
83                                 } else if (strchr(POSSIBLE_SPECIFIERS, *f))
84                                         /* Oops, an unknown specifier. */
85                                         return -EBADSLT;
86                                 else {
87                                         *(t++) = '%';
88                                         *(t++) = *f;
89                                 }
90                         }
91                 } else if (*f == '%')
92                         percent = true;
93                 else
94                         *(t++) = *f;
95 
96                 if ((size_t) (t - result) > max_length)
97                         return -ENAMETOOLONG;
98         }
99 
100         /* If string ended with a stray %, also end with % */
101         if (percent) {
102                 *(t++) = '%';
103                 if ((size_t) (t - result) > max_length)
104                         return -ENAMETOOLONG;
105         }
106         *(t++) = 0;
107 
108         *ret = TAKE_PTR(result);
109         return 0;
110 }
111 
112 /* Generic handler for simple string replacements */
113 
specifier_string(char specifier,const void * data,const char * root,const void * userdata,char ** ret)114 int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
115         char *n = NULL;
116 
117         assert(ret);
118 
119         if (!isempty(data)) {
120                 n = strdup(data);
121                 if (!n)
122                         return -ENOMEM;
123         }
124 
125         *ret = n;
126         return 0;
127 }
128 
specifier_real_path(char specifier,const void * data,const char * root,const void * userdata,char ** ret)129 int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
130         const char *path = data;
131 
132         assert(ret);
133 
134         if (!path)
135                 return -ENOENT;
136 
137         return chase_symlinks(path, root, 0, ret, NULL);
138 }
139 
specifier_real_directory(char specifier,const void * data,const char * root,const void * userdata,char ** ret)140 int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
141         _cleanup_free_ char *path = NULL;
142         int r;
143 
144         assert(ret);
145 
146         r = specifier_real_path(specifier, data, root, userdata, &path);
147         if (r < 0)
148                 return r;
149 
150         assert(path);
151         return path_extract_directory(path, ret);
152 }
153 
specifier_machine_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)154 int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
155         sd_id128_t id;
156         char *n;
157         int r;
158 
159         assert(ret);
160 
161         if (root) {
162                 _cleanup_close_ int fd = -1;
163 
164                 fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
165                 if (fd < 0)
166                         /* Translate error for missing os-release file to EUNATCH. */
167                         return fd == -ENOENT ? -EUNATCH : fd;
168 
169                 r = id128_read_fd(fd, ID128_PLAIN, &id);
170         } else
171                 r = sd_id128_get_machine(&id);
172         if (r < 0)
173                 return r;
174 
175         n = new(char, SD_ID128_STRING_MAX);
176         if (!n)
177                 return -ENOMEM;
178 
179         *ret = sd_id128_to_string(id, n);
180         return 0;
181 }
182 
specifier_boot_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)183 int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
184         sd_id128_t id;
185         char *n;
186         int r;
187 
188         assert(ret);
189 
190         r = sd_id128_get_boot(&id);
191         if (r < 0)
192                 return r;
193 
194         n = new(char, SD_ID128_STRING_MAX);
195         if (!n)
196                 return -ENOMEM;
197 
198         *ret = sd_id128_to_string(id, n);
199         return 0;
200 }
201 
specifier_hostname(char specifier,const void * data,const char * root,const void * userdata,char ** ret)202 int specifier_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
203         char *n;
204 
205         assert(ret);
206 
207         n = gethostname_malloc();
208         if (!n)
209                 return -ENOMEM;
210 
211         *ret = n;
212         return 0;
213 }
214 
specifier_short_hostname(char specifier,const void * data,const char * root,const void * userdata,char ** ret)215 int specifier_short_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
216         char *n;
217 
218         assert(ret);
219 
220         n = gethostname_short_malloc();
221         if (!n)
222                 return -ENOMEM;
223 
224         *ret = n;
225         return 0;
226 }
227 
specifier_pretty_hostname(char specifier,const void * data,const char * root,const void * userdata,char ** ret)228 int specifier_pretty_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
229         char *n = NULL;
230 
231         assert(ret);
232 
233         if (get_pretty_hostname(&n) < 0) {
234                 n = gethostname_short_malloc();
235                 if (!n)
236                         return -ENOMEM;
237         }
238 
239         *ret = n;
240         return 0;
241 }
242 
specifier_kernel_release(char specifier,const void * data,const char * root,const void * userdata,char ** ret)243 int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
244         struct utsname uts;
245         char *n;
246 
247         assert(ret);
248 
249         if (uname(&uts) < 0)
250                 return -errno;
251 
252         n = strdup(uts.release);
253         if (!n)
254                 return -ENOMEM;
255 
256         *ret = n;
257         return 0;
258 }
259 
specifier_architecture(char specifier,const void * data,const char * root,const void * userdata,char ** ret)260 int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
261         char *t;
262 
263         assert(ret);
264 
265         t = strdup(architecture_to_string(uname_architecture()));
266         if (!t)
267                 return -ENOMEM;
268 
269         *ret = t;
270         return 0;
271 }
272 
273 /* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid
274  * otherwise. We'll return an empty value or NULL in that case from the functions below. But if the
275  * os-release file is missing, we'll return -EUNATCH. This means that something is seriously wrong with the
276  * installation. */
277 
parse_os_release_specifier(const char * root,const char * id,char ** ret)278 static int parse_os_release_specifier(const char *root, const char *id, char **ret) {
279         char *v = NULL;
280         int r;
281 
282         assert(ret);
283 
284         r = parse_os_release(root, id, &v);
285         if (r >= 0)
286                 /* parse_os_release() calls parse_env_file() which only sets the return value for
287                  * entries found. Let's make sure we set the return value in all cases. */
288                 *ret = v;
289 
290         /* Translate error for missing os-release file to EUNATCH. */
291         return r == -ENOENT ? -EUNATCH : r;
292 }
293 
specifier_os_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)294 int specifier_os_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
295         return parse_os_release_specifier(root, "ID", ret);
296 }
297 
specifier_os_version_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)298 int specifier_os_version_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
299         return parse_os_release_specifier(root, "VERSION_ID", ret);
300 }
301 
specifier_os_build_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)302 int specifier_os_build_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
303         return parse_os_release_specifier(root, "BUILD_ID", ret);
304 }
305 
specifier_os_variant_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)306 int specifier_os_variant_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
307         return parse_os_release_specifier(root, "VARIANT_ID", ret);
308 }
309 
specifier_os_image_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)310 int specifier_os_image_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
311         return parse_os_release_specifier(root, "IMAGE_ID", ret);
312 }
313 
specifier_os_image_version(char specifier,const void * data,const char * root,const void * userdata,char ** ret)314 int specifier_os_image_version(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
315         return parse_os_release_specifier(root, "IMAGE_VERSION", ret);
316 }
317 
specifier_group_name(char specifier,const void * data,const char * root,const void * userdata,char ** ret)318 int specifier_group_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
319         LookupScope scope = PTR_TO_INT(data);
320         char *t;
321 
322         assert(ret);
323 
324         if (scope == LOOKUP_SCOPE_GLOBAL)
325                 return -EINVAL;
326 
327         t = gid_to_name(scope == LOOKUP_SCOPE_USER ? getgid() : 0);
328         if (!t)
329                 return -ENOMEM;
330 
331         *ret = t;
332         return 0;
333 }
334 
specifier_group_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)335 int specifier_group_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
336         LookupScope scope = PTR_TO_INT(data);
337         gid_t gid;
338 
339         assert(ret);
340 
341         if (scope == LOOKUP_SCOPE_GLOBAL)
342                 return -EINVAL;
343 
344         gid = scope == LOOKUP_SCOPE_USER ? getgid() : 0;
345 
346         if (asprintf(ret, UID_FMT, gid) < 0)
347                 return -ENOMEM;
348 
349         return 0;
350 }
351 
specifier_user_name(char specifier,const void * data,const char * root,const void * userdata,char ** ret)352 int specifier_user_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
353         LookupScope scope = PTR_TO_INT(data);
354         uid_t uid;
355         char *t;
356 
357         assert(ret);
358 
359         if (scope == LOOKUP_SCOPE_GLOBAL)
360                 return -EINVAL;
361 
362         uid = scope == LOOKUP_SCOPE_USER ? getuid() : 0;
363 
364         /* If we are UID 0 (root), this will not result in NSS, otherwise it might. This is good, as we want
365          * to be able to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed.
366 
367          * We don't use getusername_malloc() here, because we don't want to look at $USER, to remain
368          * consistent with specifer_user_id() below.
369          */
370 
371         t = uid_to_name(uid);
372         if (!t)
373                 return -ENOMEM;
374 
375         *ret = t;
376         return 0;
377 }
378 
specifier_user_id(char specifier,const void * data,const char * root,const void * userdata,char ** ret)379 int specifier_user_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
380         LookupScope scope = PTR_TO_INT(data);
381         uid_t uid;
382 
383         assert(ret);
384 
385         if (scope == LOOKUP_SCOPE_GLOBAL)
386                 return -EINVAL;
387 
388         uid = scope == LOOKUP_SCOPE_USER ? getuid() : 0;
389 
390         if (asprintf(ret, UID_FMT, uid) < 0)
391                 return -ENOMEM;
392 
393         return 0;
394 }
395 
specifier_user_home(char specifier,const void * data,const char * root,const void * userdata,char ** ret)396 int specifier_user_home(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
397         assert(ret);
398 
399         /* On PID 1 (which runs as root) this will not result in NSS,
400          * which is good. See above */
401 
402         return get_home_dir(ret);
403 }
404 
specifier_user_shell(char specifier,const void * data,const char * root,const void * userdata,char ** ret)405 int specifier_user_shell(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
406         assert(ret);
407 
408         /* On PID 1 (which runs as root) this will not result in NSS,
409          * which is good. See above */
410 
411         return get_shell(ret);
412 }
413 
specifier_tmp_dir(char specifier,const void * data,const char * root,const void * userdata,char ** ret)414 int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
415         const char *p;
416         char *copy;
417         int r;
418 
419         assert(ret);
420 
421         if (root) /* If root dir is set, don't honour $TMP or similar */
422                 p = "/tmp";
423         else {
424                 r = tmp_dir(&p);
425                 if (r < 0)
426                         return r;
427         }
428         copy = strdup(p);
429         if (!copy)
430                 return -ENOMEM;
431 
432         *ret = copy;
433         return 0;
434 }
435 
specifier_var_tmp_dir(char specifier,const void * data,const char * root,const void * userdata,char ** ret)436 int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
437         const char *p;
438         char *copy;
439         int r;
440 
441         assert(ret);
442 
443         if (root)
444                 p = "/var/tmp";
445         else {
446                 r = var_tmp_dir(&p);
447                 if (r < 0)
448                         return r;
449         }
450         copy = strdup(p);
451         if (!copy)
452                 return -ENOMEM;
453 
454         *ret = copy;
455         return 0;
456 }
457 
specifier_escape_strv(char ** l,char *** ret)458 int specifier_escape_strv(char **l, char ***ret) {
459         char **z, **p, **q;
460 
461         assert(ret);
462 
463         if (strv_isempty(l)) {
464                 *ret = NULL;
465                 return 0;
466         }
467 
468         z = new(char*, strv_length(l)+1);
469         if (!z)
470                 return -ENOMEM;
471 
472         for (p = l, q = z; *p; p++, q++) {
473 
474                 *q = specifier_escape(*p);
475                 if (!*q) {
476                         strv_free(z);
477                         return -ENOMEM;
478                 }
479         }
480 
481         *q = NULL;
482         *ret = z;
483 
484         return 0;
485 }
486 
487 const Specifier system_and_tmp_specifier_table[] = {
488         COMMON_SYSTEM_SPECIFIERS,
489         COMMON_TMP_SPECIFIERS,
490         {}
491 };
492