client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "net"
  8. "os"
  9. "os/user"
  10. "path"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "syscall"
  15. "time"
  16. "git.wecise.com/wecise/common/matrix/logger"
  17. "git.wecise.com/wecise/common/matrix/util"
  18. "golang.org/x/crypto/ssh"
  19. "golang.org/x/term"
  20. )
  21. var (
  22. DefaultCiphers = []string{
  23. "aes128-ctr",
  24. "aes192-ctr",
  25. "aes256-ctr",
  26. "aes128-gcm@openssh.com",
  27. "chacha20-poly1305@openssh.com",
  28. "arcfour256",
  29. "arcfour128",
  30. "arcfour",
  31. "aes128-cbc",
  32. "3des-cbc",
  33. "blowfish-cbc",
  34. "cast128-cbc",
  35. "aes192-cbc",
  36. "aes256-cbc",
  37. }
  38. )
  39. var regxyesno = &Regexp{regexp.MustCompile(`.*(\(yes\/no\)\?)\s*$`)}
  40. var regxpassword = &Regexp{regexp.MustCompile(`.*([Pp]assword:)\s*$`)}
  41. var regxprompt = &Regexp{regexp.MustCompile(`.*([%#\$\>])\s*$`)}
  42. type Client interface {
  43. Login()
  44. }
  45. type defaultClient struct {
  46. clientConfig *ssh.ClientConfig
  47. node *Node
  48. }
  49. func genSSHConfig(node *Node) *defaultClient {
  50. u, err := user.Current()
  51. if err != nil {
  52. logger.Error(util.ErrorWithSourceLine(err))
  53. return nil
  54. }
  55. var authMethods []ssh.AuthMethod
  56. var pemBytes []byte
  57. if node.KeyPath == "" {
  58. pemBytes, err = os.ReadFile(path.Join(u.HomeDir, ".ssh/id_rsa"))
  59. } else {
  60. pemBytes, err = os.ReadFile(node.KeyPath)
  61. }
  62. if err != nil && !os.IsNotExist(err) {
  63. logger.Error(util.ErrorWithSourceLine(err))
  64. } else if len(pemBytes) > 0 {
  65. var signer ssh.Signer
  66. if node.Passphrase != "" {
  67. signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(node.Passphrase))
  68. } else {
  69. signer, err = ssh.ParsePrivateKey(pemBytes)
  70. }
  71. if err != nil {
  72. logger.Error(util.ErrorWithSourceLine(err))
  73. } else {
  74. authMethods = append(authMethods, ssh.PublicKeys(signer))
  75. }
  76. }
  77. password := node.password()
  78. if password != nil {
  79. authMethods = append(authMethods, password)
  80. }
  81. authMethods = append(authMethods, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
  82. answers := make([]string, 0, len(questions))
  83. for i, q := range questions {
  84. fmt.Print(q)
  85. if echos[i] {
  86. scan := bufio.NewScanner(os.Stdin)
  87. if scan.Scan() {
  88. answers = append(answers, scan.Text())
  89. }
  90. err := scan.Err()
  91. if err != nil {
  92. return nil, err
  93. }
  94. } else {
  95. b, err := term.ReadPassword(int(syscall.Stdin))
  96. if err != nil {
  97. return nil, err
  98. }
  99. fmt.Println()
  100. answers = append(answers, string(b))
  101. }
  102. }
  103. return answers, nil
  104. }))
  105. config := &ssh.ClientConfig{
  106. User: node.user(),
  107. Auth: authMethods,
  108. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  109. Timeout: time.Second * 10,
  110. }
  111. config.SetDefaults()
  112. config.Ciphers = append(config.Ciphers, DefaultCiphers...)
  113. return &defaultClient{
  114. clientConfig: config,
  115. node: node,
  116. }
  117. }
  118. func NewClient(node *Node) Client {
  119. return genSSHConfig(node)
  120. }
  121. func (c *defaultClient) Login() {
  122. host := c.node.Host
  123. port := strconv.Itoa(c.node.port())
  124. jNodes := c.node.Jump
  125. var client *ssh.Client
  126. if len(jNodes) > 0 {
  127. jNode := jNodes[0]
  128. jc := genSSHConfig(jNode)
  129. proxyClient, err := ssh.Dial("tcp", net.JoinHostPort(jNode.Host, strconv.Itoa(jNode.port())), jc.clientConfig)
  130. if err != nil {
  131. logger.Error(util.ErrorWithSourceLine(err))
  132. return
  133. }
  134. conn, err := proxyClient.Dial("tcp", net.JoinHostPort(host, port))
  135. if err != nil {
  136. logger.Error(util.ErrorWithSourceLine(err))
  137. return
  138. }
  139. ncc, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, port), c.clientConfig)
  140. if err != nil {
  141. logger.Error(util.ErrorWithSourceLine(err))
  142. return
  143. }
  144. client = ssh.NewClient(ncc, chans, reqs)
  145. } else {
  146. client1, err := ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
  147. client = client1
  148. if err != nil {
  149. msg := err.Error()
  150. // use terminal password retry
  151. if strings.Contains(msg, "no supported methods remain") && !strings.Contains(msg, "password") {
  152. fmt.Printf("%s@%s's password:", c.clientConfig.User, host)
  153. var b []byte
  154. b, err = term.ReadPassword(int(syscall.Stdin))
  155. if err == nil {
  156. p := string(b)
  157. if p != "" {
  158. c.clientConfig.Auth = append(c.clientConfig.Auth, ssh.Password(p))
  159. }
  160. fmt.Println()
  161. client, err = ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
  162. }
  163. }
  164. }
  165. if err != nil {
  166. logger.Error(util.ErrorWithSourceLine(err))
  167. return
  168. }
  169. }
  170. defer client.Close()
  171. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
  172. logger.Trace("connect server ssh", fmt.Sprintf("-p %d %s@%s version: %s", c.node.port(), c.node.user(), host, string(client.ServerVersion())))
  173. }
  174. session, err := client.NewSession()
  175. if err != nil {
  176. logger.Error(util.ErrorWithSourceLine(err))
  177. return
  178. }
  179. defer session.Close()
  180. fd := int(os.Stdin.Fd())
  181. w, h, err := term.GetSize(fd)
  182. if err != nil {
  183. logger.Error(util.ErrorWithSourceLine(err))
  184. w = 300
  185. h = 50
  186. }
  187. modes := ssh.TerminalModes{
  188. ssh.ECHO: 0,
  189. ssh.TTY_OP_ISPEED: 14400,
  190. ssh.TTY_OP_OSPEED: 14400,
  191. }
  192. err = session.RequestPty("xterm", h, w, modes)
  193. if err != nil {
  194. logger.Error(util.ErrorWithSourceLine(err))
  195. return
  196. }
  197. stdinPipe, err := session.StdinPipe()
  198. if err != nil {
  199. logger.Error(util.ErrorWithSourceLine(err))
  200. session.Stdin = os.Stdin
  201. }
  202. stdoutPipe, err := session.StdoutPipe()
  203. if err != nil {
  204. logger.Error(util.ErrorWithSourceLine(err))
  205. session.Stdout = os.Stdout
  206. }
  207. stderrPipe, err := session.StderrPipe()
  208. if err != nil {
  209. logger.Error(util.ErrorWithSourceLine(err))
  210. session.Stderr = os.Stderr
  211. }
  212. err = session.Shell()
  213. if err != nil {
  214. logger.Error(util.ErrorWithSourceLine(err))
  215. return
  216. }
  217. cmdidx := 0
  218. outputproc := func(stdoutbtr *BTReader, localstdout io.Writer, stdinPipe io.Writer) {
  219. as := ""
  220. change := false
  221. for {
  222. bs, err := stdoutbtr.ReadTimeout(10 * time.Millisecond)
  223. if err != nil {
  224. if err == io.EOF {
  225. return
  226. }
  227. logger.Error(util.ErrorWithSourceLine(err))
  228. return
  229. }
  230. if len(bs) > 0 {
  231. change = true
  232. s := string(bs)
  233. localstdout.Write([]byte(s))
  234. as += s
  235. } else if change {
  236. change = false
  237. if cmdidx < len(c.node.Commands) {
  238. if msi := c.node.Commands[cmdidx].Endregx.FindStringSubmatchIndex(as); msi != nil {
  239. match := as[msi[0]:msi[1]]
  240. if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
  241. logger.Trace("match end:", "'"+match+"'")
  242. }
  243. as = "" // 全清,开始新的命令
  244. cmdidx++
  245. if cmdidx >= len(c.node.Commands) {
  246. continue
  247. }
  248. if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
  249. logger.Trace("command:", c.node.Commands[cmdidx].Cmd)
  250. }
  251. localstdout.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
  252. stdinPipe.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
  253. continue
  254. }
  255. for _, regex := range c.node.Commands[cmdidx].Regexps {
  256. if regex == nil {
  257. continue
  258. }
  259. if regex.Regexp == nil {
  260. // like match all
  261. if regex.Output != "" {
  262. localstdout.Write([]byte(regex.Output))
  263. stdinPipe.Write([]byte(regex.Output))
  264. }
  265. } else if msi := regex.Regexp.FindStringSubmatchIndex(as); msi != nil {
  266. match := as[msi[0]:msi[1]]
  267. if len(msi) >= 4 {
  268. match = as[msi[2]:msi[3]]
  269. as = as[msi[3]:] // 清除已处理完的内容
  270. } else {
  271. as = as[msi[1]:] // 清除已处理完的内容
  272. }
  273. if strings.Index(regex.Debug, "m") >= 0 || strings.Index(regex.Debug, "1") >= 0 {
  274. logger.Trace("match regex:", "'"+match+"'")
  275. }
  276. if regex.Output != "" {
  277. localstdout.Write([]byte(regex.Output))
  278. stdinPipe.Write([]byte(regex.Output))
  279. }
  280. }
  281. }
  282. }
  283. if msi := regxyesno.FindStringSubmatchIndex(as); msi != nil {
  284. match := as[msi[0]:msi[1]]
  285. if len(msi) >= 4 {
  286. as = as[msi[3]:] // 清除已处理完的内容
  287. } else {
  288. as = as[msi[1]:] // 清除已处理完的内容
  289. }
  290. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
  291. cmdidx < len(c.node.Commands) &&
  292. (strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
  293. logger.Trace("match yesno:", "'"+match+"'")
  294. }
  295. os.Stdout.Write([]byte("yes\n"))
  296. stdinPipe.Write([]byte("yes\n"))
  297. }
  298. if msi := regxpassword.FindStringSubmatchIndex(as); msi != nil {
  299. match := as[msi[0]:msi[1]]
  300. if len(msi) >= 4 {
  301. as = as[msi[3]:] // 清除已处理完的内容
  302. } else {
  303. as = as[msi[1]:] // 清除已处理完的内容
  304. }
  305. p := c.node.Commands[0].Password
  306. if cmdidx < len(c.node.Commands) {
  307. p = c.node.Commands[cmdidx].Password
  308. }
  309. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
  310. cmdidx < len(c.node.Commands) &&
  311. (strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
  312. logger.Trace("match password:", "'"+match+"'")
  313. }
  314. if p != "" {
  315. if p[0:1] == "=" {
  316. p = p[1:]
  317. } else {
  318. x, e := base64.RawStdEncoding.DecodeString(p)
  319. if e == nil {
  320. p = string(x)
  321. }
  322. // else 不是Base64编码,保持原值
  323. }
  324. // don't echo password
  325. if c.node.Commands[0].Regexps[0].Debug != "" || cmdidx < len(c.node.Commands) && c.node.Commands[cmdidx].Regexps[0].Debug != "" {
  326. os.Stdout.Write([]byte(p + "\n"))
  327. }
  328. stdinPipe.Write([]byte(p + "\n"))
  329. }
  330. }
  331. if len(as) > 1024 {
  332. as = as[len(as)-1024:]
  333. }
  334. }
  335. }
  336. }
  337. go outputproc(&BTReader{Reader: bufio.NewReader(stdoutPipe)}, os.Stdout, stdinPipe)
  338. go outputproc(&BTReader{Reader: bufio.NewReader(stderrPipe)}, os.Stderr, stdinPipe)
  339. go func() {
  340. for {
  341. bs := make([]byte, 1024)
  342. n, err := os.Stdin.Read(bs)
  343. if err != nil {
  344. if err == io.EOF {
  345. return
  346. }
  347. logger.Error(util.ErrorWithSourceLine(err))
  348. return
  349. }
  350. s := string(bs[:n])
  351. stdinPipe.Write([]byte(s))
  352. }
  353. }()
  354. // interval get terminal size
  355. // fix resize issue
  356. go func() {
  357. var (
  358. ow = w
  359. oh = h
  360. )
  361. for {
  362. cw, ch, err := term.GetSize(fd)
  363. if err != nil {
  364. break
  365. }
  366. if cw != ow || ch != oh {
  367. err = session.WindowChange(ch, cw)
  368. if err != nil {
  369. break
  370. }
  371. ow = cw
  372. oh = ch
  373. }
  374. time.Sleep(time.Second)
  375. }
  376. }()
  377. // send keepalive
  378. go func() {
  379. for {
  380. time.Sleep(time.Second * 10)
  381. client.SendRequest("nop", false, nil)
  382. }
  383. }()
  384. session.Wait()
  385. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
  386. logger.Trace("disconnected")
  387. }
  388. }
  389. type BTReader struct {
  390. *bufio.Reader
  391. bufop int32
  392. chbs chan []byte
  393. }
  394. func (me *BTReader) ReadTimeout(d time.Duration) (rbs []byte, err error) {
  395. if me.chbs == nil {
  396. me.chbs = make(chan []byte)
  397. go func() {
  398. n := 0
  399. bs := make([]byte, me.Size())
  400. for {
  401. _, err = me.ReadByte()
  402. if err != nil {
  403. return
  404. }
  405. err = me.UnreadByte()
  406. if err != nil {
  407. return
  408. }
  409. n, err = me.Read(bs[0:me.Buffered()])
  410. if err != nil {
  411. return
  412. }
  413. me.chbs <- bs[0:n]
  414. }
  415. }()
  416. }
  417. t := time.NewTimer(d)
  418. select {
  419. case rbs = <-me.chbs:
  420. return
  421. case <-t.C:
  422. return
  423. }
  424. }