forked from lnxjedi/gopherbot
/
callplugin.go
213 lines (208 loc) · 7.61 KB
/
callplugin.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
package bot
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
)
// Windows argument parsing is all over the map; try to fix it here
// Currently powershell only
func fixInterpreterArgs(interpreter string, args []string) []string {
ire := regexp.MustCompile(`.*[\/\\!](.*)`)
var i string
imatch := ire.FindStringSubmatch(interpreter)
if len(imatch) == 0 {
i = interpreter
} else {
i = imatch[1]
}
switch i {
case "powershell", "powershell.exe":
for i, _ := range args {
args[i] = strings.Replace(args[i], " ", "` ", -1)
args[i] = strings.Replace(args[i], ",", "`,", -1)
args[i] = strings.Replace(args[i], ";", "`;", -1)
if args[i] == "" {
args[i] = "\"\""
}
}
}
return args
}
// emulate Unix script convention by calling external scripts with
// an interpreter.
func getInterpreter(scriptPath string) (string, error) {
script, err := os.Open(scriptPath)
if err != nil {
err = fmt.Errorf("opening file: %s", err)
Log(Error, fmt.Sprintf("Problem getting interpreter for %s: %s", scriptPath, err))
return "", err
}
r := bufio.NewReader(script)
iline, err := r.ReadString('\n')
if err != nil {
err = fmt.Errorf("reading first line: %s", err)
Log(Error, fmt.Sprintf("Problem getting interpreter for %s: %s", scriptPath, err))
return "", err
}
if !strings.HasPrefix(iline, "#!") {
err := fmt.Errorf("Problem getting interpreter for %s; first line doesn't start with \"#!\"", scriptPath)
Log(Error, err)
return "", err
}
iline = strings.TrimRight(iline, "\n\r")
interpreter := strings.TrimPrefix(iline, "#!")
Log(Debug, fmt.Sprintf("Detected interpreter for %s: %s", scriptPath, interpreter))
return interpreter, nil
}
func getExtDefCfg(plugin *Plugin) (*[]byte, error) {
var fullPath string
if byte(plugin.pluginPath[0]) == byte("/"[0]) {
fullPath = plugin.pluginPath
} else {
_, err := os.Stat(robot.localPath + "/" + plugin.pluginPath)
if err != nil {
_, err := os.Stat(robot.installPath + "/" + plugin.pluginPath)
if err != nil {
err = fmt.Errorf("Couldn't locate external plugin %s: %v", plugin.name, err)
return nil, err
}
fullPath = robot.installPath + "/" + plugin.pluginPath
Log(Debug, "Using stock external plugin:", fullPath)
} else {
fullPath = robot.localPath + "/" + plugin.pluginPath
Log(Debug, "Using local external plugin:", fullPath)
}
}
var cfg []byte
var err error
if runtime.GOOS == "windows" {
var interpreter string
interpreter, err = getInterpreter(fullPath)
if err != nil {
err = fmt.Errorf("looking up interpreter for %s: %s", fullPath, err)
return nil, err
}
args := fixInterpreterArgs(interpreter, []string{fullPath, "configure"})
Log(Debug, fmt.Sprintf("Calling \"%s\" with args: %q", interpreter, args))
cfg, err = exec.Command(interpreter, args...).Output()
} else {
Log(Debug, fmt.Sprintf("Calling \"%s\" with arg: configure", fullPath))
cfg, err = exec.Command(fullPath, "configure").Output()
}
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("Problem retrieving default configuration for external plugin \"%s\", skipping: \"%v\", output: %s", fullPath, err, exitErr.Stderr)
} else {
err = fmt.Errorf("Problem retrieving default configuration for external plugin \"%s\", skipping: \"%v\"", fullPath, err)
}
return nil, err
}
return &cfg, nil
}
// callPlugin (normally called with go ...) sends a command to a plugin.
func callPlugin(bot *Robot, plugin *Plugin, command string, args ...string) {
pluginsRunning.Lock()
pluginsRunning.count++
pluginsRunning.Unlock()
defer func() {
pluginsRunning.Lock()
pluginsRunning.count--
if pluginsRunning.count >= 0 {
pluginsRunning.Done()
}
pluginsRunning.Unlock()
}()
if !(plugin.name == "builtInadmin" && command == "abort") {
defer checkPanic(bot, fmt.Sprintf("Plugin: %s, command: %s, arguments: %v", plugin.name, command, args))
}
Log(Debug, fmt.Sprintf("Dispatching command \"%s\" to plugin \"%s\" with arguments \"%#v\"", command, plugin.name, args))
bot.pluginID = plugin.pluginID
switch plugin.pluginType {
case plugBuiltin, plugGo:
pluginHandlers[plugin.name].Handler(bot, command, args...)
case plugExternal:
var fullPath string // full path to the executable
if len(plugin.pluginPath) == 0 {
Log(Error, "pluginPath empty for external plugin:", plugin.name)
}
if byte(plugin.pluginPath[0]) == byte("/"[0]) {
fullPath = plugin.pluginPath
} else {
_, err := os.Stat(robot.localPath + "/" + plugin.pluginPath)
if err != nil {
_, err := os.Stat(robot.installPath + "/" + plugin.pluginPath)
if err != nil {
Log(Error, fmt.Errorf("Couldn't locate external plugin %s: %v", plugin.name, err))
return
}
fullPath = robot.installPath + "/" + plugin.pluginPath
Log(Debug, "Using stock external plugin:", fullPath)
} else {
fullPath = robot.localPath + "/" + plugin.pluginPath
Log(Debug, "Using local external plugin:", fullPath)
}
}
interpreter, err := getInterpreter(fullPath)
if err != nil {
err = fmt.Errorf("looking up interpreter for %s: %s", fullPath, err)
Log(Error, fmt.Sprintf("Unable to call external plugin %s, no interpreter found: %s", fullPath, err))
return
}
externalArgs := make([]string, 0, 5+len(args))
// on Windows, we exec the interpreter with the script as first arg
if runtime.GOOS == "windows" {
externalArgs = append(externalArgs, fullPath)
}
externalArgs = append(externalArgs, command)
externalArgs = append(externalArgs, args...)
externalArgs = fixInterpreterArgs(interpreter, externalArgs)
Log(Debug, fmt.Sprintf("Calling \"%s\" with interpreter \"%s\" and args: %q", fullPath, interpreter, externalArgs))
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command(interpreter, externalArgs...)
} else {
cmd = exec.Command(fullPath, externalArgs...)
}
cmd.Env = append(os.Environ(), []string{
fmt.Sprintf("GOPHER_CHANNEL=%s", bot.Channel),
fmt.Sprintf("GOPHER_USER=%s", bot.User),
fmt.Sprintf("GOPHER_PLUGIN_ID=%s", plugin.pluginID),
}...)
// close stdout on the external plugin...
cmd.Stdout = nil
// but hold on to stderr in case we need to log an error
stderr, err := cmd.StderrPipe()
if err != nil {
Log(Error, fmt.Errorf("Creating stderr pipe for external command \"%s\": %v", fullPath, err))
bot.Reply(fmt.Sprintf("There were errors calling external plugin \"%s\", you might want to ask an administrator to check the logs", plugin.name))
return
}
if err = cmd.Start(); err != nil {
Log(Error, fmt.Errorf("Starting command \"%s\": %v", fullPath, err))
bot.Reply(fmt.Sprintf("There were errors calling external plugin \"%s\", you might want to ask an administrator to check the logs", plugin.name))
return
}
defer func() {
if err = cmd.Wait(); err != nil {
Log(Error, fmt.Errorf("Waiting on external command \"%s\": %v", fullPath, err))
bot.Reply(fmt.Sprintf("There were errors calling external plugin \"%s\", you might want to ask an administrator to check the logs", plugin.name))
}
}()
stdErrBytes, err := ioutil.ReadAll(stderr)
if err != nil {
Log(Error, fmt.Errorf("Reading from stderr for external command \"%s\": %v", fullPath, err))
bot.Reply(fmt.Sprintf("There were errors calling external plugin \"%s\", you might want to ask an administrator to check the logs", plugin.name))
return
}
stdErrString := string(stdErrBytes)
if len(stdErrString) > 0 {
Log(Warn, fmt.Errorf("Output from stderr of external command \"%s\": %s", fullPath, stdErrString))
bot.Reply(fmt.Sprintf("There was error output while calling external plugin \"%s\", you might want to ask an administrator to check the logs", plugin.name))
}
}
}