/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #include "sd-id128.h" #include "alloc-util.h" #include "architecture.h" #include "chase-symlinks.h" #include "fd-util.h" #include "format-util.h" #include "fs-util.h" #include "hostname-util.h" #include "id128-util.h" #include "macro.h" #include "os-util.h" #include "path-lookup.h" #include "path-util.h" #include "specifier.h" #include "string-util.h" #include "strv.h" #include "user-util.h" /* * Generic infrastructure for replacing %x style specifiers in * strings. Will call a callback for each replacement. */ /* Any ASCII character or digit: our pool of potential specifiers, * and "%" used for escaping. */ #define POSSIBLE_SPECIFIERS ALPHANUMERICAL "%" int specifier_printf(const char *text, size_t max_length, const Specifier table[], const char *root, const void *userdata, char **ret) { _cleanup_free_ char *result = NULL; bool percent = false; size_t l; char *t; int r; assert(ret); assert(text); assert(table); l = strlen(text); if (!GREEDY_REALLOC(result, l + 1)) return -ENOMEM; t = result; for (const char *f = text; *f != '\0'; f++, l--) { if (percent) { percent = false; if (*f == '%') *(t++) = '%'; else { const Specifier *i; for (i = table; i->specifier; i++) if (i->specifier == *f) break; if (i->lookup) { _cleanup_free_ char *w = NULL; size_t k, j; r = i->lookup(i->specifier, i->data, root, userdata, &w); if (r < 0) return r; if (isempty(w)) continue; j = t - result; k = strlen(w); if (!GREEDY_REALLOC(result, j + k + l + 1)) return -ENOMEM; memcpy(result + j, w, k); t = result + j + k; } else if (strchr(POSSIBLE_SPECIFIERS, *f)) /* Oops, an unknown specifier. */ return -EBADSLT; else { *(t++) = '%'; *(t++) = *f; } } } else if (*f == '%') percent = true; else *(t++) = *f; if ((size_t) (t - result) > max_length) return -ENAMETOOLONG; } /* If string ended with a stray %, also end with % */ if (percent) { *(t++) = '%'; if ((size_t) (t - result) > max_length) return -ENAMETOOLONG; } *(t++) = 0; *ret = TAKE_PTR(result); return 0; } /* Generic handler for simple string replacements */ int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret) { char *n = NULL; assert(ret); if (!isempty(data)) { n = strdup(data); if (!n) return -ENOMEM; } *ret = n; return 0; } int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const char *path = data; assert(ret); if (!path) return -ENOENT; return chase_symlinks(path, root, 0, ret, NULL); } int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) { _cleanup_free_ char *path = NULL; int r; assert(ret); r = specifier_real_path(specifier, data, root, userdata, &path); if (r < 0) return r; assert(path); return path_extract_directory(path, ret); } int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const sd_id128_t *id = ASSERT_PTR(data); char *n; n = new(char, SD_ID128_STRING_MAX); if (!n) return -ENOMEM; *ret = sd_id128_to_string(*id, n); return 0; } int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const sd_id128_t *id = ASSERT_PTR(data); char *n; n = new(char, SD_ID128_UUID_STRING_MAX); if (!n) return -ENOMEM; *ret = sd_id128_to_uuid_string(*id, n); return 0; } int specifier_uint64(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const uint64_t *n = ASSERT_PTR(data); return asprintf(ret, "%" PRIu64, *n) < 0 ? -ENOMEM : 0; } int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { sd_id128_t id; int r; assert(ret); if (root) { _cleanup_close_ int fd = -1; fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); if (fd < 0) /* Translate error for missing os-release file to EUNATCH. */ return fd == -ENOENT ? -EUNATCH : fd; r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &id); } else r = sd_id128_get_machine(&id); if (r < 0) return r; return specifier_id128(specifier, &id, root, userdata, ret); } int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { sd_id128_t id; int r; assert(ret); r = sd_id128_get_boot(&id); if (r < 0) return r; return specifier_id128(specifier, &id, root, userdata, ret); } int specifier_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { char *n; assert(ret); n = gethostname_malloc(); if (!n) return -ENOMEM; *ret = n; return 0; } int specifier_short_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { char *n; assert(ret); n = gethostname_short_malloc(); if (!n) return -ENOMEM; *ret = n; return 0; } int specifier_pretty_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { char *n = NULL; assert(ret); if (get_pretty_hostname(&n) < 0) { n = gethostname_short_malloc(); if (!n) return -ENOMEM; } *ret = n; return 0; } int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) { struct utsname uts; char *n; assert(ret); if (uname(&uts) < 0) return -errno; n = strdup(uts.release); if (!n) return -ENOMEM; *ret = n; return 0; } int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) { char *t; assert(ret); t = strdup(architecture_to_string(uname_architecture())); if (!t) return -ENOMEM; *ret = t; return 0; } /* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid * otherwise. We'll return an empty value or NULL in that case from the functions below. But if the * os-release file is missing, we'll return -EUNATCH. This means that something is seriously wrong with the * installation. */ static int parse_os_release_specifier(const char *root, const char *id, char **ret) { _cleanup_free_ char *v = NULL; int r; assert(ret); r = parse_os_release(root, id, &v); if (r >= 0) /* parse_os_release() calls parse_env_file() which only sets the return value for * entries found. Let's make sure we set the return value in all cases. */ *ret = TAKE_PTR(v); /* Translate error for missing os-release file to EUNATCH. */ return r == -ENOENT ? -EUNATCH : r; } int specifier_os_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { return parse_os_release_specifier(root, "ID", ret); } int specifier_os_version_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { return parse_os_release_specifier(root, "VERSION_ID", ret); } int specifier_os_build_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { return parse_os_release_specifier(root, "BUILD_ID", ret); } int specifier_os_variant_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { return parse_os_release_specifier(root, "VARIANT_ID", ret); } int specifier_os_image_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { return parse_os_release_specifier(root, "IMAGE_ID", ret); } int specifier_os_image_version(char specifier, const void *data, const char *root, const void *userdata, char **ret) { return parse_os_release_specifier(root, "IMAGE_VERSION", ret); } int specifier_group_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) { LookupScope scope = PTR_TO_INT(data); char *t; assert(ret); if (scope == LOOKUP_SCOPE_GLOBAL) return -EINVAL; t = gid_to_name(scope == LOOKUP_SCOPE_USER ? getgid() : 0); if (!t) return -ENOMEM; *ret = t; return 0; } int specifier_group_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { LookupScope scope = PTR_TO_INT(data); gid_t gid; assert(ret); if (scope == LOOKUP_SCOPE_GLOBAL) return -EINVAL; gid = scope == LOOKUP_SCOPE_USER ? getgid() : 0; if (asprintf(ret, UID_FMT, gid) < 0) return -ENOMEM; return 0; } int specifier_user_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) { LookupScope scope = PTR_TO_INT(data); uid_t uid; char *t; assert(ret); if (scope == LOOKUP_SCOPE_GLOBAL) return -EINVAL; uid = scope == LOOKUP_SCOPE_USER ? getuid() : 0; /* If we are UID 0 (root), this will not result in NSS, otherwise it might. This is good, as we want * to be able to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed. * We don't use getusername_malloc() here, because we don't want to look at $USER, to remain * consistent with specifer_user_id() below. */ t = uid_to_name(uid); if (!t) return -ENOMEM; *ret = t; return 0; } int specifier_user_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { LookupScope scope = PTR_TO_INT(data); uid_t uid; assert(ret); if (scope == LOOKUP_SCOPE_GLOBAL) return -EINVAL; uid = scope == LOOKUP_SCOPE_USER ? getuid() : 0; if (asprintf(ret, UID_FMT, uid) < 0) return -ENOMEM; return 0; } int specifier_user_home(char specifier, const void *data, const char *root, const void *userdata, char **ret) { assert(ret); /* On PID 1 (which runs as root) this will not result in NSS, * which is good. See above */ return get_home_dir(ret); } int specifier_user_shell(char specifier, const void *data, const char *root, const void *userdata, char **ret) { assert(ret); /* On PID 1 (which runs as root) this will not result in NSS, * which is good. See above */ return get_shell(ret); } int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const char *p; char *copy; int r; assert(ret); if (root) /* If root dir is set, don't honour $TMP or similar */ p = "/tmp"; else { r = tmp_dir(&p); if (r < 0) return r; } copy = strdup(p); if (!copy) return -ENOMEM; *ret = copy; return 0; } int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const char *p; char *copy; int r; assert(ret); if (root) p = "/var/tmp"; else { r = var_tmp_dir(&p); if (r < 0) return r; } copy = strdup(p); if (!copy) return -ENOMEM; *ret = copy; return 0; } int specifier_escape_strv(char **l, char ***ret) { char **z, **p, **q; assert(ret); if (strv_isempty(l)) { *ret = NULL; return 0; } z = new(char*, strv_length(l)+1); if (!z) return -ENOMEM; for (p = l, q = z; *p; p++, q++) { *q = specifier_escape(*p); if (!*q) { strv_free(z); return -ENOMEM; } } *q = NULL; *ret = z; return 0; } const Specifier system_and_tmp_specifier_table[] = { COMMON_SYSTEM_SPECIFIERS, COMMON_TMP_SPECIFIERS, {} };