blob: 55ac11a5124605eec6e1a536b33394dc4a7345b1 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include "alloc-util.h"
#include "env-util.h"
#include "errno-util.h"
#include "escape.h"
#include "extract-word.h"
#include "macro.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "utf8.h"
/* We follow bash for the character set. Different shells have different rules. */
#define VALID_BASH_ENV_NAME_CHARS \
DIGITS LETTERS \
"_"
static bool env_name_is_valid_n(const char *e, size_t n) {
if (!e)
return false;
if (n <= 0)
return false;
if (ascii_isdigit(e[0]))
return false;
/* POSIX says the overall size of the environment block cannot
* be > ARG_MAX, an individual assignment hence cannot be
* either. Discounting the equal sign and trailing NUL this
* hence leaves ARG_MAX-2 as longest possible variable
* name. */
if (n > (size_t) sysconf(_SC_ARG_MAX) - 2)
return false;
for (const char *p = e; p < e + n; p++)
if (!strchr(VALID_BASH_ENV_NAME_CHARS, *p))
return false;
return true;
}
bool env_name_is_valid(const char *e) {
return env_name_is_valid_n(e, strlen_ptr(e));
}
bool env_value_is_valid(const char *e) {
if (!e)
return false;
if (!utf8_is_valid(e))
return false;
/* Note that variable *values* may contain control characters, in particular NL, TAB, BS, DEL, ESC…
* When printing those variables with show-environment, we'll escape them. Make sure to print
* environment variables carefully! */
/* POSIX says the overall size of the environment block cannot be > ARG_MAX, an individual assignment
* hence cannot be either. Discounting the shortest possible variable name of length 1, the equal
* sign and trailing NUL this hence leaves ARG_MAX-3 as longest possible variable value. */
if (strlen(e) > sc_arg_max() - 3)
return false;
return true;
}
bool env_assignment_is_valid(const char *e) {
const char *eq;
eq = strchr(e, '=');
if (!eq)
return false;
if (!env_name_is_valid_n(e, eq - e))
return false;
if (!env_value_is_valid(eq + 1))
return false;
/* POSIX says the overall size of the environment block cannot be > ARG_MAX, hence the individual
* variable assignments cannot be either, but let's leave room for one trailing NUL byte. */
if (strlen(e) > sc_arg_max() - 1)
return false;
return true;
}
bool strv_env_is_valid(char **e) {
STRV_FOREACH(p, e) {
size_t k;
if (!env_assignment_is_valid(*p))
return false;
/* Check if there are duplicate assignments */
k = strcspn(*p, "=");
STRV_FOREACH(q, p + 1)
if (strneq(*p, *q, k) && (*q)[k] == '=')
return false;
}
return true;
}
bool strv_env_name_is_valid(char **l) {
STRV_FOREACH(p, l) {
if (!env_name_is_valid(*p))
return false;
if (strv_contains(p + 1, *p))
return false;
}
return true;
}
bool strv_env_name_or_assignment_is_valid(char **l) {
STRV_FOREACH(p, l) {
if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p))
return false;
if (strv_contains(p + 1, *p))
return false;
}
return true;
}
static int env_append(char **r, char ***k, char **a) {
assert(r);
assert(k);
assert(*k >= r);
if (!a)
return 0;
/* Expects the following arguments: 'r' shall point to the beginning of an strv we are going to append to, 'k'
* to a pointer pointing to the NULL entry at the end of the same array. 'a' shall point to another strv.
*
* This call adds every entry of 'a' to 'r', either overriding an existing matching entry, or appending to it.
*
* This call assumes 'r' has enough pre-allocated space to grow by all of 'a''s items. */
for (; *a; a++) {
char **j, *c;
size_t n;
n = strcspn(*a, "=");
if ((*a)[n] == '=')
n++;
for (j = r; j < *k; j++)
if (strneq(*j, *a, n))
break;
c = strdup(*a);
if (!c)
return -ENOMEM;
if (j >= *k) { /* Append to the end? */
(*k)[0] = c;
(*k)[1] = NULL;
(*k)++;
} else
free_and_replace(*j, c); /* Override existing item */
}
return 0;
}
char** _strv_env_merge(char **first, ...) {
_cleanup_strv_free_ char **merged = NULL;
char **k;
va_list ap;
/* Merges an arbitrary number of environment sets */
size_t n = strv_length(first);
va_start(ap, first);
for (;;) {
char **l;
l = va_arg(ap, char**);
if (l == POINTER_MAX)
break;
n += strv_length(l);
}
va_end(ap);
k = merged = new(char*, n + 1);
if (!merged)
return NULL;
merged[0] = NULL;
if (env_append(merged, &k, first) < 0)
return NULL;
va_start(ap, first);
for (;;) {
char **l;
l = va_arg(ap, char**);
if (l == POINTER_MAX)
break;
if (env_append(merged, &k, l) < 0) {
va_end(ap);
return NULL;
}
}
va_end(ap);
return TAKE_PTR(merged);
}
static bool env_match(const char *t, const char *pattern) {
assert(t);
assert(pattern);
/* pattern a matches string a
* a matches a=
* a matches a=b
* a= matches a=
* a=b matches a=b
* a= does not match a
* a=b does not match a=
* a=b does not match a
* a=b does not match a=c */
if (streq(t, pattern))
return true;
if (!strchr(pattern, '=')) {
size_t l = strlen(pattern);
return strneq(t, pattern, l) && t[l] == '=';
}
return false;
}
static bool env_entry_has_name(const char *entry, const char *name) {
const char *t;
assert(entry);
assert(name);
t = startswith(entry, name);
if (!t)
return false;
return *t == '=';
}
char **strv_env_delete(char **x, size_t n_lists, ...) {
size_t n, i = 0;
char **r;
va_list ap;
/* Deletes every entry from x that is mentioned in the other
* string lists */
n = strv_length(x);
r = new(char*, n+1);
if (!r)
return NULL;
STRV_FOREACH(k, x) {
va_start(ap, n_lists);
for (size_t v = 0; v < n_lists; v++) {
char **l;
l = va_arg(ap, char**);
STRV_FOREACH(j, l)
if (env_match(*k, *j))
goto skip;
}
va_end(ap);
r[i] = strdup(*k);
if (!r[i]) {
strv_free(r);
return NULL;
}
i++;
continue;
skip:
va_end(ap);
}
r[i] = NULL;
assert(i <= n);
return r;
}
char **strv_env_unset(char **l, const char *p) {
char **f, **t;
if (!l)
return NULL;
assert(p);
/* Drops every occurrence of the env var setting p in the
* string list. Edits in-place. */
for (f = t = l; *f; f++) {
if (env_match(*f, p)) {
free(*f);
continue;
}
*(t++) = *f;
}
*t = NULL;
return l;
}
char **strv_env_unset_many(char **l, ...) {
char **f, **t;
if (!l)
return NULL;
/* Like strv_env_unset() but applies many at once. Edits in-place. */
for (f = t = l; *f; f++) {
bool found = false;
const char *p;
va_list ap;
va_start(ap, l);
while ((p = va_arg(ap, const char*))) {
if (env_match(*f, p)) {
found = true;
break;
}
}
va_end(ap);
if (found) {
free(*f);
continue;
}
*(t++) = *f;
}
*t = NULL;
return l;
}
int strv_env_replace_consume(char ***l, char *p) {
const char *t, *name;
int r;
assert(p);
/* Replace first occurrence of the env var or add a new one in the string list. Drop other
* occurrences. Edits in-place. Does not copy p and CONSUMES p EVEN ON FAILURE.
*
* p must be a valid key=value assignment. */
t = strchr(p, '=');
if (!t) {
free(p);
return -EINVAL;
}
name = strndupa_safe(p, t - p);
STRV_FOREACH(f, *l)
if (env_entry_has_name(*f, name)) {
free_and_replace(*f, p);
strv_env_unset(f + 1, *f);
return 0;
}
/* We didn't find a match, we need to append p or create a new strv */
r = strv_consume(l, p);
if (r < 0)
return r;
return 1;
}
int strv_env_replace_strdup(char ***l, const char *assignment) {
/* Like strv_env_replace_consume(), but copies the argument. */
char *p = strdup(assignment);
if (!p)
return -ENOMEM;
return strv_env_replace_consume(l, p);
}
int strv_env_replace_strdup_passthrough(char ***l, const char *assignment) {
/* Like strv_env_replace_strdup(), but pulls the variable from the environment of
* the calling program, if a variable name without value is specified.
*/
char *p;
if (strchr(assignment, '=')) {
if (!env_assignment_is_valid(assignment))
return -EINVAL;
p = strdup(assignment);
} else {
if (!env_name_is_valid(assignment))
return -EINVAL;
/* If we can't find the variable in our environment, we will use
* the empty string. This way "passthrough" is equivalent to passing
* --setenv=FOO=$FOO in the shell. */
p = strjoin(assignment, "=", secure_getenv(assignment));
}
if (!p)
return -ENOMEM;
return strv_env_replace_consume(l, p);
}
int strv_env_assign(char ***l, const char *key, const char *value) {
if (!env_name_is_valid(key))
return -EINVAL;
/* NULL removes assignment, "" creates an empty assignment. */
if (!value) {
strv_env_unset(*l, key);
return 0;
}
char *p = strjoin(key, "=", value);
if (!p)
return -ENOMEM;
return strv_env_replace_consume(l, p);
}
char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
assert(name);
if (k <= 0)
return NULL;
STRV_FOREACH_BACKWARDS(i, l)
if (strneq(*i, name, k) &&
(*i)[k] == '=')
return *i + k + 1;
if (flags & REPLACE_ENV_USE_ENVIRONMENT) {
const char *t;
t = strndupa_safe(name, k);
return getenv(t);
};
return NULL;
}
char *strv_env_get(char **l, const char *name) {
assert(name);
return strv_env_get_n(l, name, strlen(name), 0);
}
char *strv_env_pairs_get(char **l, const char *name) {
char *result = NULL;
assert(name);
STRV_FOREACH_PAIR(key, value, l)
if (streq(*key, name))
result = *value;
return result;
}
char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
int k = 0;
STRV_FOREACH(p, e) {
size_t n;
bool duplicate = false;
if (!env_assignment_is_valid(*p)) {
if (invalid_callback)
invalid_callback(*p, userdata);
free(*p);
continue;
}
n = strcspn(*p, "=");
STRV_FOREACH(q, p + 1)
if (strneq(*p, *q, n) && (*q)[n] == '=') {
duplicate = true;
break;
}
if (duplicate) {
free(*p);
continue;
}
e[k++] = *p;
}
if (e)
e[k] = NULL;
return e;
}
char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
enum {
WORD,
CURLY,
VARIABLE,
VARIABLE_RAW,
TEST,
DEFAULT_VALUE,
ALTERNATE_VALUE,
} state = WORD;
const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */
char *k;
_cleanup_free_ char *r = NULL;
size_t i, len = 0; /* len is initialized to appease gcc */
int nest = 0;
assert(format);
for (e = format, i = 0; *e && i < n; e ++, i ++)
switch (state) {
case WORD:
if (*e == '$')
state = CURLY;
break;
case CURLY:
if (*e == '{') {
k = strnappend(r, word, e-word-1);
if (!k)
return NULL;
free_and_replace(r, k);
word = e-1;
state = VARIABLE;
nest++;
} else if (*e == '$') {
k = strnappend(r, word, e-word);
if (!k)
return NULL;
free_and_replace(r, k);
word = e+1;
state = WORD;
} else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
k = strnappend(r, word, e-word-1);
if (!k)
return NULL;
free_and_replace(r, k);
word = e-1;
state = VARIABLE_RAW;
} else
state = WORD;
break;
case VARIABLE:
if (*e == '}') {
const char *t;
t = strv_env_get_n(env, word+2, e-word-2, flags);
if (!strextend(&r, t))
return NULL;
word = e+1;
state = WORD;
nest--;
} else if (*e == ':') {
if (flags & REPLACE_ENV_ALLOW_EXTENDED) {
len = e - word - 2;
state = TEST;
} else
/* Treat this as unsupported syntax, i.e. do no replacement */
state = WORD;
}
break;
case TEST:
if (*e == '-')
state = DEFAULT_VALUE;
else if (*e == '+')
state = ALTERNATE_VALUE;
else {
state = WORD;
break;
}
test_value = e+1;
break;
case DEFAULT_VALUE: /* fall through */
case ALTERNATE_VALUE:
assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
if (*e == '{') {
nest++;
break;
}
if (*e != '}')
break;
nest--;
if (nest == 0) {
const char *t;
_cleanup_free_ char *v = NULL;
t = strv_env_get_n(env, word+2, len, flags);
if (t && state == ALTERNATE_VALUE)
t = v = replace_env_n(test_value, e-test_value, env, flags);
else if (!t && state == DEFAULT_VALUE)
t = v = replace_env_n(test_value, e-test_value, env, flags);
if (!strextend(&r, t))
return NULL;
word = e+1;
state = WORD;
}
break;
case VARIABLE_RAW:
assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
if (!strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
const char *t;
t = strv_env_get_n(env, word+1, e-word-1, flags);
if (!strextend(&r, t))
return NULL;
word = e--;
i--;
state = WORD;
}
break;
}
if (state == VARIABLE_RAW) {
const char *t;
assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
t = strv_env_get_n(env, word+1, e-word-1, flags);
return strjoin(r, t);
} else
return strnappend(r, word, e-word);
}
char **replace_env_argv(char **argv, char **env) {
char **ret;
size_t k = 0, l = 0;
l = strv_length(argv);
ret = new(char*, l+1);
if (!ret)
return NULL;
STRV_FOREACH(i, argv) {
/* If $FOO appears as single word, replace it by the split up variable */
if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
char *e;
char **w, **m = NULL;
size_t q;
e = strv_env_get(env, *i+1);
if (e) {
int r;
r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
if (r < 0) {
ret[k] = NULL;
strv_free(ret);
return NULL;
}
} else
m = NULL;
q = strv_length(m);
l = l + q - 1;
w = reallocarray(ret, l + 1, sizeof(char *));
if (!w) {
ret[k] = NULL;
strv_free(ret);
strv_free(m);
return NULL;
}
ret = w;
if (m) {
memcpy(ret + k, m, q * sizeof(char*));
free(m);
}
k += q;
continue;
}
/* If ${FOO} appears as part of a word, replace it by the variable as-is */
ret[k] = replace_env(*i, env, 0);
if (!ret[k]) {
strv_free(ret);
return NULL;
}
k++;
}
ret[k] = NULL;
return ret;
}
int getenv_bool(const char *p) {
const char *e;
e = getenv(p);
if (!e)
return -ENXIO;
return parse_boolean(e);
}
int getenv_bool_secure(const char *p) {
const char *e;
e = secure_getenv(p);
if (!e)
return -ENXIO;
return parse_boolean(e);
}
int getenv_uint64_secure(const char *p, uint64_t *ret) {
const char *e;
assert(p);
e = secure_getenv(p);
if (!e)
return -ENXIO;
return safe_atou64(e, ret);
}
int set_unset_env(const char *name, const char *value, bool overwrite) {
assert(name);
if (value)
return RET_NERRNO(setenv(name, value, overwrite));
return RET_NERRNO(unsetenv(name));
}
int putenv_dup(const char *assignment, bool override) {
const char *e, *n;
e = strchr(assignment, '=');
if (!e)
return -EINVAL;
n = strndupa_safe(assignment, e - assignment);
/* This is like putenv(), but uses setenv() so that our memory doesn't become part of environ[]. */
return RET_NERRNO(setenv(n, e + 1, override));
}
int setenv_systemd_exec_pid(bool update_only) {
char str[DECIMAL_STR_MAX(pid_t)];
const char *e;
/* Update $SYSTEMD_EXEC_PID=pid except when '*' is set for the variable. */
e = secure_getenv("SYSTEMD_EXEC_PID");
if (!e && update_only)
return 0;
if (streq_ptr(e, "*"))
return 0;
xsprintf(str, PID_FMT, getpid_cached());
if (setenv("SYSTEMD_EXEC_PID", str, 1) < 0)
return -errno;
return 1;
}
int getenv_path_list(const char *name, char ***ret_paths) {
_cleanup_strv_free_ char **l = NULL;
const char *e;
int r;
assert(name);
assert(ret_paths);
e = secure_getenv(name);
if (!e)
return -ENXIO;
r = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return log_debug_errno(r, "Failed to parse $%s: %m", name);
STRV_FOREACH(p, l) {
if (!path_is_absolute(*p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Path '%s' is not absolute, refusing.", *p);
if (!path_is_normalized(*p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Path '%s' is not normalized, refusing.", *p);
if (path_equal(*p, "/"))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Path '%s' is the root fs, refusing.", *p);
}
if (strv_isempty(l))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"No paths specified, refusing.");
*ret_paths = TAKE_PTR(l);
return 1;
}
int getenv_steal_erase(const char *name, char **ret) {
_cleanup_(erase_and_freep) char *a = NULL;
char *e;
assert(name);
/* Reads an environment variable, makes a copy of it, erases its memory in the environment block and removes
* it from there. Usecase: reading passwords from the env block (which is a bad idea, but useful for
* testing, and given that people are likely going to misuse this, be thorough) */
e = getenv(name);
if (!e) {
if (ret)
*ret = NULL;
return 0;
}
if (ret) {
a = strdup(e);
if (!a)
return -ENOMEM;
}
string_erase(e);
if (unsetenv(name) < 0)
return -errno;
if (ret)
*ret = TAKE_PTR(a);
return 1;
}