Skip to content

Instantly share code, notes, and snippets.

@SilentGopherLnx
Last active March 12, 2020 09:33
Show Gist options
  • Save SilentGopherLnx/fefd9e30893991f5c4073ed8da22c5e0 to your computer and use it in GitHub Desktop.
Save SilentGopherLnx/fefd9e30893991f5c4073ed8da22c5e0 to your computer and use it in GitHub Desktop.
SSH Client for port tunneling
package main
import (
. "github.com/SilentGopherLnx/easygolang"
. "github.com/SilentGopherLnx/easygolang/easynucular"
ui "github.com/aarzilli/nucular"
"github.com/aarzilli/nucular/label"
"github.com/aarzilli/nucular/style"
"image"
"image/color"
)
const APP_VERSION = "0.10"
const FILE_CFG string = "cfg.txt"
const VAR_USER string = "user="
const VAR_PASSW string = "passw="
const VAR_SERVER string = "server="
const VAR_PORT string = "port="
const VAR_TUNNELS string = "tunnels="
var homedir = ""
var loaded_user string = "testuser"
var loaded_passw string = "123"
var loaded_servername = "95.123.213.231"
var loaded_serverport = 22
var loaded_tunnels = "[127.0.0.1,1541,127.0.0.1,1541],[example_name_from_hosts,1560,127.0.0.1,1560]"
//===========
var statusText string = ""
var statusMutex *SyncMutex
const MAX_LINES = 10
const MAX_LINES_2 = 50
var transfer = 0
var btn_lock = false
var fix_mode = false
//===========
const SCALE float64 = 1.0
var transperent color.RGBA
var te_user ui.TextEditor
var te_passw ui.TextEditor
var l4b_save label.Label
var l4b_connected label.Label
var l4b_admin label.Label
func init() {
transperent = color.RGBA{185, 185, 185, 0}
statusMutex = NewSyncMutex()
homedir = FolderLocation_UserHome()
Prln(homedir)
Prln("Platform: " + GetOS_Name() + " " + I2S(GetOS_Bits()) + "bits CPU:" + I2S(GetPC_CountCores()) + "cores")
}
func main() {
//pack_pictures()
main_work()
}
func main_work() {
filetxt, ok := FileTextRead(FILE_CFG) //homedir +
if ok {
farr := StringSplit(filetxt+"\n", "\n")
loaded_user = getParam(farr, VAR_USER)
loaded_passw = getParam(farr, VAR_PASSW)
loaded_servername = getParam(farr, VAR_SERVER)
loaded_serverport = S2I(getParam(farr, VAR_PORT))
loaded_tunnels = getParam(farr, VAR_TUNNELS)
Prln("loaded server: [" + loaded_servername + ":" + I2S(loaded_serverport) + "] user: [" + loaded_user + "] " + loaded_tunnels)
}
if len(loaded_user) == 0 {
loaded_user = "test_usr"
}
win := ui.NewMasterWindowSize(0, "Безопасное соединение с сервером по SSH", image.Point{X: 450, Y: 450}, updateGUI)
win.SetStyle(style.FromTheme(style.WhiteTheme, SCALE))
te_user = NUCULAR_MakeTextEditor(false, 30, loaded_user)
te_passw = NUCULAR_MakeTextEditor(false, 30, loaded_passw)
btn_pic_save := ImageDecodeRGBA(GetImage_BtnSave(), transperent)
if btn_pic_save != nil {
l4b_save = label.IT(btn_pic_save, "Сохранить", "CC")
} else {
l4b_save = label.T("Сохранить")
}
btn_pic_ok := ImageDecodeRGBA(GetImage_BtnOK(), transperent)
if btn_pic_ok != nil {
l4b_connected = label.IT(btn_pic_ok, "Соединено", "CC")
} else {
l4b_connected = label.T("Соединено")
}
//lbl_connected.Color = color.RGBA{0, 0, 255, 127}
win.Main()
}
func getParam(farr []string, paramname string) string {
for j := 0; j < len(farr); j++ {
tmp := farr[j]
if len(tmp) > len(paramname) && StringPart(tmp, 1, StringLength(paramname)) == paramname {
tmp = StringTrim(StringPart(tmp, StringLength(paramname)+1, StringLength(tmp)))
return tmp
}
}
return ""
}
func pushStatus(msg string) {
statusMutex.Lock()
if len(statusText) == 0 {
statusText = msg
} else {
if len(msg) > 0 {
statusText = statusText + "\n" + msg
}
}
arr := StringSplit(statusText, "\n")
l2 := MAX_LINES
if fix_mode {
l2 = MAX_LINES_2
}
delta := len(arr) - l2
if delta > 0 {
statusText = arr[delta]
for j := delta + 1; j < len(arr); j++ {
statusText = statusText + "\n" + arr[j]
}
}
statusMutex.Unlock()
}
func updateGUI(w *ui.Window) {
w.Row(30).Dynamic(3)
w.Label("Сервер", "RC")
w.LabelColored(loaded_servername+":"+I2S(loaded_serverport), "LC", color.RGBA{0, 0, 0, 255})
w.Row(30).Dynamic(3)
w.Label("Тоннели", "RC")
w.Label(loaded_tunnels, "RC")
w.Row(30).Dynamic(3)
w.Label("Компьютер", "RC")
w.LabelColored(GetPC(), "LC", color.RGBA{0, 0, 0, 255})
w.Label(GetOS_Name()+I2S(GetOS_Bits())+" [v"+APP_VERSION+"]", "RC")
w.Row(30).Dynamic(3)
w.Label("Логин тоннеля", "RC")
te_user.Edit(w)
if w.Button(l4b_save, false) {
user := NUCULAR_GetText(te_user, true)
passw := NUCULAR_GetText(te_passw, true)
FileDelete(FILE_CFG) //homedir +
txt := VAR_SERVER + loaded_servername + "\n" + VAR_PORT + I2S(loaded_serverport) + "\n" + VAR_USER + user + "\n" + VAR_PASSW + passw + "\n" + VAR_TUNNELS + loaded_tunnels + "\n"
FileTextWrite(FILE_CFG, txt) //homedir+
pushStatus("Сохранено в файл " + FILE_CFG) // + " в папке " + homedir)
Prln("Saved: [" + user + "]")
}
w.Row(30).Dynamic(3)
w.Label("Пароль", "RC")
te_passw.Edit(w)
w.Row(30).Dynamic(1)
var btn_press = false
if !btn_lock {
btn_press = w.ButtonText("Соединение с тоннелем")
} else {
w.Button(l4b_connected, false)
}
if btn_press {
ResetPorts(func(msg string, status int) {
pushStatus(msg)
})
user := NUCULAR_GetText(te_user, true)
passw := NUCULAR_GetText(te_passw, true)
Prln("Connecting [" + user + "]{" + passw + "}")
pushStatus("Соединение... ")
btn_lock = true
res := ConnectSSH(loaded_servername, loaded_serverport, parseTunnels(loaded_tunnels), user, passw, func(msg string, status int) {
//Prln("log:" + msg)
pushStatus(msg)
s2 := MAXI(0, status)
if s2 != transfer {
if s2 > 0 {
//pushStatus("Передача идёт")
} else {
//pushStatus("Передача прервана")
}
transfer = s2
}
})
if !res {
btn_lock = false
}
}
w.Row(170).Dynamic(1) //.Static(380) // .Dynamic(1)
statusMutex.Lock()
w.Label(statusText, "LD")
statusMutex.Unlock()
}
func updateGUI_fix(w *ui.Window) {
w.Row(170).Dynamic(1) //.Static(380) // .Dynamic(1)
statusMutex.Lock()
w.Label(statusText, "LD")
statusMutex.Unlock()
}
func pack_pictures() {
Prln("saving...")
data := "package main\n"
data += ImageToCode("logo.png", "Logo")
data += ImageToCode("save.png", "BtnSave")
data += ImageToCode("ok.png", "BtnOK")
data += ImageToCode("admin.png", "BtnAdmin")
FileTextWrite("images.go", data)
data2 := "package main\n"
data2 += ImageToCode("fix_hosts_win32.zip", "ExeHostsFix")
FileTextWrite("winfile.go", data2)
Prln("saved")
}
func parseTunnels(tunnels string) []SSH_Tunnel {
arr := []SSH_Tunnel{}
farr := StringSplit(tunnels, "],[")
for j := 0; j < len(farr); j++ {
str := farr[j]
if StringPart(str, 1, 1) == "[" {
str = StringPart(str, 2, 0)
}
if StringEnd(str, 1) == "]" {
str = StringPart(str, 1, StringLength(str)-1)
}
strs := StringSplit(str, ",")
if len(strs) == 4 {
t := SSH_Tunnel{
localHost: strs[0],
localPort: S2I(strs[1]),
remoteHost: strs[2],
remotePort: S2I(strs[3]),
}
arr = append(arr, t)
}
}
return arr
}
server=95.123.123.123
port=22
user=admin
passw=password
tunnels=[127.0.0.1,1541,127.0.0.1,1541],[some_server_name,1560,127.0.0.1,1560]
package main
func GetImage_BtnSave() (*[]byte) {
var imgdata = []byte{137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,97,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,1,84,73,68,65,84,56,141,157,209,191,75,2,97,28,199,241,183,113,15,202,89,16,183,220,80,18,20,4,37,4,81,77,66,77,109,181,180,25,68,144,68,83,78,13,245,23,36,68,22,238,9,34,20,52,68,96,208,224,148,80,75,13,181,232,102,216,181,220,208,117,112,74,196,13,54,132,151,63,206,203,250,76,119,207,243,253,190,248,62,207,227,219,78,92,214,115,133,18,0,23,202,30,189,100,197,216,7,96,121,126,2,41,87,40,145,78,196,144,253,18,83,197,235,158,128,244,88,12,128,141,221,19,164,198,226,249,93,133,233,213,124,79,192,213,105,145,165,153,33,128,31,160,17,93,215,61,155,85,85,109,249,111,1,132,16,40,138,226,9,216,182,221,29,0,208,52,205,19,8,133,66,222,64,123,193,111,233,0,100,89,254,63,176,147,121,250,83,115,7,240,241,86,225,211,124,237,90,236,31,28,38,60,25,166,172,87,221,129,234,187,206,218,194,56,193,96,16,248,190,15,33,4,166,105,162,105,26,217,155,103,202,250,8,163,106,191,59,32,124,182,211,124,156,74,2,112,120,112,132,97,24,206,126,123,250,220,70,109,126,137,198,179,10,33,92,143,213,7,240,88,122,113,198,106,140,221,30,203,178,92,1,41,30,141,144,202,228,137,175,47,58,139,166,105,178,25,219,2,160,86,171,1,16,8,4,92,1,31,64,50,91,168,167,206,110,153,27,120,112,45,106,206,189,53,235,124,199,163,17,190,0,252,161,95,83,184,236,140,148,0,0,0,0,73,69,78,68,174,66,96,130}
return &imgdata
}
func GetImage_BtnOK() (*[]byte) {
var imgdata = []byte{137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,97,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,219,73,68,65,84,56,141,165,146,63,10,130,96,24,135,31,35,92,4,33,144,40,59,64,120,0,135,26,91,108,104,170,217,142,208,97,186,64,208,24,52,58,116,128,106,232,0,226,1,140,136,86,231,183,193,76,81,195,63,253,166,15,190,223,243,240,242,242,194,159,81,90,147,46,2,208,105,11,203,62,126,54,23,124,224,213,197,106,33,200,192,215,240,209,80,144,131,163,40,106,32,40,129,199,125,163,88,74,54,91,128,5,89,158,45,25,30,122,162,239,84,177,61,83,112,178,93,55,46,136,228,36,53,96,5,23,209,103,42,154,166,49,25,13,56,78,125,148,117,252,89,54,246,109,123,135,83,122,63,93,128,113,223,32,120,190,184,134,15,86,23,11,217,251,64,53,28,79,0,224,32,246,198,36,120,190,190,147,0,149,112,42,40,145,0,149,112,49,115,100,225,89,162,239,212,31,219,174,147,57,98,123,102,75,56,137,67,109,248,13,28,151,152,56,45,158,210,47,0,0,0,0,73,69,78,68,174,66,96,130}
return &imgdata
}
package main
//https://gist.github.com/svett/5d695dcc4cc6ad5dd275
import (
// "fmt"
"io"
"net"
"strconv"
. "github.com/SilentGopherLnx/easygolang"
"golang.org/x/crypto/ssh" // https://github.com/golang/crypto/tree/release-branch.go1.11
)
const LOCALHOST = "127.0.0.1"
const CODE_CONNECTED = 1
const CODE_IGNORE = 2
const CODE_WORKING = 3
var listeners []net.Listener
type ISSH func(string, int)
type Endpoint struct {
Host string
Port int
}
func (endpoint *Endpoint) String() string {
return endpoint.Host + ":" + I2S(endpoint.Port)
}
type SSH_Tunnel struct {
localHost string
localPort int
remoteHost string
remotePort int
}
type sshTunnel struct {
Local *Endpoint
Server *Endpoint
Remote *Endpoint
Config *ssh.ClientConfig
}
func (tunnel *sshTunnel) forward(localConn net.Conn, t string, f ISSH) {
serverConn, err := ssh.Dial("tcp", tunnel.Server.String(), tunnel.Config)
if err != nil {
ReportError(f, "Ошибка перенаправления 12:"+t+"\n"+err.Error(), -31)
return
}
remoteConn, err := serverConn.Dial("tcp", tunnel.Remote.String())
if err != nil {
ReportError(f, "Ошибка перенаправления 21:"+t+"\n"+err.Error(), -32)
return
}
copyConn := func(writer, reader net.Conn) {
_, err := io.Copy(writer, reader)
if err != nil {
ReportError(f, "Ошибка перенаправления:"+t+"\n"+err.Error(), -40)
} else {
ReportError(f, "", CODE_WORKING)
}
}
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
func ConnectSSH(server_ip string, server_port int, tunnels []SSH_Tunnel, user string, password string, f ISSH) bool {
serverEndpoint := &Endpoint{
Host: server_ip,
Port: server_port,
}
config := GetConfig(user, password)
ok, errmsg, _ := CheckConnection(serverEndpoint, config)
if !ok {
ReportError(f, errmsg, 0)
return false
} else {
ReportError(f, "Проверка логина и пароля выполнена", 1)
}
for j := 0; j < len(tunnels); j++ {
go StartTunnel(serverEndpoint, tunnels[j], config, f)
}
return true
}
func CheckConnection(url *Endpoint, config *ssh.ClientConfig) (bool, string, int) {
client, err := ssh.Dial("tcp", url.String(), config)
if err != nil {
errtxt := err.Error()
errstr := "Ошибка поиска сервера или проверки логина и пароля"
if StringFind(errtxt, "no such host") > 0 {
errstr = "Ошибка поиска сервера"
}
if StringFind(errtxt, "unable to authenticate") > 0 {
errstr = "Ошибка логина или пароля"
}
/*if (StringFind(errtxt, "none password") > 0 || StringFind(errtxt, "password none") > 0) && StringFind(errtxt, "no supported methods remain") > 0 {
errstr = "Ошибка пароля"
}*/
return false, errstr + "\n" + err.Error(), -1
}
session, err := client.NewSession()
if err != nil {
return false, "Ошибка теста сессии\n" + err.Error(), -2
}
session.Close()
/*b, err := session.CombinedOutput("uname -a")
if err != nil {
return false, err.Error(), -3
}
result := string(b)
Prln("Result: " + result)
return true, result, 0*/
return true, "", CODE_CONNECTED
}
func StartTunnel(serverEndpoint *Endpoint, tunnel SSH_Tunnel, sshConfig *ssh.ClientConfig, f ISSH) error {
localEndpoint := &Endpoint{
Host: tunnel.localHost,
Port: tunnel.localPort,
}
remoteEndpoint := &Endpoint{
Host: tunnel.remoteHost,
Port: tunnel.remotePort,
}
tunnel_ := &sshTunnel{
Config: sshConfig,
Local: localEndpoint,
Server: serverEndpoint,
Remote: remoteEndpoint,
}
t := tunnel.localHost + ":" + strconv.Itoa(tunnel.localPort) + " > " + tunnel.remoteHost + ":" + strconv.Itoa(tunnel.remotePort)
listener, err := net.Listen("tcp", tunnel_.Local.String())
if err != nil {
ReportError(f, "Ошибка локального порта:"+t+"\n"+err.Error(), -10)
return err
} else {
ReportError(f, "Начало туннелирования: "+t, CODE_IGNORE)
listeners = append(listeners, listener)
}
defer listener.Close()
started := false
for {
conn, err := listener.Accept()
if err != nil {
ReportError(f, "Ошибка локального разрыва тоннеля:"+t+"\n"+err.Error(), -20)
return err
} else {
if !started {
ReportError(f, "Пошли пакеты по:"+t, CODE_WORKING)
}
go tunnel_.forward(conn, t, f)
}
}
}
func GetConfig(user string, password string) *ssh.ClientConfig {
sshConfig := ssh.ClientConfig{
User: user,
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // NOT SAFE!!!!!!!!!
Auth: []ssh.AuthMethod{
ssh.Password(password), //SSHAgent()
},
}
return &sshConfig
}
func ReportError(f ISSH, str string, code int) {
Prln(str)
if f != nil {
f(str, code)
}
}
func ResetPorts(f ISSH) {
ln := len(listeners)
if ln > 0 {
ReportError(f, "Разрыв предыдущих соединений: "+I2S(ln)+"штук", CODE_IGNORE)
}
for j := 0; j < ln; j++ {
listeners[j].Close()
}
listeners = []net.Listener{}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment