/* * OpenVPN -- An application to securely tunnel IP networks * over a single TCP/UDP port, with support for SSL/TLS-based * session authentication and key exchange, * packet encryption, packet authentication, and * packet compression. * * Copyright (C) 2002-2021 OpenVPN Technologies, Inc. * Copyright (C) 2014-2015 David Sommerseth * Copyright (C) 2016-2021 David Sommerseth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_MSC_VER) #include "config-msvc.h" #endif #include "syshead.h" #include "env_set.h" #include "run_command.h" /* * Set environmental variable (int or string). * * On Posix, we use putenv for portability, * and put up with its painful semantics * that require all the support code below. */ /* General-purpose environmental variable set functions */ static char * construct_name_value(const char *name, const char *value, struct gc_arena *gc) { struct buffer out; ASSERT(name); if (!value) { value = ""; } out = alloc_buf_gc(strlen(name) + strlen(value) + 2, gc); buf_printf(&out, "%s=%s", name, value); return BSTR(&out); } static bool env_string_equal(const char *s1, const char *s2) { int c1, c2; ASSERT(s1); ASSERT(s2); while (true) { c1 = *s1++; c2 = *s2++; if (c1 == '=') { c1 = 0; } if (c2 == '=') { c2 = 0; } if (!c1 && !c2) { return true; } if (c1 != c2) { break; } } return false; } static bool remove_env_item(const char *str, const bool do_free, struct env_item **list) { struct env_item *current, *prev; ASSERT(str); ASSERT(list); for (current = *list, prev = NULL; current != NULL; current = current->next) { if (env_string_equal(current->string, str)) { if (prev) { prev->next = current->next; } else { *list = current->next; } if (do_free) { secure_memzero(current->string, strlen(current->string)); free(current->string); free(current); } return true; } prev = current; } return false; } static void add_env_item(char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc) { struct env_item *item; ASSERT(str); ASSERT(list); ALLOC_OBJ_GC(item, struct env_item, gc); item->string = do_alloc ? string_alloc(str, gc) : str; item->next = *list; *list = item; } /* struct env_set functions */ static bool env_set_del_nolock(struct env_set *es, const char *str) { return remove_env_item(str, es->gc == NULL, &es->list); } static void env_set_add_nolock(struct env_set *es, const char *str) { remove_env_item(str, es->gc == NULL, &es->list); add_env_item((char *)str, true, &es->list, es->gc); } struct env_set * env_set_create(struct gc_arena *gc) { struct env_set *es; ALLOC_OBJ_CLEAR_GC(es, struct env_set, gc); es->list = NULL; es->gc = gc; return es; } void env_set_destroy(struct env_set *es) { if (es && es->gc == NULL) { struct env_item *e = es->list; while (e) { struct env_item *next = e->next; free(e->string); free(e); e = next; } free(es); } } bool env_set_del(struct env_set *es, const char *str) { bool ret; ASSERT(es); ASSERT(str); ret = env_set_del_nolock(es, str); return ret; } void env_set_add(struct env_set *es, const char *str) { ASSERT(es); ASSERT(str); env_set_add_nolock(es, str); } const char * env_set_get(const struct env_set *es, const char *name) { const struct env_item *item = es->list; while (item && !env_string_equal(item->string, name)) { item = item->next; } return item ? item->string : NULL; } void env_set_print(int msglevel, const struct env_set *es) { if (check_debug_level(msglevel)) { const struct env_item *e; int i; if (es) { e = es->list; i = 0; while (e) { if (env_safe_to_print(e->string)) { msg(msglevel, "ENV [%d] '%s'", i, e->string); } ++i; e = e->next; } } } } void env_set_inherit(struct env_set *es, const struct env_set *src) { const struct env_item *e; ASSERT(es); if (src) { e = src->list; while (e) { env_set_add_nolock(es, e->string); e = e->next; } } } /* add/modify/delete environmental strings */ void setenv_counter(struct env_set *es, const char *name, counter_type value) { char buf[64]; openvpn_snprintf(buf, sizeof(buf), counter_format, value); setenv_str(es, name, buf); } void setenv_int(struct env_set *es, const char *name, int value) { char buf[64]; openvpn_snprintf(buf, sizeof(buf), "%d", value); setenv_str(es, name, buf); } void setenv_long_long(struct env_set *es, const char *name, long long value) { char buf[64]; openvpn_snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)value); setenv_str(es, name, buf); } void setenv_str(struct env_set *es, const char *name, const char *value) { setenv_str_ex(es, name, value, CC_NAME, 0, 0, CC_PRINT, 0, 0); } void setenv_str_safe(struct env_set *es, const char *name, const char *value) { uint8_t b[64]; struct buffer buf; buf_set_write(&buf, b, sizeof(b)); if (buf_printf(&buf, "OPENVPN_%s", name)) { setenv_str(es, BSTR(&buf), value); } else { msg(M_WARN, "setenv_str_safe: name overflow"); } } void setenv_str_incr(struct env_set *es, const char *name, const char *value) { unsigned int counter = 1; const size_t tmpname_len = strlen(name) + 5; /* 3 digits counter max */ char *tmpname = gc_malloc(tmpname_len, true, NULL); strcpy(tmpname, name); while (NULL != env_set_get(es, tmpname) && counter < 1000) { ASSERT(openvpn_snprintf(tmpname, tmpname_len, "%s_%u", name, counter)); counter++; } if (counter < 1000) { setenv_str(es, tmpname, value); } else { msg(D_TLS_DEBUG_MED, "Too many same-name env variables, ignoring: %s", name); } free(tmpname); } void setenv_del(struct env_set *es, const char *name) { ASSERT(name); setenv_str(es, name, NULL); } void setenv_str_ex(struct env_set *es, const char *name, const char *value, const unsigned int name_include, const unsigned int name_exclude, const char name_replace, const unsigned int value_include, const unsigned int value_exclude, const char value_replace) { struct gc_arena gc = gc_new(); const char *name_tmp; const char *val_tmp = NULL; ASSERT(name && strlen(name) > 1); name_tmp = string_mod_const(name, name_include, name_exclude, name_replace, &gc); if (value) { val_tmp = string_mod_const(value, value_include, value_exclude, value_replace, &gc); } ASSERT(es); if (val_tmp) { const char *str = construct_name_value(name_tmp, val_tmp, &gc); env_set_add(es, str); #if DEBUG_VERBOSE_SETENV msg(M_INFO, "SETENV_ES '%s'", str); #endif } else { env_set_del(es, name_tmp); } gc_free(&gc); } /* * Setenv functions that append an integer index to the name */ static const char * setenv_format_indexed_name(const char *name, const int i, struct gc_arena *gc) { struct buffer out = alloc_buf_gc(strlen(name) + 16, gc); if (i >= 0) { buf_printf(&out, "%s_%d", name, i); } else { buf_printf(&out, "%s", name); } return BSTR(&out); } void setenv_int_i(struct env_set *es, const char *name, const int value, const int i) { struct gc_arena gc = gc_new(); const char *name_str = setenv_format_indexed_name(name, i, &gc); setenv_int(es, name_str, value); gc_free(&gc); } void setenv_str_i(struct env_set *es, const char *name, const char *value, const int i) { struct gc_arena gc = gc_new(); const char *name_str = setenv_format_indexed_name(name, i, &gc); setenv_str(es, name_str, value); gc_free(&gc); } bool env_allowed(const char *str) { return (script_security() >= SSEC_PW_ENV || !is_password_env_var(str)); } /* Make arrays of strings */ const char ** make_env_array(const struct env_set *es, const bool check_allowed, struct gc_arena *gc) { char **ret = NULL; struct env_item *e = NULL; int i = 0, n = 0; /* figure length of es */ if (es) { for (e = es->list; e != NULL; e = e->next) { ++n; } } /* alloc return array */ ALLOC_ARRAY_CLEAR_GC(ret, char *, n+1, gc); /* fill return array */ if (es) { i = 0; for (e = es->list; e != NULL; e = e->next) { if (!check_allowed || env_allowed(e->string)) { ASSERT(i < n); ret[i++] = e->string; } } } ret[i] = NULL; return (const char **)ret; }