-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathelevation.go
222 lines (200 loc) · 6.75 KB
/
elevation.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
package cmd_toolkit
import (
"fmt"
"github.com/majohn-r/output"
"github.com/mattn/go-isatty"
"golang.org/x/sys/windows"
"os"
"strconv"
"strings"
"syscall"
)
// the vars declared below exist to make it possible for unit tests to thoroughly exercise the
// functionality in this file
var (
// IsCygwinTerminal determines whether a particular file descriptor (e.g., os.Stdin.Fd()) is a
// Cygwin terminal
IsCygwinTerminal = isatty.IsCygwinTerminal
// IsTerminal determines whether a particular file descriptor (e.g., os.Stdin.Fd()) is a
// terminal
IsTerminal = isatty.IsTerminal
// GetCurrentProcessToken gets the windows token representing the current process
GetCurrentProcessToken = windows.GetCurrentProcessToken
// ShellExecute is the windows function that runs the specified process with elevated
// privileges
ShellExecute = windows.ShellExecute
// IsElevated determines whether a specified windows token represents a process running with
// elevated privileges
IsElevated = windows.Token.IsElevated
)
// ElevationControl defines behavior for code pertaining to running a process with elevated
// privileges
type ElevationControl interface {
// Log logs the elevationControl state
Log(output.Bus, output.Level)
// Status returns a slice of status data suitable to display to the user
Status(string) []string
// WillRunElevated checks whether the process can run with elevated privileges, and if so,
// attempts to do so
WillRunElevated() bool
}
type elevationControl struct {
adminPermitted bool
elevated bool
envVarName string
stderrRedirected bool
stdinRedirected bool
stdoutRedirected bool
}
// NewElevationControl creates a new instance of elevationControl that does not use an environment variable to
// determine whether execution with elevated privileges is desired
func NewElevationControl() ElevationControl {
return &elevationControl{
adminPermitted: true,
elevated: ProcessIsElevated(),
envVarName: "",
stderrRedirected: stderrState(),
stdinRedirected: stdinState(),
stdoutRedirected: stdoutState(),
}
}
// NewElevationControlWithEnvVar creates a new instance of elevationControl that uses an
// environment variable to determine whether execution with elevated privileges is desired
func NewElevationControlWithEnvVar(envVarName string, defaultEnvVarValue bool) ElevationControl {
return &elevationControl{
adminPermitted: environmentPermits(envVarName, defaultEnvVarValue),
elevated: ProcessIsElevated(),
envVarName: envVarName,
stderrRedirected: stderrState(),
stdinRedirected: stdinState(),
stdoutRedirected: stdoutState(),
}
}
// Log is the reference implementation of the ElevationControl function
func (ec *elevationControl) Log(o output.Bus, level output.Level) {
o.Log(level, "elevation state", map[string]any{
"elevated": ec.elevated,
"admin_permission": ec.adminPermitted,
"stderr_redirected": ec.stderrRedirected,
"stdin_redirected": ec.stdinRedirected,
"stdout_redirected": ec.stdoutRedirected,
"environment_variable": ec.envVarName,
})
}
// Status is the reference implementation of the ElevationControl function
func (ec *elevationControl) Status(appName string) []string {
results := make([]string, 0, 3)
if ec.elevated {
results = append(results, fmt.Sprintf("%s is running with elevated privileges", appName))
return results
}
results = append(results, fmt.Sprintf("%s is not running with elevated privileges", appName))
if ec.redirected() {
results = append(results, ec.describeRedirection())
}
if !ec.adminPermitted {
results = append(results, fmt.Sprintf("The environment variable %s evaluates as false", ec.envVarName))
}
return results
}
// WillRunElevated is the reference implementation of the ElevationControl function
func (ec *elevationControl) WillRunElevated() bool {
if ec.canElevate() {
// https://github.com/majohn-r/mp3repair/issues/157 if privileges can be
// elevated successfully, return true, else assume user declined and
// return false.
return runElevated()
}
return false
}
func (ec *elevationControl) canElevate() bool {
if ec.elevated {
return false // already there, so, no
}
if ec.redirected() {
return false // redirection will be lost, so, no
}
return ec.adminPermitted // do what the environment variable says
}
func (ec *elevationControl) describeRedirection() string {
redirectedIO := make([]string, 0, 3)
if ec.stderrRedirected {
redirectedIO = append(redirectedIO, "stderr")
}
if ec.stdinRedirected {
redirectedIO = append(redirectedIO, "stdin")
}
if ec.stdoutRedirected {
redirectedIO = append(redirectedIO, "stdout")
}
result := ""
switch len(redirectedIO) {
case 1:
result = fmt.Sprintf("%s has been redirected", redirectedIO[0])
case 2:
result = fmt.Sprintf("%s have been redirected", strings.Join(redirectedIO, " and "))
case 3:
result = "stderr, stdin, and stdout have been redirected"
}
return result
}
func (ec *elevationControl) redirected() bool {
return ec.stderrRedirected || ec.stdinRedirected || ec.stdoutRedirected
}
func environmentPermits(varName string, defaultValue bool) bool {
if value, varDefined := os.LookupEnv(varName); varDefined {
// interpret value as bool
boolValue, parseErr := strconv.ParseBool(value)
if parseErr == nil {
return boolValue
}
fmt.Fprintf(os.Stderr, "The value %q of environment variable %q is neither true nor false\n", value, varName)
}
return defaultValue
}
func mergeArguments(args []string) string {
merged := ""
if len(args) > 1 {
merged = strings.Join(args[1:], " ")
}
return merged
}
// ProcessIsElevated determines whether the current process is running with elevated privileges
func ProcessIsElevated() bool {
t := GetCurrentProcessToken()
return IsElevated(t)
}
func redirectedDescriptor(fd uintptr) bool {
if !IsTerminal(fd) && !IsCygwinTerminal(fd) {
return true
}
return false
}
// credit: https://gist.github.com/jerblack/d0eb182cc5a1c1d92d92a4c4fcc416c6
func runElevated() (status bool) {
verb := "runas"
exe, _ := os.Executable()
cwd, _ := os.Getwd()
args := mergeArguments(os.Args)
verbPtr, _ := syscall.UTF16PtrFromString(verb)
exePtr, _ := syscall.UTF16PtrFromString(exe)
cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
argPtr, _ := syscall.UTF16PtrFromString(args)
var showCmd int32 = syscall.SW_NORMAL
// https://github.com/majohn-r/mp3repair/issues/157 if ShellExecute returns
// no error, assume the user accepted admin privileges and return true
// status
if refusedErr := ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd); refusedErr == nil {
status = true
}
return
}
func stderrState() bool {
return redirectedDescriptor(os.Stderr.Fd())
}
func stdinState() bool {
return redirectedDescriptor(os.Stdin.Fd())
}
func stdoutState() bool {
return redirectedDescriptor(os.Stdout.Fd())
}