ssh.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package outbound
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "math/rand"
  7. "net"
  8. "os"
  9. "strconv"
  10. "sync"
  11. "github.com/sagernet/sing-box/adapter"
  12. "github.com/sagernet/sing-box/common/dialer"
  13. C "github.com/sagernet/sing-box/constant"
  14. "github.com/sagernet/sing-box/log"
  15. "github.com/sagernet/sing-box/option"
  16. "github.com/sagernet/sing/common"
  17. E "github.com/sagernet/sing/common/exceptions"
  18. M "github.com/sagernet/sing/common/metadata"
  19. N "github.com/sagernet/sing/common/network"
  20. "golang.org/x/crypto/ssh"
  21. )
  22. var (
  23. _ adapter.Outbound = (*SSH)(nil)
  24. _ adapter.InterfaceUpdateListener = (*SSH)(nil)
  25. )
  26. type SSH struct {
  27. myOutboundAdapter
  28. ctx context.Context
  29. dialer N.Dialer
  30. serverAddr M.Socksaddr
  31. user string
  32. hostKey []ssh.PublicKey
  33. hostKeyAlgorithms []string
  34. clientVersion string
  35. authMethod []ssh.AuthMethod
  36. clientAccess sync.Mutex
  37. clientConn net.Conn
  38. client *ssh.Client
  39. }
  40. func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) {
  41. outbound := &SSH{
  42. myOutboundAdapter: myOutboundAdapter{
  43. protocol: C.TypeSSH,
  44. network: []string{N.NetworkTCP},
  45. router: router,
  46. logger: logger,
  47. tag: tag,
  48. },
  49. ctx: ctx,
  50. dialer: dialer.New(router, options.DialerOptions),
  51. serverAddr: options.ServerOptions.Build(),
  52. user: options.User,
  53. hostKeyAlgorithms: options.HostKeyAlgorithms,
  54. clientVersion: options.ClientVersion,
  55. }
  56. if outbound.serverAddr.Port == 0 {
  57. outbound.serverAddr.Port = 22
  58. }
  59. if outbound.user == "" {
  60. outbound.user = "root"
  61. }
  62. if outbound.clientVersion == "" {
  63. outbound.clientVersion = randomVersion()
  64. }
  65. if options.Password != "" {
  66. outbound.authMethod = append(outbound.authMethod, ssh.Password(options.Password))
  67. }
  68. if options.PrivateKey != "" || options.PrivateKeyPath != "" {
  69. var privateKey []byte
  70. if options.PrivateKey != "" {
  71. privateKey = []byte(options.PrivateKey)
  72. } else {
  73. var err error
  74. privateKey, err = os.ReadFile(os.ExpandEnv(options.PrivateKeyPath))
  75. if err != nil {
  76. return nil, E.Cause(err, "read private key")
  77. }
  78. }
  79. var signer ssh.Signer
  80. var err error
  81. if options.PrivateKeyPassphrase == "" {
  82. signer, err = ssh.ParsePrivateKey(privateKey)
  83. } else {
  84. signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(options.PrivateKeyPassphrase))
  85. }
  86. if err != nil {
  87. return nil, E.Cause(err, "parse private key")
  88. }
  89. outbound.authMethod = append(outbound.authMethod, ssh.PublicKeys(signer))
  90. }
  91. if len(options.HostKey) > 0 {
  92. for _, hostKey := range options.HostKey {
  93. key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(hostKey))
  94. if err != nil {
  95. return nil, E.New("parse host key ", key)
  96. }
  97. outbound.hostKey = append(outbound.hostKey, key)
  98. }
  99. }
  100. return outbound, nil
  101. }
  102. func randomVersion() string {
  103. version := "SSH-2.0-OpenSSH_"
  104. if rand.Intn(2) == 0 {
  105. version += "7." + strconv.Itoa(rand.Intn(10))
  106. } else {
  107. version += "8." + strconv.Itoa(rand.Intn(9))
  108. }
  109. return version
  110. }
  111. func (s *SSH) connect() (*ssh.Client, error) {
  112. if s.client != nil {
  113. return s.client, nil
  114. }
  115. s.clientAccess.Lock()
  116. defer s.clientAccess.Unlock()
  117. if s.client != nil {
  118. return s.client, nil
  119. }
  120. conn, err := s.dialer.DialContext(s.ctx, N.NetworkTCP, s.serverAddr)
  121. if err != nil {
  122. return nil, err
  123. }
  124. config := &ssh.ClientConfig{
  125. User: s.user,
  126. Auth: s.authMethod,
  127. ClientVersion: s.clientVersion,
  128. HostKeyAlgorithms: s.hostKeyAlgorithms,
  129. HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
  130. if len(s.hostKey) == 0 {
  131. return nil
  132. }
  133. serverKey := key.Marshal()
  134. for _, hostKey := range s.hostKey {
  135. if bytes.Equal(serverKey, hostKey.Marshal()) {
  136. return nil
  137. }
  138. }
  139. return E.New("host key mismatch, server send ", key.Type(), " ", base64.StdEncoding.EncodeToString(serverKey))
  140. },
  141. }
  142. clientConn, chans, reqs, err := ssh.NewClientConn(conn, s.serverAddr.Addr.String(), config)
  143. if err != nil {
  144. conn.Close()
  145. return nil, E.Cause(err, "connect to ssh server")
  146. }
  147. client := ssh.NewClient(clientConn, chans, reqs)
  148. s.clientConn = conn
  149. s.client = client
  150. go func() {
  151. client.Wait()
  152. conn.Close()
  153. s.clientAccess.Lock()
  154. s.client = nil
  155. s.clientConn = nil
  156. s.clientAccess.Unlock()
  157. }()
  158. return client, nil
  159. }
  160. func (s *SSH) InterfaceUpdated() error {
  161. common.Close(s.clientConn)
  162. return nil
  163. }
  164. func (s *SSH) Close() error {
  165. return common.Close(s.clientConn)
  166. }
  167. func (s *SSH) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
  168. client, err := s.connect()
  169. if err != nil {
  170. return nil, err
  171. }
  172. return client.Dial(network, destination.String())
  173. }
  174. func (s *SSH) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
  175. return nil, os.ErrInvalid
  176. }
  177. func (s *SSH) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
  178. return NewConnection(ctx, s, conn, metadata)
  179. }
  180. func (s *SSH) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
  181. return os.ErrInvalid
  182. }