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