diff --git a/.gitignore b/.gitignore index 8e2b4d1..decaba0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,2 @@ -# vscode -.vscode - -# Binaries for programs and plugins +.vscode/ *.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -*.DS_Store -*.sublime-* - -_TEST -_test diff --git a/README.md b/README.md index a2f4298..e4a8706 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ go-sshlib ==== -[![GoDoc](https://godoc.org/github.com/blacknon/go-sshlib?status.svg)](https://godoc.org/github.com/blacknon/go-sshlib) +[![GoDoc](https://godoc.org/github.com/abakum/go-sshlib?status.svg)](https://godoc.org/github.com/abakum/go-sshlib) ## About @@ -14,11 +14,11 @@ If use **pkcs11** authentication, cgo must be enabled. ## Usage -[See GoDoc reference.](https://godoc.org/github.com/blacknon/go-sshlib) +[See GoDoc reference.](https://godoc.org/github.com/abakum/go-sshlib) ## Download - GO111MODULE=on go get github.com/blacknon/go-sshlib + GO111MODULE=on go get github.com/abakum/go-sshlib ## Example @@ -37,7 +37,7 @@ If use **pkcs11** authentication, cgo must be enabled. "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + sshlib "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) @@ -64,7 +64,7 @@ If use **pkcs11** authentication, cgo must be enabled. // Create ssh.AuthMethod authMethod := sshlib.CreateAuthMethodPassword(password) - // If you use ssh-agent forwarding, uncomment it. + // If you use ssh-agent, uncomment it. // con.ConnectSshAgent() // Connect ssh server diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9bbaee4 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.12 \ No newline at end of file diff --git a/_example/example_cmdshell_agent.go b/_example/example_cmdshell_agent.go new file mode 100644 index 0000000..835bad9 --- /dev/null +++ b/_example/example_cmdshell_agent.go @@ -0,0 +1,62 @@ +// Copyright (c) 2020 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// Shell connection Example file. +// Change the value of the variable and compile to make sure that you can actually connect. +// + +package main + +import ( + "fmt" + "os" + + "github.com/abakum/go-sshlib" +) + +var ( + // dropbear on linux + // host = "10.161.115.160" + // port = "22" + // user = "root" + // command = "ssh user_@10.161.115.189" + + // sshd of OpenSSH on Windows + // host = "10.161.115.189" + // port = "22" + // user = "user_" + // command = "ssh root@10.161.115.160" + + // sshd of gliderlabs on Windows + host = "10.161.115.189" + port = "2222" + user = "user_" + command = "ssh root@10.161.115.160" +) + +func main() { + // Create sshlib.Connect + con := &sshlib.Connect{ + // If you use x11 forwarding, please uncomment next line. + // ForwardX11: true, + + // If you use ssh-agent forwarding, please set to true. + // And after, run `con.ConnectSshAgent()`. + ForwardAgent: true, + } + + // setup con.Agent for use ssh-agent + con.ConnectSshAgent() + + // Connect ssh server + // set authMethods to nil for use ssh-agent + err := con.CreateClient(host, port, user, nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Start ssh shell with command + con.CmdShell(nil, command) +} diff --git a/_example/example_dynamic_forward_shell.go b/_example/example_dynamic_forward_shell.go index 5bb0565..ded2714 100644 --- a/_example/example_dynamic_forward_shell.go +++ b/_example/example_dynamic_forward_shell.go @@ -13,7 +13,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) diff --git a/_example/example_multiproxy_shell.go b/_example/example_multiproxy_shell.go index b20b29e..b6e34dc 100644 --- a/_example/example_multiproxy_shell.go +++ b/_example/example_multiproxy_shell.go @@ -15,7 +15,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) @@ -59,8 +59,8 @@ func main() { socks5Proxy := &sshlib.Proxy{ Type: "socks5", - Addr: httpProxyHost, - Port: httpProxyPort, + Addr: socks5ProxyHost, + Port: socks5ProxyPort, Forwarder: httpProxyDialer, } socks5ProxyDialer, err := httpProxy.CreateProxyDialer() diff --git a/_example/example_portforward_shell.go b/_example/example_portforward_shell.go index d5a0056..e048c27 100644 --- a/_example/example_portforward_shell.go +++ b/_example/example_portforward_shell.go @@ -13,7 +13,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) diff --git a/_example/example_proxycmd_shell.go b/_example/example_proxycmd_shell.go index 44b9353..8bda43b 100644 --- a/_example/example_proxycmd_shell.go +++ b/_example/example_proxycmd_shell.go @@ -15,7 +15,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) diff --git a/_example/example_reverse_dynamic_forward_shell.go b/_example/example_reverse_dynamic_forward_shell.go index de0e61c..34cd0e2 100644 --- a/_example/example_reverse_dynamic_forward_shell.go +++ b/_example/example_reverse_dynamic_forward_shell.go @@ -13,7 +13,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) diff --git a/_example/example_reverse_portforward_shell.go b/_example/example_reverse_portforward_shell.go index 585908d..c6d3915 100644 --- a/_example/example_reverse_portforward_shell.go +++ b/_example/example_reverse_portforward_shell.go @@ -13,7 +13,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + sshlib "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) diff --git a/_example/example_shell_password.go b/_example/example_shell_password.go index fd72bd6..cf56744 100644 --- a/_example/example_shell_password.go +++ b/_example/example_shell_password.go @@ -13,14 +13,17 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) var ( - host = "target.com" - port = "22" - user = "user" + // host = "10.161.115.160" + // port = "22" + // user = "root" + host = "10.161.115.189" + port = "2222" + user = "user_" password = "password" termlog = "./test_termlog" @@ -40,8 +43,8 @@ func main() { // Create ssh.AuthMethod authMethod := sshlib.CreateAuthMethodPassword(password) - // If you use ssh-agent forwarding, uncomment it. - // con.ConnectSshAgent() + // If you use ssh-agent, uncomment it. + con.ConnectSshAgent() // Connect ssh server err := con.CreateClient(host, port, user, []ssh.AuthMethod{authMethod}) @@ -51,15 +54,9 @@ func main() { } // Set terminal log - con.SetLog(termlog, false) - - // Create Session - session, err := con.CreateSession() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + // con.SetLog(termlog, true) + con.SetLogWithRemoveAnsiCode(termlog, false) // Start ssh shell - con.Shell(session) + con.Shell(nil) } diff --git a/_example/example_shell_pkcs11.go b/_example/example_shell_pkcs11.go index a94c23c..cf63861 100644 --- a/_example/example_shell_pkcs11.go +++ b/_example/example_shell_pkcs11.go @@ -13,7 +13,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" ) var ( @@ -34,17 +34,17 @@ func main() { } // Create ssh.AuthMethod - authMethod, err := sshlib.CreateAuthMethodPKCS11("/usr/local/opt/opensc/lib/opensc-pkcs11.so", "") + authMethods, err := sshlib.CreateAuthMethodPKCS11("/usr/local/opt/opensc/lib/opensc-pkcs11.so", "") if err != nil { fmt.Println(err) os.Exit(1) } - // If you use ssh-agent forwarding, uncomment it. - // con.ConnectSshAgent() + // If you use ssh-agent, uncomment it. + con.ConnectSshAgent() // Connect ssh server - err = con.CreateClient(host, port, user, authMethod) + err = con.CreateClient(host, port, user, authMethods) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/_example/example_shell_pubkey.go b/_example/example_shell_pubkey.go index edf0635..b49b72f 100644 --- a/_example/example_shell_pubkey.go +++ b/_example/example_shell_pubkey.go @@ -13,7 +13,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) @@ -45,7 +45,7 @@ func main() { os.Exit(1) } - // If you use ssh-agent forwarding, uncomment it. + // If you use ssh-agent, uncomment it. // con.ConnectSshAgent() // Connect ssh server diff --git a/_example/example_sshproxy_agent.go b/_example/example_sshproxy_agent.go new file mode 100644 index 0000000..eaf771a --- /dev/null +++ b/_example/example_sshproxy_agent.go @@ -0,0 +1,86 @@ +// Copyright (c) 2020 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// Shell connection Example file. +// Change the value of the variable and compile to make sure that you can actually connect. +// +// This file has a simple ssh proxy connection. +// Also, the authentication method is agent authentication. +// Please replace as appropriate. + +package main + +import ( + "fmt" + "os" + + "github.com/abakum/go-sshlib" + "golang.org/x/crypto/ssh" +) + +var ( + // ssh -J user@proxy.com user@target.com + // Proxy ssh server + // host1 = "proxy.com" + // port1 = "22" + // user1 = "user" + + // sshd of OpenSSH on Windows + host1 = "10.161.115.189" + port1 = "2222" + user1 = "user_" + + // Target ssh server + // host2 = "target.com" + // port2 = "22" + // user2 = "user" + + // dropbear on linux + host2 = "10.161.115.160" + port2 = "22" + user2 = "root" +) + +func main() { + // ========== + // proxy connect + // ========== + + // Create proxy sshlib.Connect + proxyCon := &sshlib.Connect{} + + // // Connect to ssh-agent + // proxyCon.ConnectSshAgent() + + // Create ssh.AuthMethod from ssh-agent for target + AuthMethod := proxyCon.CreateAuthMethodAgent() + + // Connect proxy server + err := proxyCon.CreateClient(host1, port1, user1, nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // ========== + // target connect + // ========== + + // Create target sshlib.Connect + targetCon := &sshlib.Connect{ + ProxyDialer: proxyCon.Client, + } + + // Connect target server + err = targetCon.CreateClient(host2, port2, user2, []ssh.AuthMethod{AuthMethod}) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Set terminal log + // targetCon.SetLog(termlog, false) + + targetCon.Shell(nil) +} diff --git a/_example/example_sshproxy_shell.go b/_example/example_sshproxy_shell.go index 37d9736..7e7872e 100644 --- a/_example/example_sshproxy_shell.go +++ b/_example/example_sshproxy_shell.go @@ -15,7 +15,7 @@ import ( "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) diff --git a/agent.go b/agent.go index 5c9bbd1..7cc7740 100644 --- a/agent.go +++ b/agent.go @@ -5,9 +5,6 @@ package sshlib import ( - "net" - "os" - "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) @@ -15,23 +12,7 @@ import ( // AgentInterface Interface for storing agent.Agent or agent.ExtendedAgent. type AgentInterface interface{} -// ConnectSshAgent -func ConnectSshAgent() (ag AgentInterface) { - // Get env "SSH_AUTH_SOCK" and connect. - sockPath := os.Getenv("SSH_AUTH_SOCK") - sock, err := net.Dial("unix", sockPath) - - if err != nil { - ag = agent.NewKeyring() - } else { - // connect SSH_AUTH_SOCK - ag = agent.NewClient(sock) - } - - return -} - -// AddKeySshAgent is rapper agent.Add(). +// AddKeySshAgent is wrapper agent.Add(). // key must be a *rsa.PrivateKey, *dsa.PrivateKey or // *ecdsa.PrivateKey, which will be inserted into the agent. // @@ -44,10 +25,10 @@ func (c *Connect) AddKeySshAgent(sshAgent interface{}, key interface{}) { } switch ag := sshAgent.(type) { - case agent.Agent: - ag.Add(addedKey) case agent.ExtendedAgent: ag.Add(addedKey) + case agent.Agent: + ag.Add(addedKey) } } @@ -55,11 +36,41 @@ func (c *Connect) AddKeySshAgent(sshAgent interface{}, key interface{}) { func (c *Connect) ForwardSshAgent(session *ssh.Session) { // forward ssh-agent switch ag := c.Agent.(type) { - case agent.Agent: - agent.ForwardToAgent(c.Client, ag) case agent.ExtendedAgent: agent.ForwardToAgent(c.Client, ag) + case agent.Agent: + agent.ForwardToAgent(c.Client, ag) } agent.RequestAgentForwarding(session) } + +func (c *Connect) ConnectSshAgent() { + sock, err := NewConn() + + if err != nil { + c.Agent = agent.NewKeyring() + } else { + defer sock.Close() + c.Agent = agent.NewClient(sock) + } +} + +/* +IdentityAgent + Specifies the UNIX-domain socket used to communicate with the + authentication agent. + + This option overrides the SSH_AUTH_SOCK environment variable and + can be used to select a specific agent. Setting the socket name + to none disables the use of an authentication agent. If the + string "SSH_AUTH_SOCK" is specified, the location of the socket + will be read from the SSH_AUTH_SOCK environment variable. + Otherwise if the specified value begins with a ‘$’ character, + then it will be treated as an environment variable containing the + location of the socket. + + Arguments to IdentityAgent may use the tilde syntax to refer to a + user's home directory or the tokens described in the TOKENS + section. +*/ diff --git a/agent_unix.go b/agent_unix.go new file mode 100644 index 0000000..e36aec6 --- /dev/null +++ b/agent_unix.go @@ -0,0 +1,20 @@ +// Copyright (c) 2021 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +//go:build !windows && !plan9 && !nacl +// +build !windows,!plan9,!nacl + +package sshlib + +import ( + "net" + "os" +) + +func NewConn() (sock net.Conn, err error) { + // Get env "SSH_AUTH_SOCK" and connect. + IdentityAgent := os.Getenv("SSH_AUTH_SOCK") + sock, err = net.Dial("unix", IdentityAgent) + + return +} diff --git a/agent_windows.go b/agent_windows.go new file mode 100644 index 0000000..ec9f668 --- /dev/null +++ b/agent_windows.go @@ -0,0 +1,47 @@ +// Copyright (c) 2021 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +//go:build windows +// +build windows + +package sshlib + +import ( + "net" + "os" + "strings" + + "github.com/Microsoft/go-winio" + "github.com/abakum/pageant" +) + +func NewConn() (sock net.Conn, err error) { + const ( + PIPE = `\\.\pipe\` + sshAgentPipe = "openssh-ssh-agent" + ) + // Get env "SSH_AUTH_SOCK" and connect. + IdentityAgent := os.Getenv("SSH_AUTH_SOCK") + emptySockPath := IdentityAgent == "" + + if emptySockPath { + sock, err = pageant.NewConn() + } + + if err != nil && !emptySockPath { + // `sc query afunix` for some versions of Windows + sock, err = net.Dial("unix", IdentityAgent) + } + + if err != nil { + if emptySockPath { + IdentityAgent = sshAgentPipe + } + if !strings.HasPrefix(IdentityAgent, PIPE) { + IdentityAgent = PIPE + IdentityAgent + } + sock, err = winio.DialPipe(IdentityAgent, nil) + } + return sock, err + +} diff --git a/auth.go b/auth.go index 0f82fb0..8b71af8 100644 --- a/auth.go +++ b/auth.go @@ -13,7 +13,7 @@ package sshlib import ( "fmt" - "io/ioutil" + "os" "regexp" "strings" @@ -39,6 +39,34 @@ func CreateAuthMethodPublicKey(key, password string) (auth ssh.AuthMethod, err e return } +// CreateAuthMethodCertificate returns ssh.AuthMethod generated from Certificate. +// To generate an AuthMethod from a certificate, you will need the certificate's private key Signer. +// Signer should be generated from CreateSignerPublicKey() or CreateSignerPKCS11(). +func CreateAuthMethodCertificate(cert string, keySigner ssh.Signer) (auth ssh.AuthMethod, err error) { + signer, err := CreateSignerCertificate(cert, keySigner) + if err != nil { + return + } + + auth = ssh.PublicKeys(signer) + return +} + +// CreateAuthMethodAgent returns ssh.AuthMethod from ssh-agent. +// case c.Agent is nil then ConnectSshAgent to it +func (c *Connect) CreateAuthMethodAgent() (auth ssh.AuthMethod) { + if c.Agent == nil { + c.ConnectSshAgent() + } + switch ag := c.Agent.(type) { + case agent.ExtendedAgent: + auth = ssh.PublicKeysCallback(ag.Signers) + case agent.Agent: + auth = ssh.PublicKeysCallback(ag.Signers) + } + return +} + // CreateSignerPublicKey returns []ssh.Signer generated from public key. // If you have not specified a passphrase, please specify a empty character(""). func CreateSignerPublicKey(key, password string) (signer ssh.Signer, err error) { @@ -46,7 +74,7 @@ func CreateSignerPublicKey(key, password string) (signer ssh.Signer, err error) key = getAbsPath(key) // Read PrivateKey file - keyData, err := ioutil.ReadFile(key) + keyData, err := os.ReadFile(key) if err != nil { return } @@ -60,7 +88,8 @@ func CreateSignerPublicKey(key, password string) (signer ssh.Signer, err error) func CreateSignerPublicKeyData(keyData []byte, password string) (signer ssh.Signer, err error) { if password != "" { // password is not empty // Parse key data - data, err := sshkeys.ParseEncryptedRawPrivateKey(keyData, []byte(password)) + var data interface{} + data, err = sshkeys.ParseEncryptedRawPrivateKey(keyData, []byte(password)) if err != nil { return signer, err } @@ -71,10 +100,10 @@ func CreateSignerPublicKeyData(keyData []byte, password string) (signer ssh.Sign signer, err = ssh.ParsePrivateKey(keyData) } - return + return signer, err } -// CreateSignerPublicKeyPrompt rapper CreateSignerPKCS11. +// CreateSignerPublicKeyPrompt wrapper CreateSignerPKCS11. // Output a passphrase input prompt if the passphrase is not entered or incorrect. // // Only Support UNIX-like OS. @@ -86,7 +115,7 @@ func CreateSignerPublicKeyPrompt(key, password string) (signer ssh.Signer, err e key = getAbsPath(key) // Read PrivateKey file - keyData, err := ioutil.ReadFile(key) + keyData, err := os.ReadFile(key) if err != nil { return } @@ -117,19 +146,6 @@ func CreateSignerPublicKeyPrompt(key, password string) (signer ssh.Signer, err e return } -// CreateAuthMethodCertificate returns ssh.AuthMethod generated from Certificate. -// To generate an AuthMethod from a certificate, you will need the certificate's private key Signer. -// Signer should be generated from CreateSignerPublicKey() or CreateSignerPKCS11(). -func CreateAuthMethodCertificate(cert string, keySigner ssh.Signer) (auth ssh.AuthMethod, err error) { - signer, err := CreateSignerCertificate(cert, keySigner) - if err != nil { - return - } - - auth = ssh.PublicKeys(signer) - return -} - // CreateSignerCertificate returns ssh.Signer generated from Certificate. // To generate an AuthMethod from a certificate, you will need the certificate's private key Signer. // Signer should be generated from CreateSignerPublicKey() or CreateSignerPKCS11(). @@ -138,7 +154,7 @@ func CreateSignerCertificate(cert string, keySigner ssh.Signer) (certSigner ssh. cert = getAbsPath(cert) // Read Cert file - certData, err := ioutil.ReadFile(cert) + certData, err := os.ReadFile(cert) if err != nil { return } @@ -152,7 +168,7 @@ func CreateSignerCertificate(cert string, keySigner ssh.Signer) (certSigner ssh. // Create Certificate Struct certificate, ok := pubkey.(*ssh.Certificate) if !ok { - err = fmt.Errorf("%s\n", "Error: Not create certificate struct data") + err = fmt.Errorf("%s", "Error: Not create certificate struct data") return } @@ -169,10 +185,10 @@ func CreateSignerCertificate(cert string, keySigner ssh.Signer) (certSigner ssh. // In sshAgent, put agent.Agent or agent.ExtendedAgent. func CreateSignerAgent(sshAgent interface{}) (signers []ssh.Signer, err error) { switch ag := sshAgent.(type) { - case agent.Agent: - signers, err = ag.Signers() case agent.ExtendedAgent: signers, err = ag.Signers() + case agent.Agent: + signers, err = ag.Signers() } return diff --git a/cmd.go b/cmd.go index 3f1fac0..8f000a6 100644 --- a/cmd.go +++ b/cmd.go @@ -59,7 +59,7 @@ func (c *Connect) Command(command string) (err error) { return } -// +// RequestTty, ForwardSshAgent, X11Forward func (c *Connect) setOption(session *ssh.Session) (err error) { // Request tty if c.TTY { @@ -69,11 +69,6 @@ func (c *Connect) setOption(session *ssh.Session) (err error) { } } - // ssh agent forwarding - if c.ForwardAgent { - c.ForwardSshAgent(session) - } - // x11 forwarding if c.ForwardX11 { err = c.X11Forward(session) @@ -83,5 +78,10 @@ func (c *Connect) setOption(session *ssh.Session) (err error) { err = nil } + // ssh agent forwarding + if c.ForwardAgent { + c.ForwardSshAgent(session) + } + return } diff --git a/cmd_etc.go b/cmd_etc.go new file mode 100644 index 0000000..c75ab4d --- /dev/null +++ b/cmd_etc.go @@ -0,0 +1,42 @@ +//go:build !windows +// +build !windows + +package sshlib + +import "github.com/abakum/go-ansiterm" + +func (c *Connect) CommandAnsi(command string, _, _ bool) (err error) { + return c.Command(command) +} + +// Output runs cmd on the remote host and returns its standard output. +func (c *Connect) Output(cmd string, pty bool) (bs []byte, err error) { + // create session + if c.Session == nil { + c.Session, err = c.CreateSession() + if err != nil { + return + } + } + tty := c.TTY + c.TTY = pty + + defer func() { + c.Session = nil + c.TTY = tty + }() + + // setup options + err = c.setOption(c.Session) + if err != nil { + return + } + bs, err = c.Session.Output(cmd) + if err != nil { + return + } + if pty { + bs, err = ansiterm.Strip(bs, ansiterm.WithFe(true)) + } + return +} diff --git a/cmd_windows.go b/cmd_windows.go new file mode 100644 index 0000000..915d947 --- /dev/null +++ b/cmd_windows.go @@ -0,0 +1,94 @@ +//go:build windows +// +build windows + +package sshlib + +import ( + "os" + + "github.com/abakum/go-ansiterm" + termm "github.com/abakum/term" +) + +// CommandAnsi connect and run command over ssh for Windows without VTP. +// +// Output data is processed by channel because it is executed in parallel. If specification is troublesome, it is good to generate and process session from ssh package. +func (c *Connect) CommandAnsi(command string, emulate, fixOpenSSH bool) (err error) { + // create session + if c.Session == nil { + c.Session, err = c.CreateSession() + if err != nil { + return + } + } + defer func() { c.Session = nil }() + + // setup options + err = c.setOption(c.Session) + if err != nil { + return + } + + // Set Stdin, Stdout, Stderr... + std := termm.NewIOE() + defer std.Close() + c.Session.Stdin = std.ReadCloser() + + c.Session.Stdout = os.Stdout + c.Session.Stdout = os.Stderr + if emulate { + wo, do, err := termm.StdOE(os.Stdout) + if err == nil { + //Win7 + defer do.Close() + c.Session.Stdout = wo + } + + we, de, err := termm.StdOE(os.Stderr) + if err == nil { + defer de.Close() + c.Session.Stderr = we + } + } + if fixOpenSSH { + // fix sshd of OpenSSH + command += "&timeout/t 1" + } + + // Run Command + err = c.Session.Run(command) + + return +} + +// Output runs cmd on the remote host and returns its standard output. +func (c *Connect) Output(cmd string, pty bool) (bs []byte, err error) { + // create session + if c.Session == nil { + c.Session, err = c.CreateSession() + if err != nil { + return + } + } + tty := c.TTY + c.TTY = pty + + defer func() { + c.Session = nil + c.TTY = tty + }() + + // setup options + err = c.setOption(c.Session) + if err != nil { + return + } + bs, err = c.Session.Output(cmd) + if err != nil { + return + } + if pty { + bs, err = ansiterm.Strip(bs, ansiterm.WithFe(true)) + } + return +} diff --git a/common.go b/common.go index 5f54d3d..9d0c0f2 100644 --- a/common.go +++ b/common.go @@ -12,7 +12,7 @@ import ( "path/filepath" "strings" - "golang.org/x/crypto/ssh/terminal" + terminal "golang.org/x/term" ) // getAbsPath return absolute path convert. @@ -28,7 +28,7 @@ func getAbsPath(path string) string { // getPassphrase gets the passphrase from virtual terminal input and returns the result. Works only on UNIX-based OS. func getPassphrase(msg string) (input string, err error) { - fmt.Fprintf(os.Stderr, msg) + fmt.Fprint(os.Stderr, msg) // Open /dev/tty tty, err := os.Open("/dev/tty") diff --git a/connect.go b/connect.go index fac121e..77ca743 100644 --- a/connect.go +++ b/connect.go @@ -13,8 +13,9 @@ import ( "time" "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/crypto/ssh/agent" "golang.org/x/net/proxy" + "golang.org/x/term" ) // Connect structure to store contents about ssh connection. @@ -78,6 +79,18 @@ type Connect struct { // Forward x11 flag. ForwardX11 bool + // Version contains the version identification string that will + // be used for the connection. If empty, a reasonable default is used. + Version string + + // HostKeyCallback is the function type used for verifying server keys. + // A HostKeyCallback must return nil if the host key is OK, or an error to reject it. + // It receives the hostname as passed to Dial or NewClientConn. + // The remote address is the RemoteAddr of the net.Conn underlying the SSH connection. + HostKeyCallback ssh.HostKeyCallback + + HostKeyAlgorithms []string + // shell terminal log flag logging bool @@ -114,7 +127,23 @@ func (c *Connect) CreateClient(host, port, user string, authMethods []ssh.AuthMe } config.HostKeyCallback = c.verifyAndAppendNew } else { - config.HostKeyCallback = ssh.InsecureIgnoreHostKey() + if c.HostKeyCallback == nil { + config.HostKeyCallback = ssh.InsecureIgnoreHostKey() + } else { + config.HostKeyCallback = c.HostKeyCallback + if c.HostKeyAlgorithms != nil { + config.HostKeyAlgorithms = c.HostKeyAlgorithms + } + } + } + + if c.Agent != nil { + switch ag := c.Agent.(type) { + case agent.ExtendedAgent: + config.Auth = append(config.Auth, ssh.PublicKeysCallback(ag.Signers)) + case agent.Agent: + config.Auth = append(config.Auth, ssh.PublicKeysCallback(ag.Signers)) + } } // check Dialer @@ -128,6 +157,11 @@ func (c *Connect) CreateClient(host, port, user string, authMethods []ssh.AuthMe return } + // check Version + if c.Version != "" { + config.ClientVersion = "SSH-2.0-" + c.Version + } + // Create new ssh connect sshCon, channel, req, err := ssh.NewClientConn(netConn, uri, config) if err != nil { @@ -197,7 +231,6 @@ func (c *Connect) CheckClientAlive() error { // RequestTty requests the association of a pty with the session on the remote // host. Terminal size is obtained from the currently connected terminal -// func RequestTty(session *ssh.Session) (err error) { modes := ssh.TerminalModes{ ssh.ECHO: 1, @@ -207,18 +240,18 @@ func RequestTty(session *ssh.Session) (err error) { // Get terminal window size fd := int(os.Stdout.Fd()) - width, hight, err := terminal.GetSize(fd) + width, hight, err := term.GetSize(fd) if err != nil { return } // Get env `TERM` - term := os.Getenv("TERM") - if len(term) == 0 { - term = "xterm" + xterm := os.Getenv("TERM") + if len(xterm) == 0 { + xterm = "xterm-256color" } - if err = session.RequestPty(term, hight, width, modes); err != nil { + if err = session.RequestPty(xterm, hight, width, modes); err != nil { session.Close() return } @@ -233,7 +266,7 @@ func RequestTty(session *ssh.Session) (err error) { switch s { case winch: fd := int(os.Stdout.Fd()) - width, hight, _ = terminal.GetSize(fd) + width, hight, _ = term.GetSize(fd) session.WindowChange(hight, width) } } diff --git a/doc.go b/doc.go index f578570..73c713f 100644 --- a/doc.go +++ b/doc.go @@ -6,7 +6,7 @@ Package sshlib is a library to easily connect with ssh by go. You can perform multiple proxy, x11 forwarding, PKCS11 authentication, etc... -Example simple ssh shell +# Example simple ssh shell It is example code. simple connect ssh shell. You can also do tab completion, send sigint signal(Ctrl+C). @@ -16,7 +16,7 @@ It is example code. simple connect ssh shell. You can also do tab completion, se "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + sshlib "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) @@ -43,7 +43,7 @@ It is example code. simple connect ssh shell. You can also do tab completion, se // Create ssh.AuthMethod authMethod := sshlib.CreateAuthMethodPassword(password) - // If you use ssh-agent forwarding, uncomment it. + // If you use ssh-agent, uncomment it. // con.ConnectSshAgent() // Connect ssh server @@ -60,9 +60,7 @@ It is example code. simple connect ssh shell. You can also do tab completion, se con.Shell() } - - -Example simple ssh proxy shell +# Example simple ssh proxy shell Multple proxy by ssh connection is also available. Please refer to the sample code for usage with http and socks5 proxy. @@ -72,7 +70,7 @@ Multple proxy by ssh connection is also available. Please refer to the sample co "fmt" "os" - sshlib "github.com/blacknon/go-sshlib" + sshlib "github.com/abakum/go-sshlib" "golang.org/x/crypto/ssh" ) @@ -136,7 +134,6 @@ Multple proxy by ssh connection is also available. Please refer to the sample co targetCon.Shell() } - This library was created for my ssh client (https://github.com/blacknon/lssh) */ package sshlib diff --git a/forward.go b/forward.go index c32b41a..72c12f0 100644 --- a/forward.go +++ b/forward.go @@ -152,7 +152,7 @@ func readAuthority(hostname, display string) ( if len(fname) == 0 { home := os.Getenv("HOME") if len(home) == 0 { - err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set") + err = errors.New(" Xauthority not found: $XAUTHORITY, $HOME not set") return "", nil, err } fname = home + "/.Xauthority" @@ -226,7 +226,11 @@ func getString(r io.Reader, b []byte) (string, error) { // example) "127.0.0.1:22", "abc.com:9977" func (c *Connect) TCPLocalForward(localAddr, remoteAddr string) (err error) { // create listner - listner, err := net.Listen("tcp", localAddr) + var ( + local, remote net.Conn + listner net.Listener + ) + listner, err = net.Listen("tcp", localAddr) if err != nil { return } @@ -235,13 +239,13 @@ func (c *Connect) TCPLocalForward(localAddr, remoteAddr string) (err error) { go func() { for { // local (type net.Conn) - local, err := listner.Accept() + local, err = listner.Accept() if err != nil { return } // remote (type net.Conn) - remote, err := c.Client.Dial("tcp", remoteAddr) + remote, err = c.Client.Dial("tcp", remoteAddr) // forward go c.forwarder(local, remote) diff --git a/forward_test.go b/forward_test.go index 4a11169..6cad693 100644 --- a/forward_test.go +++ b/forward_test.go @@ -24,7 +24,7 @@ func TestGetDisplay(t *testing.T) { } } -func ExampleConnect_TCPForward() { +func ExampleConnect_TCPLocalForward() { // host host := "target.com" port := "22" diff --git a/go.mod b/go.mod index 8a7e937..747251f 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,31 @@ -module github.com/blacknon/go-sshlib +module github.com/abakum/go-sshlib + +// replace github.com/abakum/term => ../term require ( + github.com/Microsoft/go-winio v0.5.2 github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // TODO: マージされたらベースのリポジトリに変更する github.com/ThalesIgnite/crypto11 v1.2.5 + github.com/abakum/go-ansiterm v0.0.0-20240209124652-4fc46d492442 + github.com/abakum/pageant v0.0.0-20231124135236-c9f79a77a513 + github.com/abakum/term v0.0.0-20240212164236-135562d7e4cf github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/lunixbochs/vtclean v1.0.0 github.com/miekg/pkcs11 v1.1.1 - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/net v0.0.0-20220526153639-5463443f8c37 - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a + golang.org/x/crypto v0.19.0 + golang.org/x/net v0.10.0 + golang.org/x/sys v0.17.0 + golang.org/x/term v0.17.0 ) require ( - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.7.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect - golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.17 +go 1.21.4 replace github.com/ThalesIgnite/crypto11 v1.2.5 => github.com/blacknon/crypto11 v1.2.6 diff --git a/go.sum b/go.sum index d07da75..269529e 100644 --- a/go.sum +++ b/go.sum @@ -1,69 +1,87 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 h1:VauE2GcJNZFun2Och6tIT2zJZK1v6jxALQDA9BIji/E= github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5/go.mod h1:gxOHeajFfvGQh/fxlC8oOKBe23xnnJTif00IFFbiT+o= +github.com/abakum/go-ansiterm v0.0.0-20240209124652-4fc46d492442 h1:SsivDZBadH7DqG/Wfilz3+HW/WBuEz4Iw+T6yqo5uvU= +github.com/abakum/go-ansiterm v0.0.0-20240209124652-4fc46d492442/go.mod h1:WzQf6EqfkRdEyEveQ6edF8KNbe3UW1jVDBmV2uVkNLs= +github.com/abakum/pageant v0.0.0-20231124135236-c9f79a77a513 h1:CYzvRvNCO1Cho3f8UR3k9PbeIkzlGM69MQLDE1Rd4Z0= +github.com/abakum/pageant v0.0.0-20231124135236-c9f79a77a513/go.mod h1:LocqZdC4qlU9eXgFamJgIeJ4Z5RK29LS4zuAbJ9RIAw= +github.com/abakum/term v0.0.0-20240212164236-135562d7e4cf h1:gzZvsnAL6dxpaIg508x7sJHBv1nSaUCArB8zqYrCmio= +github.com/abakum/term v0.0.0-20240212164236-135562d7e4cf/go.mod h1:NnfnOO9uPC2m1Yzao/8lJXaNJlDKjU5bsYeFabF1IQg= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/blacknon/crypto11 v1.2.6 h1:Mv+Boto0qVR1O2k5lmoCYcyXfiQYY7mQ1P/TcK5Tw5c= github.com/blacknon/crypto11 v1.2.6/go.mod h1:HThRIRjHpJIJwcExGgNuPCyf26HqcFVTTAnipaXWz7M= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= -github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= -golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/knownhosts.go b/knownhosts.go index cf7fff6..2aa07e6 100644 --- a/knownhosts.go +++ b/knownhosts.go @@ -6,6 +6,7 @@ package sshlib import ( "bufio" + "bytes" "fmt" "net" "os" @@ -81,7 +82,7 @@ func (c *Connect) verifyAndAppendNew(hostname string, remote net.Addr, key ssh.P // check error keyErr, ok := err.(*knownhosts.KeyError) - if !ok || len(keyErr.Want) > 0 { + if keyErr != nil && (!ok || len(keyErr.Want) > 0) { for _, w := range keyErr.Want { oldkey := w.String() line = w.Line @@ -105,7 +106,7 @@ func (c *Connect) verifyAndAppendNew(hostname string, remote net.Addr, key ssh.P line = 0 } - err = writeKnownHostsKey(filepath, line, hostname, remote, key) + writeKnownHostsKey(filepath, line, hostname, remote, key) return nil } @@ -268,3 +269,30 @@ func writeKnownHostsKey(filepath string, linenum int, hostname string, remote ne return } + +type hostKeys struct { + keys []ssh.PublicKey +} + +func (f *hostKeys) check(hostname string, remote net.Addr, key ssh.PublicKey) error { + if len(f.keys) == 0 { + return fmt.Errorf("ssh: no required host keys") + } + km := key.Marshal() + for _, fKey := range f.keys { + if fKey == nil { + continue + } + if bytes.Equal(km, fKey.Marshal()) { + return nil + } + } + return fmt.Errorf("ssh: no one host key from %v match %s", f.keys, ssh.FingerprintSHA256(key)) +} + +// HostKeyCallback returns a function for use in +// ClientConfig.HostKeyCallback to accept specific host keys. +func HostKeyCallback(keys ...ssh.PublicKey) ssh.HostKeyCallback { + hk := &hostKeys{keys} + return hk.check +} diff --git a/proxy.go b/proxy.go index 70ce44f..4b54695 100644 --- a/proxy.go +++ b/proxy.go @@ -16,7 +16,6 @@ import ( "golang.org/x/net/proxy" ) -// type Proxy struct { // Type set proxy type. // Can specify `http`, `https`, `socks`, `socks5`, `command`. @@ -49,7 +48,6 @@ type Proxy struct { } // CreateProxyDialer retrun proxy.Dialer. -// func (p *Proxy) CreateProxyDialer() (proxyDialer proxy.Dialer, err error) { switch p.Type { case "http", "https": @@ -64,7 +62,6 @@ func (p *Proxy) CreateProxyDialer() (proxyDialer proxy.Dialer, err error) { } // CreateHttpProxy return proxy.Dialer as http proxy. -// func (p *Proxy) CreateHttpProxyDialer() (proxyDialer proxy.Dialer, err error) { // Regist dialer proxy.RegisterDialerType("http", newHttpProxy) @@ -92,13 +89,14 @@ func (p *Proxy) CreateHttpProxyDialer() (proxyDialer proxy.Dialer, err error) { } // CreateSocks5Proxy return proxy.Dialer as Socks5 proxy. -// func (p *Proxy) CreateSocks5ProxyDialer() (proxyDialer proxy.Dialer, err error) { var proxyAuth *proxy.Auth if p.User != "" && p.Password != "" { - proxyAuth.User = p.User - proxyAuth.Password = p.Password + proxyAuth = &proxy.Auth{ + User: p.User, + Password: p.Password, + } } var forwarder proxy.Dialer @@ -112,7 +110,6 @@ func (p *Proxy) CreateSocks5ProxyDialer() (proxyDialer proxy.Dialer, err error) // CreateProxyCommandProxyDialer as ProxyCommand. // When passing ProxyCommand, replace %h, %p and %r etc... -// func (p *Proxy) CreateProxyCommandProxyDialer() (proxyDialer proxy.Dialer, err error) { np := new(NetPipe) np.Command = p.Command @@ -121,15 +118,13 @@ func (p *Proxy) CreateProxyCommandProxyDialer() (proxyDialer proxy.Dialer, err e return } -// type NetPipe struct { Command string } -// func (n *NetPipe) Dial(network, addr string) (con net.Conn, err error) { - network = "" - addr = "" + // network = "" + // addr = "" // Create net.Pipe(), and set proxyCommand con, srv := net.Pipe() @@ -147,7 +142,6 @@ func (n *NetPipe) Dial(network, addr string) (con net.Conn, err error) { return } -// type httpProxy struct { host string haveAuth bool @@ -157,7 +151,6 @@ type httpProxy struct { } // Dial return net.Conn via http proxy. -// func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { c, err := s.forward.Dial("tcp", s.host) if err != nil { diff --git a/shell.go b/shell.go index 9fa61f9..045dd29 100644 --- a/shell.go +++ b/shell.go @@ -12,20 +12,33 @@ import ( "os" "time" - "github.com/lunixbochs/vtclean" + "github.com/abakum/go-ansiterm" "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) // Shell connect login shell over ssh. +// If session is nil then session will be created. func (c *Connect) Shell(session *ssh.Session) (err error) { + if session == nil && c.Session == nil { + session, err = c.CreateSession() + if err != nil { + return + } + c.Session = session + } + defer func() { c.Session = nil }() + // Input terminal Make raw fd := int(os.Stdin.Fd()) - state, err := terminal.MakeRaw(fd) + state, err := term.MakeRaw(fd) if err != nil { return } - defer terminal.Restore(fd, state) + defer term.Restore(fd, state) + + // set FD + session.Stdin, session.Stdout, session.Stderr = GetStdin(), os.Stdout, os.Stderr // setup err = c.setupShell(session) @@ -43,23 +56,32 @@ func (c *Connect) Shell(session *ssh.Session) (err error) { go c.SendKeepAlive(session) err = session.Wait() - if err != nil { - return - } - return } // Shell connect command shell over ssh. // Used to start a shell with a specified command. +// If session is nil then session will be created. func (c *Connect) CmdShell(session *ssh.Session, command string) (err error) { + if session == nil && c.Session == nil { + session, err = c.CreateSession() + if err != nil { + return + } + c.Session = session + } + defer func() { c.Session = nil }() + // Input terminal Make raw fd := int(os.Stdin.Fd()) - state, err := terminal.MakeRaw(fd) + state, err := term.MakeRaw(fd) if err != nil { return } - defer terminal.Restore(fd, state) + defer term.Restore(fd, state) + + // set FD + session.Stdin, session.Stdout, session.Stderr = GetStdin(), os.Stdout, os.Stderr // setup err = c.setupShell(session) @@ -134,14 +156,11 @@ func (c *Connect) logger(session *ssh.Session) (err error) { // remove ansi code. if c.logRemoveAnsiCode { - // NOTE: - // In vtclean.Clean, the beginning of the line is deleted for some reason. - // for that reason, one character add at line head. - printLine = "." + printLine - printLine = vtclean.Clean(printLine, false) + printLine, _ = ansiterm.StripBytes([]byte(printLine), ansiterm.WithFe(true)) + printLine += "\n" } - fmt.Fprintf(logfile, printLine) + fmt.Fprint(logfile, printLine) preLine = []byte{} } } else { @@ -154,13 +173,8 @@ func (c *Connect) logger(session *ssh.Session) (err error) { return err } +// logger, RequestTty, ForwardX11, ForwardAgent func (c *Connect) setupShell(session *ssh.Session) (err error) { - // set FD - stdin := GetStdin() - session.Stdin = stdin - session.Stdout = os.Stdout - session.Stderr = os.Stderr - // Logging if c.logging { err = c.logger(session) @@ -182,8 +196,8 @@ func (c *Connect) setupShell(session *ssh.Session) (err error) { if err != nil { log.Println(err) } + err = nil } - err = nil // ssh agent forwarding if c.ForwardAgent { diff --git a/shell_etc.go b/shell_etc.go new file mode 100644 index 0000000..0ab51bc --- /dev/null +++ b/shell_etc.go @@ -0,0 +1,10 @@ +//go:build !windows +// +build !windows + +package sshlib + +import "golang.org/x/crypto/ssh" + +func (c *Connect) ShellAnsi(session *ssh.Session, _ bool) (err error) { + return c.Shell(session) +} diff --git a/shell_windows.go b/shell_windows.go new file mode 100644 index 0000000..883fa0a --- /dev/null +++ b/shell_windows.go @@ -0,0 +1,65 @@ +//go:build windows +// +build windows + +package sshlib + +import ( + "os" + + termm "github.com/abakum/term" + "golang.org/x/crypto/ssh" +) + +// ShellAnsi connect login shell over ssh for Windows without VTP +// If session is nil then session will be created. +func (c *Connect) ShellAnsi(session *ssh.Session, emulate bool) (err error) { + // create session + if session == nil && c.Session == nil { + session, err = c.CreateSession() + if err != nil { + return + } + c.Session = session + } + defer func() { c.Session = nil }() + + // Set Stdin, Stdout, Stderr... + std := termm.NewIOE() + defer std.Close() + c.Session.Stdin = std.ReadCloser() + + c.Session.Stdout = os.Stdout + c.Session.Stdout = os.Stderr + if emulate { + wo, do, err := termm.StdOE(os.Stdout) + if err == nil { + //Win7 + defer do.Close() + c.Session.Stdout = wo + } + + we, de, err := termm.StdOE(os.Stderr) + if err == nil { + defer de.Close() + c.Session.Stderr = we + } + } + + // setup + err = c.setupShell(c.Session) + if err != nil { + return + } + + // Start shell + err = c.Session.Shell() + if err != nil { + return + } + + // keep alive packet + go c.SendKeepAlive(c.Session) + + err = c.Session.Wait() + return +} diff --git a/stdin_windows.go b/stdin_windows.go index a70c726..f9cc9a2 100644 --- a/stdin_windows.go +++ b/stdin_windows.go @@ -9,13 +9,13 @@ package sshlib import ( "io" - windowsconsole "github.com/moby/term/windows" + termw "github.com/abakum/term/windows" "golang.org/x/sys/windows" ) func GetStdin() io.ReadCloser { h := uint32(windows.STD_INPUT_HANDLE) - stdin := windowsconsole.NewAnsiReader(int(h)) + stdin := termw.NewAnsiReader(int(h)) return stdin }