diff --git a/Main.go b/Main.go index a4c43f5..19b85bd 100644 --- a/Main.go +++ b/Main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/SommerEngineering/SSHTunnel/Tunnel" "golang.org/x/crypto/ssh" "runtime" ) @@ -9,7 +10,7 @@ import ( func main() { // Show the current version: - fmt.Println(`SSHTunnel v1.1.0`) + fmt.Println(`SSHTunnel v1.2.0`) // Allow Go to use all CPUs: runtime.GOMAXPROCS(runtime.NumCPU()) @@ -29,18 +30,19 @@ func main() { } // Create the SSH configuration: + Tunnel.SetPassword4Callback(password) config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ ssh.Password(password), - ssh.PasswordCallback(passwordCallback), - ssh.KeyboardInteractive(keyboardInteractiveChallenge), + ssh.PasswordCallback(Tunnel.PasswordCallback), + ssh.KeyboardInteractive(Tunnel.KeyboardInteractiveChallenge), }, } // Create the local end-point: - localListener := createLocalEndPoint() + localListener := Tunnel.CreateLocalEndPoint(localAddrString) // Accept client connections (will block forever): - acceptClients(localListener, config) + Tunnel.AcceptClients(localListener, config, serverAddrString, remoteAddrString) } diff --git a/PasswordCallback.go b/PasswordCallback.go deleted file mode 100644 index 17b56d5..0000000 --- a/PasswordCallback.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -// Just a callback function for the password request. -func passwordCallback() (string, error) { - return password, nil -} diff --git a/AcceptClients.go b/Tunnel/AcceptClients.go similarity index 63% rename from AcceptClients.go rename to Tunnel/AcceptClients.go index aea2628..27403d3 100644 --- a/AcceptClients.go +++ b/Tunnel/AcceptClients.go @@ -1,28 +1,28 @@ -package main - -import ( - "golang.org/x/crypto/ssh" - "log" - "net" -) - -func acceptClients(connection net.Listener, config *ssh.ClientConfig) { - - // Endless loop - for { - - // Accept (another) client connection: - if localConn, err := connection.Accept(); err != nil { - - // Fail - log.Printf("Accepting a client failed: %s\n", err.Error()) - } else { - - // Success - log.Println(`Client accepted.`) - - // Start the forwarding: - go forward(localConn, config) - } - } -} +package Tunnel + +import ( + "golang.org/x/crypto/ssh" + "log" + "net" +) + +func AcceptClients(connection net.Listener, config *ssh.ClientConfig, serverAddrString, remoteAddrString string) { + + // Endless loop + for { + + // Accept (another) client connection: + if localConn, err := connection.Accept(); err != nil { + + // Fail + log.Printf("Accepting a client failed: %s\n", err.Error()) + } else { + + // Success + log.Println(`Client accepted.`) + + // Start the forwarding: + go forward(localConn, config, serverAddrString, remoteAddrString) + } + } +} diff --git a/Constants.go b/Tunnel/Constants.go similarity index 93% rename from Constants.go rename to Tunnel/Constants.go index 27b1bcf..23d9035 100644 --- a/Constants.go +++ b/Tunnel/Constants.go @@ -1,7 +1,7 @@ -package main - -const ( - maxRetriesLocal = 16 // How many retries are allowed to create the local end-point? - maxRetriesRemote = 16 // How many retries are allowed to create the remote end-point? - maxRetriesServer = 16 // How many retries are allowed to create the SSH server's connection? -) +package Tunnel + +const ( + maxRetriesLocal = 16 // How many retries are allowed to create the local end-point? + maxRetriesRemote = 16 // How many retries are allowed to create the remote end-point? + maxRetriesServer = 16 // How many retries are allowed to create the SSH server's connection? +) diff --git a/CreateLocalEndPoint.go b/Tunnel/CreateLocalEndPoint.go similarity index 87% rename from CreateLocalEndPoint.go rename to Tunnel/CreateLocalEndPoint.go index c779bd5..50db12a 100644 --- a/CreateLocalEndPoint.go +++ b/Tunnel/CreateLocalEndPoint.go @@ -1,35 +1,35 @@ -package main - -import ( - "log" - "net" - "time" -) - -func createLocalEndPoint() (localListener net.Listener) { - - // Loop for the necessary retries - for { - - // Try to create the local end-point - if localListenerObj, err := net.Listen(`tcp`, localAddrString); err != nil { - - // It was not able to create the end-point: - currentRetriesLocal++ - log.Printf("Was not able to create the local end-point %s: %s\n", localAddrString, err.Error()) - - // Is another retry possible? - if currentRetriesLocal < maxRetriesLocal { - log.Println(`Retry...`) - time.Sleep(1 * time.Second) - } else { - log.Fatalln(`No more retries for the local end-point: ` + localAddrString) // => Exit - } - } else { - // Success! - log.Println(`Listen to local address ` + localAddrString) - localListener = localListenerObj - return - } - } -} +package Tunnel + +import ( + "log" + "net" + "time" +) + +func CreateLocalEndPoint(localAddrString string) (localListener net.Listener) { + + // Loop for the necessary retries + for { + + // Try to create the local end-point + if localListenerObj, err := net.Listen(`tcp`, localAddrString); err != nil { + + // It was not able to create the end-point: + currentRetriesLocal++ + log.Printf("Was not able to create the local end-point %s: %s\n", localAddrString, err.Error()) + + // Is another retry possible? + if currentRetriesLocal < maxRetriesLocal { + log.Println(`Retry...`) + time.Sleep(1 * time.Second) + } else { + log.Fatalln(`No more retries for the local end-point: ` + localAddrString) // => Exit + } + } else { + // Success! + log.Println(`Listen to local address ` + localAddrString) + localListener = localListenerObj + return + } + } +} diff --git a/Forward.go b/Tunnel/Forward.go similarity index 93% rename from Forward.go rename to Tunnel/Forward.go index ce0c4ac..7fc2549 100644 --- a/Forward.go +++ b/Tunnel/Forward.go @@ -1,97 +1,97 @@ -package main - -import ( - "golang.org/x/crypto/ssh" - "log" - "net" - "time" -) - -func forward(localConn net.Conn, config *ssh.ClientConfig) { - - defer localConn.Close() - currentRetriesServer := 0 - currentRetriesRemote := 0 - var sshClientConnection *ssh.Client = nil - - // Loop for retries: - for { - - // Try to connect to the SSH server: - if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil { - - // Failed: - currentRetriesServer++ - log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error()) - - // Is a retry alowed? - if currentRetriesServer < maxRetriesServer { - log.Println(`Retry...`) - time.Sleep(1 * time.Second) - } else { - - // After the return, this thread is closed down. The client can try it again... - log.Println(`No more retries for connecting the SSH server.`) - return - } - - } else { - - // Success: - log.Println(`Connected to the SSH server ` + serverAddrString) - sshClientConnection = sshClientConn - defer sshClientConnection.Close() - break - } - } - - // Loop for retries: - for { - - // Try to create the remote end-point: - if sshConn, err := sshClientConnection.Dial(`tcp`, remoteAddrString); err != nil { - - // Failed: - currentRetriesRemote++ - log.Printf("Was not able to create the remote end-point %s: %s\n", remoteAddrString, err.Error()) - - // Is another retry allowed? - if currentRetriesRemote < maxRetriesRemote { - log.Println(`Retry...`) - time.Sleep(1 * time.Second) - } else { - - // After the return, this thread is closed down. The client can try it again... - log.Println(`No more retries for connecting the remote end-point.`) - return - } - } else { - - // Fine, the connections are up and ready :-) - log.Printf("The remote end-point %s is connected.\n", remoteAddrString) - defer sshConn.Close() - - // To be able to close down both transfer threads, we create a channel: - quit := make(chan bool) - - // Create the transfers to/from both sides (two new threads are created for this): - go transfer(localConn, sshConn, `Local => Remote`, quit) - go transfer(sshConn, localConn, `Remote => Local`, quit) - - // Wait and look if any of the two transfer theads are down: - isRunning := true - for isRunning { - select { - case <-quit: - log.Println(`At least one transfer was stopped.`) - isRunning = false - break - } - } - - // Now, close all the channels and therefore, force the other / second thread to go down: - log.Println(`Close now all connections.`) - return - } - } -} +package Tunnel + +import ( + "golang.org/x/crypto/ssh" + "log" + "net" + "time" +) + +func forward(localConn net.Conn, config *ssh.ClientConfig, serverAddrString, remoteAddrString string) { + + defer localConn.Close() + currentRetriesServer := 0 + currentRetriesRemote := 0 + var sshClientConnection *ssh.Client = nil + + // Loop for retries: + for { + + // Try to connect to the SSH server: + if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil { + + // Failed: + currentRetriesServer++ + log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error()) + + // Is a retry alowed? + if currentRetriesServer < maxRetriesServer { + log.Println(`Retry...`) + time.Sleep(1 * time.Second) + } else { + + // After the return, this thread is closed down. The client can try it again... + log.Println(`No more retries for connecting the SSH server.`) + return + } + + } else { + + // Success: + log.Println(`Connected to the SSH server ` + serverAddrString) + sshClientConnection = sshClientConn + defer sshClientConnection.Close() + break + } + } + + // Loop for retries: + for { + + // Try to create the remote end-point: + if sshConn, err := sshClientConnection.Dial(`tcp`, remoteAddrString); err != nil { + + // Failed: + currentRetriesRemote++ + log.Printf("Was not able to create the remote end-point %s: %s\n", remoteAddrString, err.Error()) + + // Is another retry allowed? + if currentRetriesRemote < maxRetriesRemote { + log.Println(`Retry...`) + time.Sleep(1 * time.Second) + } else { + + // After the return, this thread is closed down. The client can try it again... + log.Println(`No more retries for connecting the remote end-point.`) + return + } + } else { + + // Fine, the connections are up and ready :-) + log.Printf("The remote end-point %s is connected.\n", remoteAddrString) + defer sshConn.Close() + + // To be able to close down both transfer threads, we create a channel: + quit := make(chan bool) + + // Create the transfers to/from both sides (two new threads are created for this): + go transfer(localConn, sshConn, `Local => Remote`, quit) + go transfer(sshConn, localConn, `Remote => Local`, quit) + + // Wait and look if any of the two transfer theads are down: + isRunning := true + for isRunning { + select { + case <-quit: + log.Println(`At least one transfer was stopped.`) + isRunning = false + break + } + } + + // Now, close all the channels and therefore, force the other / second thread to go down: + log.Println(`Close now all connections.`) + return + } + } +} diff --git a/KeyboardInteractiveChallenge.go b/Tunnel/KeyboardInteractiveChallenge.go similarity index 85% rename from KeyboardInteractiveChallenge.go rename to Tunnel/KeyboardInteractiveChallenge.go index 570b979..01f0e56 100644 --- a/KeyboardInteractiveChallenge.go +++ b/Tunnel/KeyboardInteractiveChallenge.go @@ -1,35 +1,35 @@ -package main - -import ( - "log" -) - -// Another auth. method. -func keyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { - - // Log all the provided data: - log.Println(`User: ` + user) - log.Println(`Instruction: ` + instruction) - log.Println(`Questions:`) - for q := range questions { - log.Println(q) - } - - // How many questions are asked? - countQuestions := len(questions) - - if countQuestions == 1 { - - // We expect that in this case (only one question is asked), that the server want to know the password ;-) - answers = make([]string, countQuestions, countQuestions) - answers[0] = password - - } else if countQuestions > 1 { - - // After logging, this call will exit the whole program: - log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`) - } - - err = nil - return -} +package Tunnel + +import ( + "log" +) + +// Another auth. method. +func KeyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + + // Log all the provided data: + log.Println(`User: ` + user) + log.Println(`Instruction: ` + instruction) + log.Println(`Questions:`) + for q := range questions { + log.Println(q) + } + + // How many questions are asked? + countQuestions := len(questions) + + if countQuestions == 1 { + + // We expect that in this case (only one question is asked), that the server want to know the password ;-) + answers = make([]string, countQuestions, countQuestions) + answers[0] = callbackPassword + + } else if countQuestions > 1 { + + // After logging, this call will exit the whole program: + log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`) + } + + err = nil + return +} diff --git a/Tunnel/PasswordCallback.go b/Tunnel/PasswordCallback.go new file mode 100644 index 0000000..89bbc4a --- /dev/null +++ b/Tunnel/PasswordCallback.go @@ -0,0 +1,10 @@ +package Tunnel + +// Just a callback function for the password request. +func PasswordCallback() (string, error) { + return callbackPassword, nil +} + +func SetPassword4Callback(password string) { + callbackPassword = password +} diff --git a/Transfer.go b/Tunnel/Transfer.go similarity index 95% rename from Transfer.go rename to Tunnel/Transfer.go index b5d3fac..5b2aa76 100644 --- a/Transfer.go +++ b/Tunnel/Transfer.go @@ -1,27 +1,27 @@ -package main - -import ( - "io" - "log" -) - -// The transfer function. -func transfer(fromReader io.Reader, toWriter io.Writer, name string, quit chan bool) { - - log.Printf("%s transfer started.", name) - - // This call blocks until the client or service will close the connection. - // Therefore, this call maybe takes hours or even longer. Concern, may this - // program will be used to connect multiple servers to make e.g. a database - // available... - if _, err := io.Copy(toWriter, fromReader); err != nil { - - // In this case, we do not fail the whole program: Regarding how the client - // or the service was e.g. shut down, the error may only means 'client has been closed'. - log.Printf("%s transfer failed: %s\n", name, err.Error()) - } else { - log.Printf("%s transfer closed.\n", name) - } - - quit <- true -} +package Tunnel + +import ( + "io" + "log" +) + +// The transfer function. +func transfer(fromReader io.Reader, toWriter io.Writer, name string, quit chan bool) { + + log.Printf("%s transfer started.", name) + + // This call blocks until the client or service will close the connection. + // Therefore, this call maybe takes hours or even longer. Concern, may this + // program will be used to connect multiple servers to make e.g. a database + // available... + if _, err := io.Copy(toWriter, fromReader); err != nil { + + // In this case, we do not fail the whole program: Regarding how the client + // or the service was e.g. shut down, the error may only means 'client has been closed'. + log.Printf("%s transfer failed: %s\n", name, err.Error()) + } else { + log.Printf("%s transfer closed.\n", name) + } + + quit <- true +} diff --git a/Tunnel/Variables.go b/Tunnel/Variables.go new file mode 100644 index 0000000..216c0ab --- /dev/null +++ b/Tunnel/Variables.go @@ -0,0 +1,6 @@ +package Tunnel + +var ( + currentRetriesLocal = 0 // Check how many retries are occur for creating the local end-point + callbackPassword = `` +) diff --git a/Variables.go b/Variables.go index 237d757..79c8171 100644 --- a/Variables.go +++ b/Variables.go @@ -1,10 +1,9 @@ package main var ( - username = `` // The SSH user's name - password = `` // The user's password - serverAddrString = `` // The SSH server address - localAddrString = `` // The local end-point - remoteAddrString = `` // The remote end-point (on the SSH server's side) - currentRetriesLocal = 0 // Check how many retries are occur for creating the local end-point + username = `` // The SSH user's name + password = `` // The user's password + serverAddrString = `` // The SSH server address + localAddrString = `` // The local end-point + remoteAddrString = `` // The remote end-point (on the SSH server's side) )