-
Notifications
You must be signed in to change notification settings - Fork 19
/
testutil.go
332 lines (284 loc) · 9.02 KB
/
testutil.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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
package sshego
import (
"context"
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"strings"
"time"
ssh "github.com/glycerine/sshego/xendor/github.com/glycerine/xcryptossh"
)
type TestSetup struct {
CliCfg *SshegoConfig
SrvCfg *SshegoConfig
Mylogin string
RsaPath string
Totp string
Pw string
}
func GenTestConfig() (c *SshegoConfig, releasePorts func()) {
cfg := NewSshegoConfig()
cfg.Origdir, cfg.Tempdir = MakeAndMoveToTempDir() // cd to tempdir
// copy in a 3 host fake known hosts
err := exec.Command("cp", "-rp", cfg.Origdir+"/testdata", cfg.Tempdir+"/").Run()
panicOn(err)
cfg.ClientKnownHostsPath = cfg.Tempdir + "/testdata/fake_known_hosts_without_b"
// poll until the copy has actually finished
tries := 40
pause := 1e0 * time.Millisecond
found := false
i := 0
for ; i < tries; i++ {
if fileExists(cfg.ClientKnownHostsPath) {
found = true
break
}
time.Sleep(pause)
}
if !found {
panic(fmt.Sprintf("could not locate copied file '%s' after %v tries with %v sleep between each try.", cfg.ClientKnownHostsPath, tries, pause))
}
pp("good: we found '%s' after %v sleeps", cfg.ClientKnownHostsPath, i)
cfg.BitLenRSAkeys = 1024 // faster for testing
cfg.KnownHosts, err = NewKnownHosts(cfg.ClientKnownHostsPath, KHSsh)
panicOn(err)
//old: cfg.ClientKnownHostsPath = cfg.Tempdir + "/client_known_hosts"
// get a bunch of distinct ports, all different.
sshdLsn, sshdLsnPort := GetAvailPort() // sshd local listen
sshdTargetLsn, sshdTargetLsnPort := GetAvailPort() // target for client, sshd
xportLsn, xport := GetAvailPort() // xport
fwdStartLsn, fwdStartLsnPort := GetAvailPort() // fwdStart
fwdTargetLsn, fwdTargetLsnPort := GetAvailPort() // fwdTarget
revStartLsn, revStartLsnPort := GetAvailPort() // revStart
revTargetLsn, revTargetLsnPort := GetAvailPort() // revTarget
// racy, but rare: somebody else could grab this port
// after our Close() and before we can grab it again.
// Meh. Built into the way unix works. As long
// as we aren't testing on an overloaded super
// busy network box, it should be fine.
releasePorts = func() {
sshdLsn.Close()
sshdTargetLsn.Close()
xportLsn.Close()
fwdStartLsn.Close()
fwdTargetLsn.Close()
revStartLsn.Close()
revTargetLsn.Close()
}
cfg.SshegoSystemMutexPort = xport
cfg.EmbeddedSSHd.Title = "esshd"
cfg.EmbeddedSSHd.Addr = fmt.Sprintf("127.0.0.1:%v", sshdLsnPort)
cfg.EmbeddedSSHd.ParseAddr()
cfg.LocalToRemote.Listen.Title = "fwd-start"
cfg.LocalToRemote.Listen.Addr = fmt.Sprintf("127.0.0.1:%v", fwdStartLsnPort)
cfg.LocalToRemote.Listen.ParseAddr()
cfg.LocalToRemote.Remote.Title = "fwd-target"
cfg.LocalToRemote.Remote.Addr = fmt.Sprintf("127.0.0.1:%v", fwdTargetLsnPort)
cfg.LocalToRemote.Remote.ParseAddr()
cfg.RemoteToLocal.Listen.Title = "rev-start"
cfg.RemoteToLocal.Listen.Addr = fmt.Sprintf("127.0.0.1:%v", revStartLsnPort)
cfg.RemoteToLocal.Listen.ParseAddr()
cfg.RemoteToLocal.Remote.Title = "rev-target"
cfg.RemoteToLocal.Remote.Addr = fmt.Sprintf("127.0.0.1:%v", revTargetLsnPort)
cfg.RemoteToLocal.Remote.ParseAddr()
cfg.EmbeddedSSHdHostDbPath = cfg.Tempdir + "/server_hostdb"
// temp, let compile
_, _ = sshdLsn, sshdLsnPort
_, _ = sshdTargetLsn, sshdTargetLsnPort
_, _ = xportLsn, xport
_, _ = fwdStartLsn, fwdStartLsnPort
_, _ = fwdTargetLsn, fwdTargetLsnPort
_, _ = revStartLsn, revStartLsnPort
_, _ = revTargetLsn, revTargetLsnPort
return cfg, releasePorts
}
func MakeAndMoveToTempDir() (origdir string, tmpdir string) {
// make new temp dir
var err error
origdir, err = os.Getwd()
if err != nil {
panic(err)
}
tmpdir, err = ioutil.TempDir(origdir, "temp.sshego.test.dir")
if err != nil {
panic(err)
}
err = os.Chdir(tmpdir)
if err != nil {
panic(err)
}
return origdir, tmpdir
}
func TempDirCleanup(origdir string, tmpdir string) {
// cleanup
os.Chdir(origdir)
err := os.RemoveAll(tmpdir)
if err != nil {
panic(err)
}
fmt.Printf("\n TempDirCleanup of '%s' done.\n", tmpdir)
}
// GetAvailPort asks the OS for an unused port,
// returning a bound net.Listener and the port number
// to which it is bound. The caller should
// Close() the listener when it is done with
// the port.
func GetAvailPort() (net.Listener, int) {
lsn, _ := net.Listen("tcp", ":0")
r := lsn.Addr()
return lsn, r.(*net.TCPAddr).Port
}
// waitUntilAddrAvailable returns -1 if the addr was
// always unavailable after tries sleeps of dur time.
// Otherwise it returns the number of tries it took.
// Between attempts we wait 'dur' time before trying
// again.
func WaitUntilAddrAvailable(addr string, dur time.Duration, tries int) int {
for i := 0; i < tries; i++ {
var isbound bool
isbound = IsAlreadyBound(addr)
if isbound {
time.Sleep(dur)
} else {
fmt.Printf("\n took %v %v sleeps for address '%v' to become available.\n", i, dur, addr)
return i
}
}
return -1
}
func IsAlreadyBound(addr string) bool {
ln, err := net.Listen("tcp", addr)
if err != nil {
return true
}
ln.Close()
return false
}
func VerifyClientServerExchangeAcrossSshd(channelToTcpServer net.Conn, confirmationPayload, confirmationReply string, payloadByteCount int) {
m, err := channelToTcpServer.Write([]byte(confirmationPayload))
panicOn(err)
if m != len(confirmationPayload) {
panic("too short a write!")
}
// check reply
rep := make([]byte, payloadByteCount)
m, err = channelToTcpServer.Read(rep)
panicOn(err)
if m != payloadByteCount {
panic(fmt.Sprintf("too short a reply! m = %v, expected %v. rep = '%v'", m, payloadByteCount, string(rep)))
}
srep := string(rep)
if srep != confirmationReply {
panic(fmt.Errorf("saw '%s' but expected '%s'", srep, confirmationReply))
}
pp("reply success! we got the expected srep reply '%s'", srep)
}
func StartBackgroundTestTcpServer(mgr *ssh.Halter, payloadByteCount int, confirmationPayload string, confirmationReply string, tcpSrvLsn net.Listener, pnc *net.Conn) {
go func() {
pp("startBackgroundTestTcpServer() about to call Accept().")
tcpServerConn, err := tcpSrvLsn.Accept()
if pnc != nil {
*pnc = tcpServerConn
}
panicOn(err)
mgr.MarkReady()
pp("startBackgroundTestTcpServer() progress: got Accept() back: %v. LocalAddr='%v'. RemoteAddr='%v'",
tcpServerConn, tcpServerConn.LocalAddr(), tcpServerConn.RemoteAddr())
b := make([]byte, payloadByteCount)
n, err := tcpServerConn.Read(b)
panicOn(err)
if n != payloadByteCount {
panic(fmt.Errorf("read too short! got %v but expected %v: '%s'", n, payloadByteCount, string(b)))
}
saw := string(b)
if saw != confirmationPayload {
panic(fmt.Errorf("expected '%s', but saw '%s'", confirmationPayload, saw))
}
pp("success! server got expected confirmation payload of '%s'", saw)
// reply back
n, err = tcpServerConn.Write([]byte(confirmationReply))
panicOn(err)
if n != payloadByteCount {
panic(fmt.Errorf("write too short! got %v but expected %v", n, payloadByteCount))
}
<-mgr.ReqStopChan()
tcpServerConn.Close()
mgr.MarkDone()
}()
}
func TestCreateNewAccount(srvCfg *SshegoConfig) (mylogin, totpPath, rsaPath, pw string, err error) {
srvCfg.Mut.Lock()
defer srvCfg.Mut.Unlock()
mylogin = "bob"
myemail := "[email protected]"
fullname := "Bob Fakey McFakester"
pw = fmt.Sprintf("%x", string(CryptoRandBytes(30)))
pp("srvCfg.HostDb = %#v", srvCfg.HostDb)
totpPath, _, rsaPath, err = srvCfg.HostDb.AddUser(
mylogin, myemail, pw, "gosshtun", fullname, "")
return
}
func UnencPingPong(dest, confirmationPayload, confirmationReply string, payloadByteCount int) {
conn, err := net.Dial("tcp", dest)
panicOn(err)
m, err := conn.Write([]byte(confirmationPayload))
panicOn(err)
if m != payloadByteCount {
panic("too short a write!")
}
// check reply
rep := make([]byte, payloadByteCount)
m, err = conn.Read(rep)
panicOn(err)
if m != payloadByteCount {
panic("too short a reply!")
}
srep := string(rep)
if srep != confirmationReply {
panic(fmt.Errorf("saw '%s' but expected '%s'", srep, confirmationReply))
}
pp("reply success! we got the expected srep reply '%s'", srep)
conn.Close()
}
func MakeTestSshClientAndServer(startEsshd bool) *TestSetup {
srvCfg, r1 := GenTestConfig()
cliCfg, r2 := GenTestConfig()
cliCfg.KeepAliveEvery = time.Second
ctx := context.Background()
// now that we have all different ports, we
// must release them for use below.
r1()
r2()
srvCfg.NewEsshd()
if startEsshd {
srvCfg.Esshd.Start(ctx)
}
// create a new acct
mylogin, totpPath, rsaPath, pw, err := TestCreateNewAccount(srvCfg)
panicOn(err)
// allow server to be discovered
cliCfg.AddIfNotKnown = true
cliCfg.TestAllowOneshotConnect = true
cliCfg.Username = mylogin
cliCfg.PrivateKeyPath = rsaPath
// cliCfg.TotpUrl = totpPath
// cliCfg.Pw = pw
totpUrl, err := ioutil.ReadFile(totpPath)
panicOn(err)
totp := strings.TrimSpace(string(totpUrl))
// tell the client not to run an esshd
cliCfg.EmbeddedSSHd.Addr = ""
//cliCfg.LocalToRemote.Listen.Addr = ""
//rev := cliCfg.RemoteToLocal.Listen.Addr
cliCfg.RemoteToLocal.Listen.Addr = ""
return &TestSetup{
CliCfg: cliCfg,
SrvCfg: srvCfg,
Mylogin: mylogin,
RsaPath: rsaPath,
Totp: totp,
Pw: pw,
}
}