-
Notifications
You must be signed in to change notification settings - Fork 14
/
script.go
210 lines (167 loc) · 4.66 KB
/
script.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
/*
pipethis: Stop piping the internet into your shell
Copyright 2016 Ellotheth
Use of this source code is governed by the GNU Public License version 2
(GPLv2). You should have received a copy of the GPLv2 along with your copy of
the source. If not, see http://www.gnu.org/licenses/gpl-2.0.html.
*/
package main
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/clearsign"
)
// Script represents a shell script to be inspected, verified, and run.
type Script struct {
author string
source string
filename string
clearsigned bool
}
// NewScript copies the shell script specified in location (which may be local
// or remote) to a temporary file and loads it into a Script.
func NewScript(location string) (*Script, error) {
script := &Script{source: location}
body, err := getFile(location)
if err != nil {
return nil, err
}
defer body.Close()
file, err := ioutil.TempFile("", "pipethis-")
if err != nil {
return nil, err
}
defer file.Close()
script.filename = file.Name()
contents, err := ioutil.ReadAll(body)
if err != nil {
return nil, err
}
contents, err = script.detachSignature(contents)
if err != nil {
return nil, err
}
_, err = file.Write(contents)
if err != nil {
return nil, err
}
return script, nil
}
func (s *Script) detachSignature(contents []byte) ([]byte, error) {
block, _ := clearsign.Decode(contents)
// if the signature is not attached, return the contents without
// modification
if block == nil {
return contents, nil
}
s.clearsigned = true
// get the raw script, without the signature or PGP headers, and without
// the CRs
contents = bytes.Replace(block.Bytes, []byte{0x0d}, nil, -1)
// create a file for the armored signature
sig, err := os.Create(s.filename + ".sig")
if err != nil {
return nil, err
}
defer sig.Close()
// write the armored signature
sigWriter, err := armor.Encode(sig, "PGP SIGNATURE", block.ArmoredSignature.Header)
if err != nil {
return nil, err
}
defer sigWriter.Close()
_, err = io.Copy(sigWriter, block.ArmoredSignature.Body)
if err != nil {
return nil, err
}
return contents, nil
}
// IsPiped is true when the script was read from STDIN (so the source location
// is empty)
func (s Script) IsPiped() bool {
return s.source == ""
}
// Name is the name of the temporary file holding the shell script.
func (s Script) Name() string {
return s.filename
}
// Source is the original location of the shell script (local or remote).
func (s Script) Source() string {
return s.source
}
// Body opens Script.Name() for reading.
func (s Script) Body() (ReadSeekCloser, error) {
return os.Open(s.Name())
}
// Author parses Script.Body() for the PIPETHIS_AUTHOR token, and saves it if
// it's found.
func (s *Script) Author() (string, error) {
if s.author != "" {
return s.author, nil
}
file, err := s.Body()
if err != nil {
return "", err
}
defer file.Close()
if author := parseToken(`.*PIPETHIS_AUTHOR\s+(\w+)`, file); author != "" {
s.author = author
return s.author, nil
}
return "", errors.New("Author not found")
}
// Run creates a new process, running Script.Name() with target and any
// additional arguments from the command line. It returns the result of the
// process.
func (s Script) Run(target string, args ...string) error {
log.Println("Running", s.Name(), "with", target)
// the first argument is the script source location. replace it with the
// temporary filename.
args[0] = s.Name()
cmd := exec.Command(target, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Echo prints the contents of the script to STDOUT
func (s Script) Echo() error {
log.Println("Sending", s.Name(), "to STDOUT for more processing")
body, err := s.Body()
if err != nil {
return err
}
defer body.Close()
_, err = io.Copy(os.Stdout, body)
return err
}
// Inspect checks whether an inspection was requested, and sends Script.Name()
// to editor if so. When editor exits, Inspect prompts the user to continue
// processing, and returns true to continue or false to stop.
func (s Script) Inspect(inspect bool, editor string) bool {
if !inspect || s.IsPiped() {
return true
}
log.Println("Opening", s.Name(), "in", editor)
cmd := exec.Command(editor, s.Name())
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Run()
runScript := "y"
fmt.Print("Continue processing ", s.Name(), "? (Y/n) ")
fmt.Scanf("%s", &runScript)
return strings.ToLower(runScript) == "y"
}
// IsClearsigned returns true if the script and signature are attached,
// and false otherwise.
func (s Script) IsClearsigned() bool {
return s.clearsigned
}