2020-05-04 13:53:59 +08:00
|
|
|
package ssh
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net"
|
|
|
|
"net/url"
|
2021-02-06 00:26:58 +08:00
|
|
|
"os"
|
2021-04-20 20:55:40 +08:00
|
|
|
"sync"
|
2020-05-04 13:53:59 +08:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
|
2020-10-01 22:49:14 +08:00
|
|
|
"github.com/nadoo/glider/log"
|
2020-05-04 13:53:59 +08:00
|
|
|
"github.com/nadoo/glider/proxy"
|
|
|
|
)
|
|
|
|
|
2020-05-06 20:10:18 +08:00
|
|
|
// SSH is a base ssh struct.
|
2020-05-04 13:53:59 +08:00
|
|
|
type SSH struct {
|
|
|
|
dialer proxy.Dialer
|
|
|
|
proxy proxy.Proxy
|
|
|
|
addr string
|
2021-04-20 15:28:52 +08:00
|
|
|
|
2021-04-20 20:55:40 +08:00
|
|
|
mu sync.Mutex
|
2021-04-20 15:28:52 +08:00
|
|
|
sshCfg *ssh.ClientConfig
|
|
|
|
sshConn ssh.Conn
|
|
|
|
sshChan <-chan ssh.NewChannel
|
|
|
|
sshReq <-chan *ssh.Request
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
proxy.RegisterDialer("ssh", NewSSHDialer)
|
|
|
|
}
|
|
|
|
|
2020-05-06 20:10:18 +08:00
|
|
|
// NewSSH returns a ssh proxy.
|
2020-05-04 13:53:59 +08:00
|
|
|
func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
|
|
|
u, err := url.Parse(s)
|
|
|
|
if err != nil {
|
|
|
|
log.F("parse err: %s", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
user := u.User.Username()
|
|
|
|
if user == "" {
|
|
|
|
user = "root"
|
|
|
|
}
|
|
|
|
|
|
|
|
config := &ssh.ClientConfig{
|
|
|
|
User: user,
|
|
|
|
Timeout: time.Second * 3,
|
|
|
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if pass, _ := u.User.Password(); pass != "" {
|
|
|
|
config.Auth = []ssh.AuthMethod{ssh.Password(pass)}
|
|
|
|
}
|
|
|
|
|
2020-05-04 15:33:26 +08:00
|
|
|
if key := u.Query().Get("key"); key != "" {
|
|
|
|
keyAuth, err := privateKeyAuth(key)
|
|
|
|
if err != nil {
|
|
|
|
log.F("[ssh] read key file error: %s", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
config.Auth = append(config.Auth, keyAuth)
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
2021-04-20 15:28:52 +08:00
|
|
|
t := &SSH{
|
2020-05-04 13:53:59 +08:00
|
|
|
dialer: d,
|
|
|
|
proxy: p,
|
|
|
|
addr: u.Host,
|
2021-04-20 15:28:52 +08:00
|
|
|
sshCfg: config,
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, port, _ := net.SplitHostPort(t.addr); port == "" {
|
|
|
|
t.addr = net.JoinHostPort(t.addr, "22")
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
2021-04-20 15:28:52 +08:00
|
|
|
return t, t.initConn()
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewSSHDialer returns a ssh proxy dialer.
|
|
|
|
func NewSSHDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
|
|
|
return NewSSH(s, d, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Addr returns forwarder's address.
|
|
|
|
func (s *SSH) Addr() string {
|
|
|
|
if s.addr == "" {
|
|
|
|
return s.dialer.Addr()
|
|
|
|
}
|
|
|
|
return s.addr
|
|
|
|
}
|
|
|
|
|
2021-04-20 15:28:52 +08:00
|
|
|
func (s *SSH) initConn() error {
|
2021-04-20 20:55:40 +08:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
2021-04-20 15:28:52 +08:00
|
|
|
c, err := s.dialer.Dial("tcp", s.addr)
|
2020-05-04 13:53:59 +08:00
|
|
|
if err != nil {
|
|
|
|
log.F("[ssh]: dial to %s error: %s", s.addr, err)
|
2021-04-20 15:28:52 +08:00
|
|
|
return err
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
2021-04-20 15:28:52 +08:00
|
|
|
s.sshConn, s.sshChan, s.sshReq, err = ssh.NewClientConn(c, s.addr, s.sshCfg)
|
2020-05-04 13:53:59 +08:00
|
|
|
if err != nil {
|
|
|
|
log.F("[ssh]: initial connection to %s error: %s", s.addr, err)
|
2021-04-20 15:28:52 +08:00
|
|
|
return err
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
2021-04-20 15:28:52 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dial connects to the address addr on the network net via the proxy.
|
2021-04-20 20:55:40 +08:00
|
|
|
func (s *SSH) Dial(network, addr string) (net.Conn, error) {
|
|
|
|
if c, err := ssh.NewClient(s.sshConn, s.sshChan, s.sshReq).Dial(network, addr); err == nil {
|
|
|
|
return c, nil
|
2021-04-20 15:28:52 +08:00
|
|
|
}
|
2021-04-20 20:55:40 +08:00
|
|
|
s.sshConn.Close()
|
|
|
|
s.initConn()
|
|
|
|
return ssh.NewClient(s.sshConn, s.sshChan, s.sshReq).Dial(network, addr)
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// DialUDP connects to the given address via the proxy.
|
|
|
|
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
2020-12-02 19:00:39 +08:00
|
|
|
return nil, nil, proxy.ErrNotSupported
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
2020-05-04 15:33:26 +08:00
|
|
|
func privateKeyAuth(file string) (ssh.AuthMethod, error) {
|
2021-02-06 00:26:58 +08:00
|
|
|
buffer, err := os.ReadFile(file)
|
2020-05-04 13:53:59 +08:00
|
|
|
if err != nil {
|
2020-05-04 15:33:26 +08:00
|
|
|
return nil, err
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
key, err := ssh.ParsePrivateKey(buffer)
|
|
|
|
if err != nil {
|
2020-05-04 15:33:26 +08:00
|
|
|
return nil, err
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|
|
|
|
|
2020-05-04 15:33:26 +08:00
|
|
|
return ssh.PublicKeys(key), nil
|
2020-05-04 13:53:59 +08:00
|
|
|
}
|