Files

107 lines
2.3 KiB
Go

package ssh
import (
"context"
"errors"
"fmt"
"net"
"net/url"
"os"
"golang.org/x/crypto/ssh"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/utils"
)
type SSH struct {
addr string
config *ssh.ClientConfig
}
func New(addr, user, pass, keyFile, passphrase string) (*SSH, error) {
var auth []ssh.AuthMethod
if pass != "" {
auth = append(auth, ssh.Password(pass))
}
if keyFile != "" {
key, err := os.ReadFile(keyFile)
if err != nil {
return nil, fmt.Errorf("ssh: read file: %w", err)
}
var signer ssh.Signer
if passphrase != "" {
signer, err = ssh.ParsePrivateKeyWithPassphrase(
key, []byte(passphrase))
} else {
signer, err = ssh.ParsePrivateKey(key)
}
if err != nil {
return nil, fmt.Errorf("ssh: parse private key: %w", err)
}
auth = append(auth, ssh.PublicKeys(signer))
}
return &SSH{
addr: addr,
config: &ssh.ClientConfig{
User: user,
Auth: auth,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: utils.TCPConnectTimeout,
},
}, nil
}
func (s *SSH) DialContext(ctx context.Context, metadata *M.Metadata) (_ net.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", s.addr, err)
}
utils.SetKeepAlive(c)
defer func(c net.Conn) {
utils.SafeConnClose(c, err)
}(c)
sc, ch, reqs, err := ssh.NewClientConn(c, s.addr, s.config)
if err != nil {
return nil, err
}
client := ssh.NewClient(sc, ch, reqs)
conn, err := client.Dial("tcp", metadata.DestinationAddress())
if err != nil {
client.Close()
return nil, err
}
return &sshConn{conn, client}, nil
}
func (s *SSH) DialUDP(*M.Metadata) (net.PacketConn, error) {
return nil, errors.ErrUnsupported
}
type sshConn struct {
net.Conn
client *ssh.Client
}
func (c *sshConn) Close() error {
defer c.client.Close()
return c.Conn.Close()
}
func Parse(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
keyFile := u.Query().Get("privateKeyFile")
passphrase := u.Query().Get("passphrase")
return New(address, username, password, keyFile, passphrase)
}
func init() {
proxy.RegisterProtocol("ssh", Parse)
}