github.com/devops-filetransfer/sshego@v7.0.4+incompatible/testutil.go (about) 1 package sshego 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "os" 9 "os/exec" 10 "strings" 11 "time" 12 13 ssh "github.com/glycerine/sshego/xendor/github.com/glycerine/xcryptossh" 14 ) 15 16 type TestSetup struct { 17 CliCfg *SshegoConfig 18 SrvCfg *SshegoConfig 19 Mylogin string 20 RsaPath string 21 Totp string 22 Pw string 23 } 24 25 func GenTestConfig() (c *SshegoConfig, releasePorts func()) { 26 27 cfg := NewSshegoConfig() 28 cfg.Origdir, cfg.Tempdir = MakeAndMoveToTempDir() // cd to tempdir 29 30 // copy in a 3 host fake known hosts 31 err := exec.Command("cp", "-rp", cfg.Origdir+"/testdata", cfg.Tempdir+"/").Run() 32 panicOn(err) 33 34 cfg.ClientKnownHostsPath = cfg.Tempdir + "/testdata/fake_known_hosts_without_b" 35 36 // poll until the copy has actually finished 37 tries := 40 38 pause := 1e0 * time.Millisecond 39 found := false 40 i := 0 41 for ; i < tries; i++ { 42 if fileExists(cfg.ClientKnownHostsPath) { 43 found = true 44 break 45 } 46 time.Sleep(pause) 47 } 48 if !found { 49 panic(fmt.Sprintf("could not locate copied file '%s' after %v tries with %v sleep between each try.", cfg.ClientKnownHostsPath, tries, pause)) 50 } 51 pp("good: we found '%s' after %v sleeps", cfg.ClientKnownHostsPath, i) 52 53 cfg.BitLenRSAkeys = 1024 // faster for testing 54 55 cfg.KnownHosts, err = NewKnownHosts(cfg.ClientKnownHostsPath, KHSsh) 56 panicOn(err) 57 //old: cfg.ClientKnownHostsPath = cfg.Tempdir + "/client_known_hosts" 58 59 // get a bunch of distinct ports, all different. 60 sshdLsn, sshdLsnPort := GetAvailPort() // sshd local listen 61 sshdTargetLsn, sshdTargetLsnPort := GetAvailPort() // target for client, sshd 62 xportLsn, xport := GetAvailPort() // xport 63 fwdStartLsn, fwdStartLsnPort := GetAvailPort() // fwdStart 64 fwdTargetLsn, fwdTargetLsnPort := GetAvailPort() // fwdTarget 65 revStartLsn, revStartLsnPort := GetAvailPort() // revStart 66 revTargetLsn, revTargetLsnPort := GetAvailPort() // revTarget 67 68 // racy, but rare: somebody else could grab this port 69 // after our Close() and before we can grab it again. 70 // Meh. Built into the way unix works. As long 71 // as we aren't testing on an overloaded super 72 // busy network box, it should be fine. 73 releasePorts = func() { 74 sshdLsn.Close() 75 sshdTargetLsn.Close() 76 xportLsn.Close() 77 78 fwdStartLsn.Close() 79 fwdTargetLsn.Close() 80 revStartLsn.Close() 81 revTargetLsn.Close() 82 } 83 84 cfg.SshegoSystemMutexPort = xport 85 86 cfg.EmbeddedSSHd.Title = "esshd" 87 cfg.EmbeddedSSHd.Addr = fmt.Sprintf("127.0.0.1:%v", sshdLsnPort) 88 cfg.EmbeddedSSHd.ParseAddr() 89 90 cfg.LocalToRemote.Listen.Title = "fwd-start" 91 cfg.LocalToRemote.Listen.Addr = fmt.Sprintf("127.0.0.1:%v", fwdStartLsnPort) 92 cfg.LocalToRemote.Listen.ParseAddr() 93 94 cfg.LocalToRemote.Remote.Title = "fwd-target" 95 cfg.LocalToRemote.Remote.Addr = fmt.Sprintf("127.0.0.1:%v", fwdTargetLsnPort) 96 cfg.LocalToRemote.Remote.ParseAddr() 97 98 cfg.RemoteToLocal.Listen.Title = "rev-start" 99 cfg.RemoteToLocal.Listen.Addr = fmt.Sprintf("127.0.0.1:%v", revStartLsnPort) 100 cfg.RemoteToLocal.Listen.ParseAddr() 101 102 cfg.RemoteToLocal.Remote.Title = "rev-target" 103 cfg.RemoteToLocal.Remote.Addr = fmt.Sprintf("127.0.0.1:%v", revTargetLsnPort) 104 cfg.RemoteToLocal.Remote.ParseAddr() 105 106 cfg.EmbeddedSSHdHostDbPath = cfg.Tempdir + "/server_hostdb" 107 108 // temp, let compile 109 _, _ = sshdLsn, sshdLsnPort 110 _, _ = sshdTargetLsn, sshdTargetLsnPort 111 _, _ = xportLsn, xport 112 _, _ = fwdStartLsn, fwdStartLsnPort 113 _, _ = fwdTargetLsn, fwdTargetLsnPort 114 _, _ = revStartLsn, revStartLsnPort 115 _, _ = revTargetLsn, revTargetLsnPort 116 117 return cfg, releasePorts 118 } 119 120 func MakeAndMoveToTempDir() (origdir string, tmpdir string) { 121 122 // make new temp dir 123 var err error 124 origdir, err = os.Getwd() 125 if err != nil { 126 panic(err) 127 } 128 tmpdir, err = ioutil.TempDir(origdir, "temp.sshego.test.dir") 129 if err != nil { 130 panic(err) 131 } 132 err = os.Chdir(tmpdir) 133 if err != nil { 134 panic(err) 135 } 136 137 return origdir, tmpdir 138 } 139 140 func TempDirCleanup(origdir string, tmpdir string) { 141 // cleanup 142 os.Chdir(origdir) 143 err := os.RemoveAll(tmpdir) 144 if err != nil { 145 panic(err) 146 } 147 fmt.Printf("\n TempDirCleanup of '%s' done.\n", tmpdir) 148 } 149 150 // GetAvailPort asks the OS for an unused port, 151 // returning a bound net.Listener and the port number 152 // to which it is bound. The caller should 153 // Close() the listener when it is done with 154 // the port. 155 func GetAvailPort() (net.Listener, int) { 156 lsn, _ := net.Listen("tcp", ":0") 157 r := lsn.Addr() 158 return lsn, r.(*net.TCPAddr).Port 159 } 160 161 // waitUntilAddrAvailable returns -1 if the addr was 162 // always unavailable after tries sleeps of dur time. 163 // Otherwise it returns the number of tries it took. 164 // Between attempts we wait 'dur' time before trying 165 // again. 166 func WaitUntilAddrAvailable(addr string, dur time.Duration, tries int) int { 167 for i := 0; i < tries; i++ { 168 var isbound bool 169 isbound = IsAlreadyBound(addr) 170 if isbound { 171 time.Sleep(dur) 172 } else { 173 fmt.Printf("\n took %v %v sleeps for address '%v' to become available.\n", i, dur, addr) 174 return i 175 } 176 } 177 return -1 178 } 179 180 func IsAlreadyBound(addr string) bool { 181 182 ln, err := net.Listen("tcp", addr) 183 if err != nil { 184 return true 185 } 186 ln.Close() 187 return false 188 } 189 190 func VerifyClientServerExchangeAcrossSshd(channelToTcpServer net.Conn, confirmationPayload, confirmationReply string, payloadByteCount int) { 191 m, err := channelToTcpServer.Write([]byte(confirmationPayload)) 192 panicOn(err) 193 if m != len(confirmationPayload) { 194 panic("too short a write!") 195 } 196 197 // check reply 198 rep := make([]byte, payloadByteCount) 199 m, err = channelToTcpServer.Read(rep) 200 panicOn(err) 201 if m != payloadByteCount { 202 panic(fmt.Sprintf("too short a reply! m = %v, expected %v. rep = '%v'", m, payloadByteCount, string(rep))) 203 } 204 srep := string(rep) 205 if srep != confirmationReply { 206 panic(fmt.Errorf("saw '%s' but expected '%s'", srep, confirmationReply)) 207 } 208 pp("reply success! we got the expected srep reply '%s'", srep) 209 } 210 211 func StartBackgroundTestTcpServer(mgr *ssh.Halter, payloadByteCount int, confirmationPayload string, confirmationReply string, tcpSrvLsn net.Listener, pnc *net.Conn) { 212 go func() { 213 214 pp("startBackgroundTestTcpServer() about to call Accept().") 215 tcpServerConn, err := tcpSrvLsn.Accept() 216 if pnc != nil { 217 *pnc = tcpServerConn 218 } 219 panicOn(err) 220 mgr.MarkReady() 221 pp("startBackgroundTestTcpServer() progress: got Accept() back: %v. LocalAddr='%v'. RemoteAddr='%v'", 222 tcpServerConn, tcpServerConn.LocalAddr(), tcpServerConn.RemoteAddr()) 223 224 b := make([]byte, payloadByteCount) 225 n, err := tcpServerConn.Read(b) 226 panicOn(err) 227 if n != payloadByteCount { 228 panic(fmt.Errorf("read too short! got %v but expected %v: '%s'", n, payloadByteCount, string(b))) 229 } 230 saw := string(b) 231 232 if saw != confirmationPayload { 233 panic(fmt.Errorf("expected '%s', but saw '%s'", confirmationPayload, saw)) 234 } 235 236 pp("success! server got expected confirmation payload of '%s'", saw) 237 238 // reply back 239 n, err = tcpServerConn.Write([]byte(confirmationReply)) 240 panicOn(err) 241 if n != payloadByteCount { 242 panic(fmt.Errorf("write too short! got %v but expected %v", n, payloadByteCount)) 243 } 244 <-mgr.ReqStopChan() 245 tcpServerConn.Close() 246 mgr.MarkDone() 247 }() 248 } 249 250 func TestCreateNewAccount(srvCfg *SshegoConfig) (mylogin, totpPath, rsaPath, pw string, err error) { 251 srvCfg.Mut.Lock() 252 defer srvCfg.Mut.Unlock() 253 mylogin = "bob" 254 myemail := "bob@example.com" 255 fullname := "Bob Fakey McFakester" 256 pw = fmt.Sprintf("%x", string(CryptoRandBytes(30))) 257 258 pp("srvCfg.HostDb = %#v", srvCfg.HostDb) 259 totpPath, _, rsaPath, err = srvCfg.HostDb.AddUser( 260 mylogin, myemail, pw, "gosshtun", fullname, "") 261 return 262 } 263 264 func UnencPingPong(dest, confirmationPayload, confirmationReply string, payloadByteCount int) { 265 conn, err := net.Dial("tcp", dest) 266 panicOn(err) 267 m, err := conn.Write([]byte(confirmationPayload)) 268 panicOn(err) 269 if m != payloadByteCount { 270 panic("too short a write!") 271 } 272 273 // check reply 274 rep := make([]byte, payloadByteCount) 275 m, err = conn.Read(rep) 276 panicOn(err) 277 if m != payloadByteCount { 278 panic("too short a reply!") 279 } 280 srep := string(rep) 281 if srep != confirmationReply { 282 panic(fmt.Errorf("saw '%s' but expected '%s'", srep, confirmationReply)) 283 } 284 pp("reply success! we got the expected srep reply '%s'", srep) 285 conn.Close() 286 } 287 288 func MakeTestSshClientAndServer(startEsshd bool) *TestSetup { 289 srvCfg, r1 := GenTestConfig() 290 cliCfg, r2 := GenTestConfig() 291 cliCfg.KeepAliveEvery = time.Second 292 ctx := context.Background() 293 294 // now that we have all different ports, we 295 // must release them for use below. 296 r1() 297 r2() 298 srvCfg.NewEsshd() 299 if startEsshd { 300 srvCfg.Esshd.Start(ctx) 301 } 302 // create a new acct 303 mylogin, totpPath, rsaPath, pw, err := TestCreateNewAccount(srvCfg) 304 panicOn(err) 305 306 // allow server to be discovered 307 cliCfg.AddIfNotKnown = true 308 cliCfg.TestAllowOneshotConnect = true 309 cliCfg.Username = mylogin 310 cliCfg.PrivateKeyPath = rsaPath 311 // cliCfg.TotpUrl = totpPath 312 // cliCfg.Pw = pw 313 314 totpUrl, err := ioutil.ReadFile(totpPath) 315 panicOn(err) 316 totp := strings.TrimSpace(string(totpUrl)) 317 318 // tell the client not to run an esshd 319 cliCfg.EmbeddedSSHd.Addr = "" 320 //cliCfg.LocalToRemote.Listen.Addr = "" 321 //rev := cliCfg.RemoteToLocal.Listen.Addr 322 cliCfg.RemoteToLocal.Listen.Addr = "" 323 324 return &TestSetup{ 325 CliCfg: cliCfg, 326 SrvCfg: srvCfg, 327 Mylogin: mylogin, 328 RsaPath: rsaPath, 329 Totp: totp, 330 Pw: pw, 331 } 332 }