-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathftp_probe.go
256 lines (218 loc) · 5.92 KB
/
ftp_probe.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
// FTP Tester
//
// The FTP tester allows you to make a connection to an FTP-server,
// and optionally retrieve a file.
//
// A basic test can be invoked via input like so:
//
// host.example.com must run ftp [with port 21]
//
// A more complex test would involve actually retrieving a file. To make
// the test-definition natural you do this by specifying an URI:
//
// ftp://ftp.cpan.org/pub/gnu/=README must run ftp
//
// Downloading a file requires a login, so by default we'll try an anonymous
// one. If you need to specify real credentials you can do so by adding
// the appropriate username & password:
//
// ftp://ftp.example.com/path/to/README must run ftp with username 'user@host.com' with password 'secret'
//
// Of course the URI could also be used to specify the login details:
//
// ftp://user@example.com:secret@ftp.cpan.org/pub/gnu/=README must run ftp
//
// To ensure that the remote-file contains content you expect you can
// also verify a specific string is included within the response, via the
// "content" parameter:
//
// ftp://ftp.example.com/path/to/README.md must run ftp with content '2018'
package protocols
import (
"fmt"
"io/ioutil"
"net/url"
"strconv"
"strings"
"github.com/jlaffaye/ftp"
"github.com/skx/overseer/test"
)
// FTPTest is our object.
type FTPTest struct {
}
// Arguments returns the names of arguments which this protocol-test
// understands, along with corresponding regular-expressions to validate
// their values.
func (s *FTPTest) Arguments() map[string]string {
known := map[string]string{
"content": ".*",
"password": ".*",
"port": "^[0-9]+$",
"username": ".*",
}
return known
}
// Example returns sample usage-instructions for self-documentation purposes.
func (s *FTPTest) Example() string {
str := `
FTP Tester
----------
The FTP tester allows you to make a connection to an FTP-server,
and optionally retrieve a file.
A basic test can be invoked via input like so:
host.example.com must run ftp [with port 21]
A more complex test would involve actually retrieving a file. To make
the test-definition natural you do this by specifying an URI:
ftp://ftp.cpan.org/pub/gnu/=README must run ftp
Downloading a file requires a login, so by default we'll try an anonymous
one. If you need to specify real credentials you can do so by adding
the appropriate username & password:
ftp://ftp.example.com/path/to/README must run ftp with username 'user@host.com' with password 'secret'
Of course the URI could also be used to specify the login details:
ftp://user@example.com:secret@ftp.cpan.org/pub/gnu/=README must run ftp
To ensure that the remote-file contains content you expect you can
also verify a specific string is included within the response, via the
"content" parameter:
ftp://ftp.example.com/path/to/README.md must run ftp with content '2018'
`
return str
}
// RunTest is the part of our API which is invoked to actually execute a
// test against the given target.
//
// In this case we make a TCP connection, defaulting to port 21, and
// look for a response which appears to be an FTP-server.
func (s *FTPTest) RunTest(tst test.Test, target string, opts test.Options) error {
//
// Holder for any error we might encounter.
//
var err error
//
// The default port to connect to.
//
port := 21
//
// Our default credentials
//
username := "anonymous"
password := "overseer@example.com"
//
// The target-file we're going to retrieve, if any
//
file := "/"
//
// If we've been given an URI then we should update the
// port if it is non-standard, and possibly retrieve an
// actual file too.
//
if strings.Contains(tst.Target, "://") {
// Parse the URI.
var u *url.URL
u, err = url.Parse(tst.Target)
if err != nil {
return err
}
// Record the path to fetch.
file = u.Path
// Update the default port, if a port-number was given.
if u.Port() != "" {
port, err = strconv.Atoi(u.Port())
if err != nil {
return err
}
}
// The URI might contain username/password
if u.User.Username() != "" {
username = u.User.Username()
p, _ := u.User.Password()
if p != "" {
password = p
}
}
}
fmt.Printf("Username: %s -> %s\n", username, password)
//
// If the user specified a different port update to use it.
//
// Do this after the URI-parsing.
//
if tst.Arguments["port"] != "" {
port, err = strconv.Atoi(tst.Arguments["port"])
if err != nil {
return err
}
}
//
// Default to connecting to an IPv4-address
//
address := fmt.Sprintf("%s:%d", target, port)
//
// If we find a ":" we know it is an IPv6 address though
//
if strings.Contains(target, ":") {
address = fmt.Sprintf("[%s]:%d", target, port)
}
//
// Make the connection.
//
var conn *ftp.ServerConn
conn, err = ftp.Dial(address, ftp.DialWithTimeout(opts.Timeout))
if err != nil {
return err
}
defer conn.Quit()
//
// If the user specified different/real credentials, use them instead.
//
if tst.Arguments["username"] != "" {
username = tst.Arguments["username"]
}
if tst.Arguments["password"] != "" {
password = tst.Arguments["password"]
}
//
// If we have been given a path/file to fetch, via an URI
// input, then fetch it.
//
// Before attempting the fetch login.
//
if file != "/" {
//
// Login
//
err = conn.Login(username, password)
if err != nil {
return err
}
//
// Retrieve the file.
//
resp, err := conn.Retr(file)
if err != nil {
return err
}
defer resp.Close()
//
// Actually fetch the contents of the file.
//
buf, err := ioutil.ReadAll(resp)
if err != nil {
return err
}
//
// If we're doing a content-match then do that here
//
if tst.Arguments["content"] != "" {
if !strings.Contains(string(buf), tst.Arguments["content"]) {
return fmt.Errorf("body didn't contain '%s'", tst.Arguments["content"])
}
}
}
return nil
}
// Register our protocol-tester.
func init() {
Register("ftp", func() ProtocolTest {
return &FTPTest{}
})
}