diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1e4909 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +*~ +*.exe diff --git a/app.go b/app.go new file mode 100644 index 0000000..2225ed1 --- /dev/null +++ b/app.go @@ -0,0 +1,65 @@ +package sshhop + +import ( + "flag" + "log" + "golang.org/x/crypto/ssh" + "io/ioutil" +) + + +func(c *Client) Start(){ + + //The intermediary server for port binding + serverHostname := flag.String("server", "alpine.cse.unr.edu", "Intermediate Server") + username := flag.String("username", "user", "Username") + privKey := flag.String("privkey", "/nfs/home/user/.ssh/id_rsa", "Private Key") + + var endpoints EndpointsFlag + flag.Var(&endpoints,"endpoints", "Local and remote endpoints") + flag.Parse() + + if len(endpoints) < 1 { + log.Printf("No endpoints defined: %d\n", len(endpoints)) + + endpoints = make(EndpointsFlag, 0) + endpoints = append(endpoints, "www.cse.unr.edu:22") + endpoints = append(endpoints, "www.cse.unr.edu:22") + endpoints = append(endpoints, "www.cse.unr.edu:22") + } + + + // 27013 + serverEndpoint := &Endpoint{ + Host: *serverHostname, + Port: 22, + } + + key, err := ioutil.ReadFile(*privKey) + if err != nil { + log.Fatalf("Unable to read private key: %v", err) + } + + + // Create the Signer for this private key. + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + log.Fatalf("unable to parse private key: %v", err) + } + + sshConfig := &ssh.ClientConfig{ + User: *username, + HostKeyCallback: KeyPrint, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + } + + tunnel := &SSHtunnel{ + Config: sshConfig, + Server: serverEndpoint, + Remote: endpoints, + } + + tunnel.Start() +} diff --git a/app_test.go b/app_test.go new file mode 100644 index 0000000..4a09d40 --- /dev/null +++ b/app_test.go @@ -0,0 +1,110 @@ +package sshhop + +import ( + "testing" + "fmt" +) + +//function +func TestEndPointString(t *testing.T) { + var testend Endpoint + + testend.Host = "localhost" + testend.Port = 5555 + + // make a string and test what the string should be + stringWant := "localhost:5555" + + returnedString := testend.String() + + fmt.Printf("\nstringWant: %s\n\n", stringWant) + fmt.Printf("returnedString : %s\n\n", returnedString) + // return string == stringwant // success +} + +//function +func TestEndPointParseString(t *testing.T) { + var testRemote, testRemote1 Endpoint + var testLocal, testLocal1 Endpoint + + + // make a string and test what the string should be + testLocal, testRemote = ParseEndpointString("5555:motherbrain.unr.edu:5555") + testLocal1, testRemote1 = ParseEndpointString("27013:motherbrain.unr.edu:27013") + + fmt.Printf("Local: %s %d\n\n", testLocal.Host, testLocal.Port) + fmt.Printf("Remote: %s %d \n\n", testRemote.Host, testRemote.Port) + + fmt.Printf("Local: %s %d\n\n", testLocal1.Host, testLocal1.Port) + fmt.Printf("Remote: %s %d \n\n", testRemote1.Host, testRemote1.Port) + + if testLocal.Host != "localhost"{ + t.Error("Invalid Local EP Hostname") + } + + if testLocal.Port != 5555 { + t.Error("Invalid Local EP Port") + } + + if testRemote.Host != "motherbrain.unr.edu"{ + t.Error("Invalid remote EP Hostname") + } + + if testRemote.Port != 5555 { + t.Error("Invalid remote EP Port") + } + + //////////////////////////////////////// + + if testLocal1.Host != "localhost"{ + t.Error("Invalid Local EP Hostname") + } + + if testLocal1.Port != 27013 { + t.Error("Invalid Local EP Port") + } + + if testRemote1.Host != "motherbrain.unr.edu"{ + t.Error("Invalid remote EP Hostname") + } + + if testRemote1.Port != 27013 { + t.Error("Invalid remote EP Port") + } + // return string == stringwant // success +} + +func TestEndPointsArray(t *testing.T) { + + //Input + testInput := []string{ + "5555:motherbrain.unr.edu:5555", + "27013:motherbrain.unr.edu:27013", + } + fmt.Printf("Input %v \n\n", testInput) + + //Anticipated output + testLocalEndpoints := [2]Endpoint{ + {Host: "localhost", Port: 5555}, + {Host: "localhost", Port: 27013}, + }; + testRemoteEndpoints := [2]Endpoint{ + {Host: "motherbrain.unr.edu", Port: 5555}, + {Host: "motherbrain.unr.edu", Port: 27013}, + }; + + for r,_ := range(testLocalEndpoints){ + fmt.Printf("Test Local: %v\n\n", testLocalEndpoints[r]) + fmt.Printf("Test Remote: %v\n\n", testRemoteEndpoints[r]) + } + + // try to match the testInput with the anticipated output by passing the test, + // append the input to array + myLocalEndpoints, myRemoteEndpoints := ParseEndpointsFromArray(testInput); + + //Real output + for r,_ := range(myLocalEndpoints){ + fmt.Printf("Local: %v\n\n", myLocalEndpoints[r]) + fmt.Printf("Remote: %v\n\n", myRemoteEndpoints[r]) + } +} diff --git a/bin/main.exe.manifest b/bin/main.exe.manifest new file mode 100644 index 0000000..c4d6dd4 --- /dev/null +++ b/bin/main.exe.manifest @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/bin/main.go b/bin/main.go new file mode 100644 index 0000000..3ef7105 --- /dev/null +++ b/bin/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "sshhop" + "fmt" +) + + +func main() { + var ep = &sshhop.Endpoint{}; + var sshClient = &sshhop.Client{}; + + sshClient.Start() + fmt.Printf("%v", ep) +} diff --git a/credentials.go b/credentials.go new file mode 100644 index 0000000..4d88741 --- /dev/null +++ b/credentials.go @@ -0,0 +1,7 @@ +package sshhop + + +type Credentials struct { + Username string + Password string +} diff --git a/endpoint.go b/endpoint.go new file mode 100644 index 0000000..ce8bb98 --- /dev/null +++ b/endpoint.go @@ -0,0 +1,53 @@ +package sshhop + +import ( + "encoding/base64" + "fmt" + "golang.org/x/crypto/ssh" + "net" + "strconv" + "strings" +) + +type Client struct { +} + +type Endpoint struct { + Host string + Port int +} + +func KeyPrint(dialAddr string, addr net.Addr, key ssh.PublicKey) error { + fmt.Printf("%s %s %s\n", strings.Split(dialAddr, ":")[0], key.Type(), base64.StdEncoding.EncodeToString(key.Marshal())) + return nil +} + +func (endpoint *Endpoint) String() string { + return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port) +} + +func ParseEndpointString(endpointString string) (localEp Endpoint, remoteEp Endpoint) { + + localEp.Host = "localhost" + + buffer := strings.Split(endpointString, ":") + localEp.Port, _ = strconv.Atoi(buffer[0]) + + remoteEp.Host = buffer[1] + remoteEp.Port, _ = strconv.Atoi(buffer[2]) + + return localEp, remoteEp +} + +func ParseEndpointsFromArray(endpointStruct []string) ([]Endpoint, []Endpoint) { + localEndpoints := make([]Endpoint, 0) + remoteEndpoints := make([]Endpoint, 0) + + for _, e := range endpointStruct { + localE, remoteE := ParseEndpointString(e) + localEndpoints = append(localEndpoints, localE) + remoteEndpoints = append(remoteEndpoints, remoteE) + } + + return localEndpoints, remoteEndpoints +} diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go new file mode 100644 index 0000000..78cc77b --- /dev/null +++ b/endpoint/endpoint.go @@ -0,0 +1,85 @@ +package endpoint + +import ( + + "fmt" + "io" + + "golang.org/x/crypto/ssh" + + + "net" +) + +type Endpoint struct { + Host string + Port int +} + +func (endpoint *Endpoint) String() string { + return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port) +} + +type SSHtunnel struct { + Local *Endpoint + Server *Endpoint + Remote *Endpoint + + Config *ssh.ClientConfig + ServerConnection *ssh.ServerConfig +} + +// type localEndpoint []Endpoint +// +// type remoteEndpoint []Endpoint + + +//func (index *arrayFlags) Set + +// var testend Endpoint +// +// testend.Host = "localhost" +// testend.Port = "5" + +func (tunnel *SSHtunnel) Start() error { + for { + listener, err := net.Listen("tcp", tunnel.Local.String()) + if err != nil { + return err + } + defer listener.Close() + + for { + conn, err := listener.Accept() + if err != nil { + return err + } + go tunnel.forward(conn) + } +} + } + + +func (tunnel *SSHtunnel) forward(localConn net.Conn) { + serverConn, err := ssh.Dial("tcp", tunnel.Server.String(), tunnel.Config) + if err != nil { + fmt.Printf("Server dial error: %s\n", err) + return + } + + remoteConn, err := serverConn.Dial("tcp", tunnel.Remote.String()) + if err != nil { + fmt.Printf("Remote dial error: %s\n", err) + return + } + + copyConn:=func(writer, reader net.Conn) { + _, err:= io.Copy(writer, reader) + if err != nil { + fmt.Printf("io.Copy error: %s", err) + } + } + + go copyConn(localConn, remoteConn) + go copyConn(remoteConn, localConn) +} diff --git a/endpoint/endpoint_test.go b/endpoint/endpoint_test.go new file mode 100644 index 0000000..d398c85 --- /dev/null +++ b/endpoint/endpoint_test.go @@ -0,0 +1,37 @@ +package endpoint + +import ( + "testing" + "fmt" + "sshtunnel" +) + +//function +func TestEndPointString(t *testing.T) { + var testend sshtunnel.Endpoint + + testend.Host = "localhost" + testend.Port = 5555 + + // make a string and test what the string should be + stringWant := "localhost:5555" + + returnedString := testend.String() + + fmt.Printf("stringWant: %s\n", stringWant) + fmt.Printf("returnedString : %s\n", returnedString) + // return string == stringwant // success +} + +//function +func TestEndPointParseString(t *testing.T) { + var testRemote sshtunnel.Endpoint + var testLocal sshtunnel.Endpoint + + // make a string and test what the string should be + testLocal, testRemote = sshtunnel.ParseEndpointString("5555:motherbrain.unr.edu:5555") + + fmt.Printf("Local: %s %s\n", testLocal.Host, testLocal.Port) + fmt.Printf("Remote: %s %s \n", testRemote.Host, testRemote.Port) + // return string == stringwant // success +} diff --git a/endpoints_flag.go b/endpoints_flag.go new file mode 100644 index 0000000..c8c5ad1 --- /dev/null +++ b/endpoints_flag.go @@ -0,0 +1,12 @@ +package sshhop + +type EndpointsFlag []string + +func (i *EndpointsFlag) String() string { + return "my string representation" +} + +func (i *EndpointsFlag) Set(value string) error { + *i = append(*i, value) + return nil +} diff --git a/terminal/channel.go b/terminal/channel.go new file mode 100644 index 0000000..2480aed --- /dev/null +++ b/terminal/channel.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" +) + +func sum(s []int, c chan int) { + sumOfNum := 0 + for _, v := range s { + + } +} diff --git a/tunnel.go b/tunnel.go new file mode 100644 index 0000000..8c5467e --- /dev/null +++ b/tunnel.go @@ -0,0 +1,83 @@ +package sshhop + +import ( + "fmt" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + "log" + "net" + "os" + "sync" + "bytes" +) + +type SSHtunnel struct { + Server *Endpoint + Remote []string + ErrorChans []chan error + ServerClient *ssh.Client + Config *ssh.ClientConfig + ServerConnection *ssh.ServerConfig + TargetClients []*ssh.Client + sync.Mutex +} + +func (tunnel *SSHtunnel) Start() error { + var err error + + tunnel.ErrorChans = make([]chan error, len(tunnel.Remote)) + tunnel.TargetClients = make([]*ssh.Client, 0) + + tunnel.ServerClient, err = ssh.Dial("tcp", tunnel.Server.String(), tunnel.Config) + if err != nil { + fmt.Printf("Server dial error: %s\n", err) + os.Exit(1) + return err + } + + // + for i, _ := range tunnel.Remote { + + conn, err := tunnel.ServerClient.Dial("tcp", tunnel.Remote[i]) + if err != nil { + return err + } + + ncc, chans, reqs, err := ssh.NewClientConn(conn, tunnel.Remote[i], tunnel.Config) + if err != nil { + log.Fatalf("New client connection error: %s\n", err) + return err + } + client := ssh.NewClient(ncc, chans, reqs) + //Keep clients in array + tunnel.TargetClients = append(tunnel.TargetClients, client) + + //Execute a command + //Ideally this would be done in a go routine for concurrency + session, _ := client.NewSession() + defer session.Close() + + var stdoutBuf bytes.Buffer + session.Stdout = &stdoutBuf + session.Run("hostname") + + log.Printf("Target Hostname: %s", stdoutBuf.String()) + + } + + return err + +} + +func SSHAgent() ssh.AuthMethod { + if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { + return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers) + } + return nil +} + +func check(e error) { + if e != nil { + panic(e) + } +} diff --git a/windows_ui.go b/windows_ui.go new file mode 100644 index 0000000..a3d7b4a --- /dev/null +++ b/windows_ui.go @@ -0,0 +1,71 @@ +// +build windows,!linux + +package sshhop + +import ( + . "github.com/lxn/walk/declarative" + "github.com/lxn/walk" +) + +type PasswordForm struct { + Credentials *Credentials + +} + +func NewPasswordForm() *PasswordForm { + + return &PasswordForm{} +} + +func (p *PasswordForm) Show(){ + var mainWindow *walk.MainWindow + var usernameLE, passwordLE *walk.LineEdit + p.Credentials = &Credentials{Username:"Test"} + + MainWindow{ + AssignTo: &mainWindow, + Title: "Login", + MinSize: Size{250, 150}, + Layout: VBox{}, + Children: []Widget{ + HSplitter{ + Children: []Widget{ + Label{ Text: "Username"}, + LineEdit{ AssignTo: &usernameLE }, + + }, + }, + HSplitter{ + Children: []Widget{ + Label{ Text: "Password"}, + LineEdit{PasswordMode: true, + AssignTo: &passwordLE, + OnKeyDown: func(key walk.Key) { + if key == walk.KeyReturn { + p.Credentials.Username = usernameLE.Text() + p.Credentials.Password = passwordLE.Text() + mainWindow.Close() + } + }, + }, + + }, + }, + PushButton{ + Text: "Login", + OnClicked: func() { + p.Credentials.Username = usernameLE.Text() + p.Credentials.Password = passwordLE.Text() + mainWindow.Close() + }, + }, + PushButton{ + Text: "Cancel", + OnClicked: func() { + mainWindow.Close() + }, + }, + + }, + }.Run() +}