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  }