Compare commits

...

5 Commits

Author SHA1 Message Date
Thorsten Sommer
56f0a8dd64 Merge branch 'master' of github.com:SommerEngineering/SSHTunnel 2016-03-16 08:08:08 +01:00
Thorsten Sommer
c85e21231b Version 1.3.0
In case the password must provided interactively, it is now invisible.
2016-03-16 08:05:56 +01:00
Thorsten Sommer
6146f74c72 Update README.md 2015-09-28 16:07:29 +02:00
Thorsten Sommer
c2a03bfdcf Update README.md 2015-09-28 16:07:00 +02:00
Thorsten Sommer
a16b99af6e Version 1.2.0 2015-09-28 15:28:11 +02:00
12 changed files with 308 additions and 287 deletions

103
Main.go
View File

@ -1,46 +1,57 @@
package main package main
import ( import (
"fmt" "fmt"
"golang.org/x/crypto/ssh" "github.com/SommerEngineering/SSHTunnel/Tunnel"
"runtime" "github.com/howeyc/gopass"
) "golang.org/x/crypto/ssh"
"log"
func main() { "os"
"runtime"
// Show the current version: )
fmt.Println(`SSHTunnel v1.1.0`)
func main() {
// Allow Go to use all CPUs:
runtime.GOMAXPROCS(runtime.NumCPU()) // Show the current version:
log.Println(`SSHTunnel v1.3.0`)
// Read the configuration from the command-line args:
readFlags() // Allow Go to use all CPUs:
runtime.GOMAXPROCS(runtime.NumCPU())
// Check if the password was provided:
for true { // Read the configuration from the command-line args:
if password == `` { readFlags()
// Promt for the password:
fmt.Println(`Please provide the password for the connection:`) // Check if the password was provided:
fmt.Scanln(&password) for true {
} else { if password == `` {
break // Promt for the password:
} fmt.Println(`Please provide the password for the connection:`)
} if pass, errPass := gopass.GetPasswd(); errPass != nil {
log.Println(`There was an error reading the password securely: ` + errPass.Error())
// Create the SSH configuration: os.Exit(1)
config := &ssh.ClientConfig{ return
User: username, } else {
Auth: []ssh.AuthMethod{ password = string(pass)
ssh.Password(password), }
ssh.PasswordCallback(passwordCallback), } else {
ssh.KeyboardInteractive(keyboardInteractiveChallenge), break
}, }
} }
// Create the local end-point: // Create the SSH configuration:
localListener := createLocalEndPoint() Tunnel.SetPassword4Callback(password)
config := &ssh.ClientConfig{
// Accept client connections (will block forever): User: username,
acceptClients(localListener, config) Auth: []ssh.AuthMethod{
} ssh.Password(password),
ssh.PasswordCallback(Tunnel.PasswordCallback),
ssh.KeyboardInteractive(Tunnel.KeyboardInteractiveChallenge),
},
}
// Create the local end-point:
localListener := Tunnel.CreateLocalEndPoint(localAddrString)
// Accept client connections (will block forever):
Tunnel.AcceptClients(localListener, config, serverAddrString, remoteAddrString)
}

View File

@ -1,6 +0,0 @@
package main
// Just a callback function for the password request.
func passwordCallback() (string, error) {
return password, nil
}

View File

@ -22,6 +22,7 @@ SSHTunnel is a tiny small program to tunnel something through a SSH without any
- At the moment, SSHTunnel uses only the password authentication methods. Therefore, it is currently not possible to use e.g. a certificate, etc. Nevertheless, the implementation of this feature is possible. - At the moment, SSHTunnel uses only the password authentication methods. Therefore, it is currently not possible to use e.g. a certificate, etc. Nevertheless, the implementation of this feature is possible.
- The configuration must be provided by using the command-line arguments. It is currently not possible to use e.g. a configuration file. - The configuration must be provided by using the command-line arguments. It is currently not possible to use e.g. a configuration file.
- You can avoid the password argument if you prefer to provide the password on demand. - You can avoid the password argument if you prefer to provide the password on demand.
- [Ocean Remote Connections](https://github.com/SommerEngineering/OceanRemoteConnections) is a simple GUI for SSH Tunnel, PuTTY, RDP and WinSCP.
### Download ### Download
Go and get the latest release from the [release page](https://github.com/SommerEngineering/SSHTunnel/releases). Go and get the latest release from the [release page](https://github.com/SommerEngineering/SSHTunnel/releases).

View File

@ -1,28 +1,28 @@
package main package Tunnel
import ( import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"log" "log"
"net" "net"
) )
func acceptClients(connection net.Listener, config *ssh.ClientConfig) { func AcceptClients(connection net.Listener, config *ssh.ClientConfig, serverAddrString, remoteAddrString string) {
// Endless loop // Endless loop
for { for {
// Accept (another) client connection: // Accept (another) client connection:
if localConn, err := connection.Accept(); err != nil { if localConn, err := connection.Accept(); err != nil {
// Fail // Fail
log.Printf("Accepting a client failed: %s\n", err.Error()) log.Printf("Accepting a client failed: %s\n", err.Error())
} else { } else {
// Success // Success
log.Println(`Client accepted.`) log.Println(`Client accepted.`)
// Start the forwarding: // Start the forwarding:
go forward(localConn, config) go forward(localConn, config, serverAddrString, remoteAddrString)
} }
} }
} }

View File

@ -1,7 +1,7 @@
package main package Tunnel
const ( const (
maxRetriesLocal = 16 // How many retries are allowed to create the local end-point? 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? 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? maxRetriesServer = 16 // How many retries are allowed to create the SSH server's connection?
) )

View File

@ -1,35 +1,35 @@
package main package Tunnel
import ( import (
"log" "log"
"net" "net"
"time" "time"
) )
func createLocalEndPoint() (localListener net.Listener) { func CreateLocalEndPoint(localAddrString string) (localListener net.Listener) {
// Loop for the necessary retries // Loop for the necessary retries
for { for {
// Try to create the local end-point // Try to create the local end-point
if localListenerObj, err := net.Listen(`tcp`, localAddrString); err != nil { if localListenerObj, err := net.Listen(`tcp`, localAddrString); err != nil {
// It was not able to create the end-point: // It was not able to create the end-point:
currentRetriesLocal++ currentRetriesLocal++
log.Printf("Was not able to create the local end-point %s: %s\n", localAddrString, err.Error()) log.Printf("Was not able to create the local end-point %s: %s\n", localAddrString, err.Error())
// Is another retry possible? // Is another retry possible?
if currentRetriesLocal < maxRetriesLocal { if currentRetriesLocal < maxRetriesLocal {
log.Println(`Retry...`) log.Println(`Retry...`)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} else { } else {
log.Fatalln(`No more retries for the local end-point: ` + localAddrString) // => Exit log.Fatalln(`No more retries for the local end-point: ` + localAddrString) // => Exit
} }
} else { } else {
// Success! // Success!
log.Println(`Listen to local address ` + localAddrString) log.Println(`Listen to local address ` + localAddrString)
localListener = localListenerObj localListener = localListenerObj
return return
} }
} }
} }

View File

@ -1,97 +1,97 @@
package main package Tunnel
import ( import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"log" "log"
"net" "net"
"time" "time"
) )
func forward(localConn net.Conn, config *ssh.ClientConfig) { func forward(localConn net.Conn, config *ssh.ClientConfig, serverAddrString, remoteAddrString string) {
defer localConn.Close() defer localConn.Close()
currentRetriesServer := 0 currentRetriesServer := 0
currentRetriesRemote := 0 currentRetriesRemote := 0
var sshClientConnection *ssh.Client = nil var sshClientConnection *ssh.Client = nil
// Loop for retries: // Loop for retries:
for { for {
// Try to connect to the SSH server: // Try to connect to the SSH server:
if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil { if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil {
// Failed: // Failed:
currentRetriesServer++ currentRetriesServer++
log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error()) log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error())
// Is a retry alowed? // Is a retry alowed?
if currentRetriesServer < maxRetriesServer { if currentRetriesServer < maxRetriesServer {
log.Println(`Retry...`) log.Println(`Retry...`)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} else { } else {
// After the return, this thread is closed down. The client can try it again... // After the return, this thread is closed down. The client can try it again...
log.Println(`No more retries for connecting the SSH server.`) log.Println(`No more retries for connecting the SSH server.`)
return return
} }
} else { } else {
// Success: // Success:
log.Println(`Connected to the SSH server ` + serverAddrString) log.Println(`Connected to the SSH server ` + serverAddrString)
sshClientConnection = sshClientConn sshClientConnection = sshClientConn
defer sshClientConnection.Close() defer sshClientConnection.Close()
break break
} }
} }
// Loop for retries: // Loop for retries:
for { for {
// Try to create the remote end-point: // Try to create the remote end-point:
if sshConn, err := sshClientConnection.Dial(`tcp`, remoteAddrString); err != nil { if sshConn, err := sshClientConnection.Dial(`tcp`, remoteAddrString); err != nil {
// Failed: // Failed:
currentRetriesRemote++ currentRetriesRemote++
log.Printf("Was not able to create the remote end-point %s: %s\n", remoteAddrString, err.Error()) log.Printf("Was not able to create the remote end-point %s: %s\n", remoteAddrString, err.Error())
// Is another retry allowed? // Is another retry allowed?
if currentRetriesRemote < maxRetriesRemote { if currentRetriesRemote < maxRetriesRemote {
log.Println(`Retry...`) log.Println(`Retry...`)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} else { } else {
// After the return, this thread is closed down. The client can try it again... // 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.`) log.Println(`No more retries for connecting the remote end-point.`)
return return
} }
} else { } else {
// Fine, the connections are up and ready :-) // Fine, the connections are up and ready :-)
log.Printf("The remote end-point %s is connected.\n", remoteAddrString) log.Printf("The remote end-point %s is connected.\n", remoteAddrString)
defer sshConn.Close() defer sshConn.Close()
// To be able to close down both transfer threads, we create a channel: // To be able to close down both transfer threads, we create a channel:
quit := make(chan bool) quit := make(chan bool)
// Create the transfers to/from both sides (two new threads are created for this): // Create the transfers to/from both sides (two new threads are created for this):
go transfer(localConn, sshConn, `Local => Remote`, quit) go transfer(localConn, sshConn, `Local => Remote`, quit)
go transfer(sshConn, localConn, `Remote => Local`, quit) go transfer(sshConn, localConn, `Remote => Local`, quit)
// Wait and look if any of the two transfer theads are down: // Wait and look if any of the two transfer theads are down:
isRunning := true isRunning := true
for isRunning { for isRunning {
select { select {
case <-quit: case <-quit:
log.Println(`At least one transfer was stopped.`) log.Println(`At least one transfer was stopped.`)
isRunning = false isRunning = false
break break
} }
} }
// Now, close all the channels and therefore, force the other / second thread to go down: // Now, close all the channels and therefore, force the other / second thread to go down:
log.Println(`Close now all connections.`) log.Println(`Close now all connections.`)
return return
} }
} }
} }

