Skip to content

Instantly share code, notes, and snippets.

@davidrenne
Created May 14, 2019 15:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidrenne/1254918151e1c8eda592928f1b72b5d5 to your computer and use it in GitHub Desktop.
Save davidrenne/1254918151e1c8eda592928f1b72b5d5 to your computer and use it in GitHub Desktop.
package protocols
import (
"fmt"
"io"
"net"
"runtime/debug"
"strings"
"time"
// "log"
"sync"
"golang.org/x/crypto/ssh"
"github.com/felixge/tcpkeepalive"
"github.com/pkg/errors"
"github.com/DanielRenne/GoCore/core/atomicTypes"
"github.com/atlonaeng/studio/sessionFunctions"
)
const (
SSH_BUFF = 1024
RECONNECTION_TIME = 3000
)
//OnData callback when data is received by a protocol.
type OnData func(client *TerminalSSH, data []byte)
//OnError callback when an error occurs.
type OnError func(client *TerminalSSH, err error, message string)
//OnConnection callback when a connection is established.
type OnConnection func(client *TerminalSSH)
type OnLog func(message string)
//TerminalSSH provides an object that can exec a new ssh terminal for mac and linux.
type TerminalSSH struct {
OnConnection OnConnection
OnData OnData
OnError OnError
OnLog OnLog
processWriter stdInSync
processReader stdOutSync
processError stdErrorSync
endConnection chan int
endConnectionWaiting atomicTypes.AtomicBool
password string
explicitlyDisconnected atomicTypes.AtomicBool
KillReadErr atomicTypes.AtomicBool
KillRead atomicTypes.AtomicBool
}
type stdInSync struct {
sync.RWMutex
writer io.Writer
}
type stdOutSync struct {
sync.RWMutex
reader io.Reader
}
type stdErrorSync struct {
sync.RWMutex
reader io.Reader
}
//Connect will start the ssh terminal launch and connection.
func (terminal *TerminalSSH) Connect(userName string, password string, address string, port string) {
go terminal.connectTCPKeepAlive(userName, password, address, port)
}
func (terminal *TerminalSSH) sshInteractive(user, instruction, password string, questions []string, echos []bool) (answers []string, err error) {
answers = make([]string, len(questions))
for n, _ := range questions {
answers[n] = password
}
return answers, nil
}
func (terminal *TerminalSSH) connectTCPKeepAlive(userName string, password string, address string, port string) {
defer func() {
if r := recover(); r != nil {
session_functions.Print("\n\nPanic Stack: " + string(debug.Stack()))
session_functions.VelocityLog("Panic Recovered", fmt.Sprintf("%+v", r))
return
}
}()
//Default to Non-KeyboardInteractive
keyboardInteractiveCheck := false
for {
if terminal.OnLog != nil {
terminal.OnLog("Connecting to SSH " + address)
}
if terminal.explicitlyDisconnected.Get() {
return
}
terminal.endConnectionWaiting.Set(false)
terminal.endConnection = make(chan int)
keyboardInteractive := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
answers, err = terminal.sshInteractive(user, instruction, password, questions, echos)
return
}
var config *ssh.ClientConfig
if keyboardInteractiveCheck == false {
config = &ssh.ClientConfig{
User: userName,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
}
} else {
config = &ssh.ClientConfig{
User: userName,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.KeyboardInteractive(keyboardInteractive),
},
}
}
// modes := ssh.TerminalModes{
// ssh.ECHO: 0, // disable echoing
// }
conn, err := net.DialTimeout("tcp", address+":"+port, time.Duration(3000)*time.Millisecond)
if err != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, err, "TCP Dial Error")
}
time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
continue
}
keepAliveTCPConn, _ := conn.(*net.TCPConn)
kaConn, errKeepAlive := tcpkeepalive.EnableKeepAlive(keepAliveTCPConn)
if errKeepAlive == nil {
kaConn.SetKeepAliveIdle(2 * time.Second)
kaConn.SetKeepAliveCount(2)
kaConn.SetKeepAliveInterval(2 * time.Second)
} else {
session_functions.VelocityLog("Error->protocols->tcp.Connect", "Failed to set Keep Alive on SSH Connection: "+errKeepAlive.Error())
}
sshConn, chans, reqs, err := ssh.NewClientConn(conn, address, config)
if err != nil {
if terminal.OnError != nil {
if strings.Contains(err.Error(), "no supported methods remain") {
if keyboardInteractiveCheck {
keyboardInteractiveCheck = false
} else {
keyboardInteractiveCheck = true
}
}
terminal.OnError(terminal, err, "Failed to Create New Client Conn")
}
conn.Close()
time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
continue
}
if terminal.OnLog != nil {
terminal.OnLog("Created New Client Conn")
}
sshClient := ssh.NewClient(sshConn, chans, reqs)
session, err := sshClient.NewSession()
if err != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, err, "Failed to Establish SSH Session")
}
conn.Close()
time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
continue
}
// err = session.RequestPty("xterm", 80, 40, modes)
// if err != nil {
// if terminal.OnError != nil {
// terminal.OnError(terminal, err, "Failed to Establish Psuedo Terminal")
// conn.Close()
// }
// time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
// continue
// }
// if terminal.OnLog != nil {
// terminal.OnLog("Created Psuedo Terminal")
// }
writer, err := session.StdinPipe()
if err != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, err, "Failed to set Stdin Pipe")
}
time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
continue
}
terminal.setProcessWriter(writer)
reader, err := session.StdoutPipe()
if err != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, err, "Failed to set Stdout Pipe")
}
time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
continue
}
terminal.setProcessReader(reader)
errReader, err := session.StderrPipe()
if err != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, err, "Failed to set Stderr Pipe")
}
time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
continue
}
terminal.setProcessError(errReader)
go terminal.readStdOut()
go terminal.readStdErr()
// go func() {
err = session.Shell()
if err != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, err, "Shell Exited")
}
conn.Close()
time.Sleep(time.Millisecond * time.Duration(RECONNECTION_TIME))
continue
}
if terminal.OnConnection != nil {
go terminal.OnConnection(terminal)
}
if terminal.OnLog != nil {
terminal.OnLog("Started SSH Shell Successfully.")
}
terminal.endConnectionWaiting.Set(true)
<-terminal.endConnection
session.Close()
if terminal.OnLog != nil {
terminal.OnLog("SSH Connection Ended: IP: " + address + " Port: " + port)
}
time.Sleep(time.Second * 3)
}
}
func (terminal *TerminalSSH) Disconnect() {
if terminal.endConnectionWaiting.Get() {
terminal.endConnection <- 0
}
}
func (terminal *TerminalSSH) DisconnectExplicitlyFromTerminal() {
terminal.explicitlyDisconnected.Set(true)
if terminal.endConnectionWaiting.Get() {
terminal.endConnection <- 0
}
}
func (terminal *TerminalSSH) Write(data []byte) {
defer func() {
if r := recover(); r != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, fmt.Errorf("%+v", r), "Write: Line 130 with string ("+string(data)+")")
}
return
}
}()
writer := terminal.getProcessWriter()
_, err := writer.Write(data)
if err != nil {
if terminal.endConnectionWaiting.Get() {
terminal.endConnection <- 0
}
if terminal.OnError != nil {
terminal.OnError(terminal, err, "Write: Line 141")
}
return
}
return
}
func (terminal *TerminalSSH) readStdOut() {
defer func() {
if r := recover(); r != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, fmt.Errorf("%+v", r), "readStdOut: Panic")
}
return
}
}()
result := make([]byte, SSH_BUFF)
for {
reader := terminal.getProcessReader()
length, err := reader.Read(result)
if err != nil {
if terminal.endConnectionWaiting.Get() {
terminal.endConnection <- 0
}
if terminal.OnError != nil {
terminal.OnError(terminal, err, "readStdOutGeneralError: "+err.Error())
return
}
}
if terminal.OnData != nil {
terminal.OnData(terminal, result[:length])
}
}
}
func (terminal *TerminalSSH) readStdErr() {
defer func() {
if r := recover(); r != nil {
if terminal.OnError != nil {
terminal.OnError(terminal, fmt.Errorf("%+v", r), "readStdErr: Panic")
}
return
}
}()
result := make([]byte, SSH_BUFF)
for {
reader := terminal.getProcessError()
length, err := reader.Read(result)
if err != nil {
if terminal.endConnectionWaiting.Get() {
terminal.endConnection <- 0
}
if terminal.OnError != nil {
terminal.OnError(terminal, err, "readStdErrGeneralError: "+err.Error())
return
}
return
}
if terminal.endConnectionWaiting.Get() {
terminal.endConnection <- 0
}
if terminal.OnError != nil {
terminal.OnError(terminal, errors.New("StdErr Recieved"), string(result[:length]))
}
}
}
func (terminal *TerminalSSH) setProcessWriter(writer io.WriteCloser) {
terminal.processWriter.Lock()
terminal.processWriter.writer = writer
terminal.processWriter.Unlock()
}
func (terminal *TerminalSSH) getProcessWriter() (writer io.Writer) {
terminal.processWriter.RLock()
writer = terminal.processWriter.writer
terminal.processWriter.RUnlock()
return
}
func (terminal *TerminalSSH) setProcessReader(reader io.Reader) {
terminal.processReader.Lock()
terminal.processReader.reader = reader
terminal.processReader.Unlock()
}
func (terminal *TerminalSSH) getProcessReader() (reader io.Reader) {
terminal.processReader.RLock()
reader = terminal.processReader.reader
terminal.processReader.RUnlock()
return
}
func (terminal *TerminalSSH) setProcessError(reader io.Reader) {
terminal.processError.Lock()
terminal.processError.reader = reader
terminal.processError.Unlock()
}
func (terminal *TerminalSSH) getProcessError() (reader io.Reader) {
terminal.processError.RLock()
reader = terminal.processError.reader
terminal.processError.RUnlock()
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment