github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/osutil/env.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil 21 22 import ( 23 "fmt" 24 "os" 25 "sort" 26 "strconv" 27 "strings" 28 29 "github.com/snapcore/snapd/strutil" 30 ) 31 32 // GetenvBool returns whether the given key may be considered "set" in the 33 // environment (i.e. it is set to one of "1", "true", etc). 34 // 35 // An optional second argument can be provided, which determines how to 36 // treat missing or unparsable values; default is to treat them as false. 37 func GetenvBool(key string, dflt ...bool) bool { 38 if val := strings.TrimSpace(os.Getenv(key)); val != "" { 39 if b, err := strconv.ParseBool(val); err == nil { 40 return b 41 } 42 } 43 44 if len(dflt) > 0 { 45 return dflt[0] 46 } 47 48 return false 49 } 50 51 // GetenvInt64 interprets the value of the given environment variable 52 // as an int64 and returns the corresponding value. The base can be 53 // implied via the prefix (0x for 16, 0 for 8; otherwise 10). 54 // 55 // An optional second argument can be provided, which determines how to 56 // treat missing or unparsable values; default is to treat them as 0. 57 func GetenvInt64(key string, dflt ...int64) int64 { 58 if val := strings.TrimSpace(os.Getenv(key)); val != "" { 59 if b, err := strconv.ParseInt(val, 0, 64); err == nil { 60 return b 61 } 62 } 63 64 if len(dflt) > 0 { 65 return dflt[0] 66 } 67 68 return 0 69 } 70 71 // Environment is an unordered map of key=value strings. 72 // 73 // Environment can be manipulated with available methods and eventually 74 // converted to low-level representation necessary when executing programs. 75 // This approach discourages operations that could result in duplicate 76 // environment variable definitions from being constructed. 77 type Environment map[string]string 78 79 func parseEnvEntry(entry string) (string, string, error) { 80 parts := strings.SplitN(entry, "=", 2) 81 if len(parts) != 2 { 82 return "", "", fmt.Errorf("cannot parse environment entry: %q", entry) 83 } 84 key, value := parts[0], parts[1] 85 if key == "" { 86 return "", "", fmt.Errorf("environment variable name cannot be empty: %q", entry) 87 } 88 return key, value, nil 89 } 90 91 // parseRawEnvironment parsers raw environment. 92 // 93 // This function fails if any of the provided values are not in the form of 94 // key=value or if there are duplicate keys. 95 func parseRawEnvironment(raw []string) (Environment, error) { 96 env := make(Environment, len(raw)) 97 for _, entry := range raw { 98 key, value, err := parseEnvEntry(entry) 99 if err != nil { 100 return nil, err 101 } 102 if _, ok := env[key]; ok { 103 return nil, fmt.Errorf("cannot overwrite earlier value of %q", key) 104 } 105 env[key] = value 106 } 107 return env, nil 108 } 109 110 // OSEnvironment returns the environment of the calling process. 111 func OSEnvironment() (Environment, error) { 112 return parseRawEnvironment(os.Environ()) 113 } 114 115 // OSEnvironmentUnescapeUnsafe returns the environment of the calling process. 116 // It will also strip unsafeEscapePrefix from any variable starting with it. 117 // Use-case/assumption is that ForExecEscapeUnsafe was used previously 118 // along the exec chain. 119 func OSEnvironmentUnescapeUnsafe(unsafeEscapePrefix string) (Environment, error) { 120 env, err := parseRawEnvironment(os.Environ()) 121 if err != nil { 122 return nil, err 123 } 124 125 for key, value := range env { 126 if newKey := strings.TrimPrefix(key, unsafeEscapePrefix); key != newKey { 127 delete(env, key) 128 if _, ok := env[newKey]; ok { 129 // assume newKey was originally 130 // dropped when the escaped key and 131 // value were set so current value is 132 // newer here, keep it 133 continue 134 } 135 env[newKey] = value 136 } 137 } 138 return env, nil 139 } 140 141 // ForExec returns the environment in a form suitable for using with 142 // the exec family of functions. 143 // 144 // The returned environment is sorted lexicographically by variable name. 145 func (env Environment) ForExec() []string { 146 raw := make([]string, 0, len(env)) 147 keys := make([]string, 0, len(env)) 148 for key := range env { 149 keys = append(keys, key) 150 } 151 sort.Strings(keys) 152 for _, key := range keys { 153 raw = append(raw, fmt.Sprintf("%s=%s", key, env[key])) 154 } 155 return raw 156 } 157 158 // ForExecEscapeUnsafe returns the environment in a form suitable for 159 // using with the exec family of functions. 160 // 161 // Further variables that are usually stripped out by ld.so when starting a 162 // setuid process are renamed by prepending unsafeEscapePrefix to 163 // them. 164 // 165 // Unlikely variables already starting with the prefix will be dropped, 166 // they would be mishandled down chain. 167 // 168 // The returned environment is sorted lexicographically by final variable name. 169 func (env Environment) ForExecEscapeUnsafe(unsafeEscapePrefix string) []string { 170 raw := make([]string, 0, len(env)) 171 keys := make([]string, 0, len(env)) 172 escaped := 0 173 for key := range env { 174 if strings.HasPrefix(key, unsafeEscapePrefix) { 175 continue 176 } 177 if unsafeEnv[key] { 178 key = unsafeEscapePrefix + key 179 escaped += 1 180 } 181 keys = append(keys, key) 182 } 183 sort.Strings(keys) 184 var firstEscaped int 185 if escaped > 0 { 186 firstEscaped = sort.SearchStrings(keys, unsafeEscapePrefix) 187 } 188 for i, key := range keys { 189 envKey := key 190 if i >= firstEscaped && i < (firstEscaped+escaped) { 191 envKey = key[len(unsafeEscapePrefix):] 192 } 193 raw = append(raw, fmt.Sprintf("%s=%s", key, env[envKey])) 194 } 195 return raw 196 } 197 198 // ExpandableEnv represents alterations to an environment as ordered 199 // key, value entries. 200 // 201 // Values can refer to predefined entries by using shell-like 202 // syntax $KEY or ${KEY}. 203 type ExpandableEnv struct { 204 *strutil.OrderedMap 205 } 206 207 // NewExpandableEnv returns a new expandable environment comprised of given pairs. 208 func NewExpandableEnv(pairs ...string) ExpandableEnv { 209 return ExpandableEnv{OrderedMap: strutil.NewOrderedMap(pairs...)} 210 } 211 212 // ExtendWithExpanded extends the environment with eenv. 213 // 214 // Environment is modified in place. Each variable defined by eenv is 215 // expanded according to os.Expand, using the environment itself as it 216 // gets extended. Undefined variables expand to an empty string. 217 func (env *Environment) ExtendWithExpanded(eenv ExpandableEnv) { 218 if *env == nil { 219 *env = make(Environment) 220 } 221 222 for _, key := range eenv.Keys() { 223 (*env)[key] = os.Expand(eenv.Get(key), func(varName string) string { 224 return (*env)[varName] 225 }) 226 } 227 } 228 229 // unsafeEnv is a set of unsafe environment variables. 230 // 231 // Environment variables glibc strips out when running a setuid binary. 232 // Taken from https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/generic/unsecvars.h;hb=HEAD 233 // TODO: use go generate to obtain this list at build time. 234 var unsafeEnv = map[string]bool{ 235 "GCONV_PATH": true, 236 "GETCONF_DIR": true, 237 "GLIBC_TUNABLES": true, 238 "HOSTALIASES": true, 239 "LD_AUDIT": true, 240 "LD_DEBUG": true, 241 "LD_DEBUG_OUTPUT": true, 242 "LD_DYNAMIC_WEAK": true, 243 "LD_HWCAP_MASK": true, 244 "LD_LIBRARY_PATH": true, 245 "LD_ORIGIN_PATH": true, 246 "LD_PRELOAD": true, 247 "LD_PROFILE": true, 248 "LD_SHOW_AUXV": true, 249 "LD_USE_LOAD_BIAS": true, 250 "LOCALDOMAIN": true, 251 "LOCPATH": true, 252 "MALLOC_TRACE": true, 253 "NIS_PATH": true, 254 "NLSPATH": true, 255 "RESOLV_HOST_CONF": true, 256 "RES_OPTIONS": true, 257 "TMPDIR": true, 258 "TZDIR": true, 259 }