View File

@ -1,35 +1,35 @@
package main package Tunnel
import ( import (
"log" "log"
) )
// Another auth. method. // Another auth. method.
func keyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { func KeyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
// Log all the provided data: // Log all the provided data:
log.Println(`User: ` + user) log.Println(`User: ` + user)
log.Println(`Instruction: ` + instruction) log.Println(`Instruction: ` + instruction)
log.Println(`Questions:`) log.Println(`Questions:`)
for q := range questions { for q := range questions {
log.Println(q) log.Println(q)
} }
// How many questions are asked? // How many questions are asked?
countQuestions := len(questions) countQuestions := len(questions)
if countQuestions == 1 { if countQuestions == 1 {
// We expect that in this case (only one question is asked), that the server want to know the password ;-) // 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 = make([]string, countQuestions, countQuestions)
answers[0] = password answers[0] = callbackPassword
} else if countQuestions > 1 { } else if countQuestions > 1 {
// After logging, this call will exit the whole program: // After logging, this call will exit the whole program:
log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`) log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`)
} }
err = nil err = nil
return return
} }

View File

@ -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
}

View File

@ -1,27 +1,27 @@
package main package Tunnel
import ( import (
"io" "io"
"log" "log"
) )
// The transfer function. // The transfer function.
func transfer(fromReader io.Reader, toWriter io.Writer, name string, quit chan bool) { func transfer(fromReader io.Reader, toWriter io.Writer, name string, quit chan bool) {
log.Printf("%s transfer started.", name) log.Printf("%s transfer started.", name)
// This call blocks until the client or service will close the connection. // This call blocks until the client or service will close the connection.
// Therefore, this call maybe takes hours or even longer. Concern, may this // 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 // program will be used to connect multiple servers to make e.g. a database
// available... // available...
if _, err := io.Copy(toWriter, fromReader); err != nil { if _, err := io.Copy(toWriter, fromReader); err != nil {
// In this case, we do not fail the whole program: Regarding how the client // 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'. // 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()) log.Printf("%s transfer failed: %s\n", name, err.Error())
} else { } else {
log.Printf("%s transfer closed.\n", name) log.Printf("%s transfer closed.\n", name)
} }
quit <- true quit <- true
} }

6
Tunnel/Variables.go Normal file
View File

@ -0,0 +1,6 @@
package Tunnel
var (
currentRetriesLocal = 0 // Check how many retries are occur for creating the local end-point
callbackPassword = ``
)

View File

@ -1,10 +1,9 @@
package main package main
var ( var (
username = `` // The SSH user's name username = `` // The SSH user's name
password = `` // The user's password password = `` // The user's password
serverAddrString = `` // The SSH server address serverAddrString = `` // The SSH server address
localAddrString = `` // The local end-point localAddrString = `` // The local end-point
remoteAddrString = `` // The remote end-point (on the SSH server's side) 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
) )