Last active
March 12, 2020 09:33
-
-
Save SilentGopherLnx/fefd9e30893991f5c4073ed8da22c5e0 to your computer and use it in GitHub Desktop.
SSH Client for port tunneling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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