client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "net"
  9. "os"
  10. "os/user"
  11. "path"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "syscall"
  16. "time"
  17. "git.wecise.com/wecise/common/matrix/logger"
  18. "git.wecise.com/wecise/common/matrix/util"
  19. "golang.org/x/crypto/ssh"
  20. "golang.org/x/crypto/ssh/terminal"
  21. )
  22. var (
  23. DefaultCiphers = []string{
  24. "aes128-ctr",
  25. "aes192-ctr",
  26. "aes256-ctr",
  27. "aes128-gcm@openssh.com",
  28. "chacha20-poly1305@openssh.com",
  29. "arcfour256",
  30. "arcfour128",
  31. "arcfour",
  32. "aes128-cbc",
  33. "3des-cbc",
  34. "blowfish-cbc",
  35. "cast128-cbc",
  36. "aes192-cbc",
  37. "aes256-cbc",
  38. }
  39. )
  40. var regxyesno = &Regexp{regexp.MustCompile(`.*(\(yes\/no\)\?)\s*$`)}
  41. var regxpassword = &Regexp{regexp.MustCompile(`.*([Pp]assword:)\s*$`)}
  42. var regxprompt = &Regexp{regexp.MustCompile(`.*([%#\$\>])\s*$`)}
  43. type Client interface {
  44. Login()
  45. }
  46. type defaultClient struct {
  47. clientConfig *ssh.ClientConfig
  48. node *Node
  49. }
  50. func genSSHConfig(node *Node) *defaultClient {
  51. u, err := user.Current()
  52. if err != nil {
  53. logger.Error(util.ErrorWithSourceLine(err))
  54. return nil
  55. }
  56. var authMethods []ssh.AuthMethod
  57. var pemBytes []byte
  58. if node.KeyPath == "" {
  59. pemBytes, err = ioutil.ReadFile(path.Join(u.HomeDir, ".ssh/id_rsa"))
  60. } else {
  61. pemBytes, err = ioutil.ReadFile(node.KeyPath)
  62. }
  63. if err != nil && !os.IsNotExist(err) {
  64. logger.Error(util.ErrorWithSourceLine(err))
  65. } else if len(pemBytes) > 0 {
  66. var signer ssh.Signer
  67. if node.Passphrase != "" {
  68. signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(node.Passphrase))
  69. } else {
  70. signer, err = ssh.ParsePrivateKey(pemBytes)
  71. }
  72. if err != nil {
  73. logger.Error(util.ErrorWithSourceLine(err))
  74. } else {
  75. authMethods = append(authMethods, ssh.PublicKeys(signer))
  76. }
  77. }
  78. password := node.password()
  79. if password != nil {
  80. authMethods = append(authMethods, password)
  81. }
  82. authMethods = append(authMethods, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
  83. answers := make([]string, 0, len(questions))
  84. for i, q := range questions {
  85. fmt.Print(q)
  86. if echos[i] {
  87. scan := bufio.NewScanner(os.Stdin)
  88. if scan.Scan() {
  89. answers = append(answers, scan.Text())
  90. }
  91. err := scan.Err()
  92. if err != nil {
  93. return nil, err
  94. }
  95. } else {
  96. b, err := terminal.ReadPassword(int(syscall.Stdin))
  97. if err != nil {
  98. return nil, err
  99. }
  100. fmt.Println()
  101. answers = append(answers, string(b))
  102. }
  103. }
  104. return answers, nil
  105. }))
  106. config := &ssh.ClientConfig{
  107. User: node.user(),
  108. Auth: authMethods,
  109. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  110. Timeout: time.Second * 10,
  111. }
  112. config.SetDefaults()
  113. config.Ciphers = append(config.Ciphers, DefaultCiphers...)
  114. return &defaultClient{
  115. clientConfig: config,
  116. node: node,
  117. }
  118. }
  119. func NewClient(node *Node) Client {
  120. return genSSHConfig(node)
  121. }
  122. func (c *defaultClient) Login() {
  123. host := c.node.Host
  124. port := strconv.Itoa(c.node.port())
  125. jNodes := c.node.Jump
  126. var client *ssh.Client
  127. if len(jNodes) > 0 {
  128. jNode := jNodes[0]
  129. jc := genSSHConfig(jNode)
  130. proxyClient, err := ssh.Dial("tcp", net.JoinHostPort(jNode.Host, strconv.Itoa(jNode.port())), jc.clientConfig)
  131. if err != nil {
  132. logger.Error(util.ErrorWithSourceLine(err))
  133. return
  134. }
  135. conn, err := proxyClient.Dial("tcp", net.JoinHostPort(host, port))
  136. if err != nil {
  137. logger.Error(util.ErrorWithSourceLine(err))
  138. return
  139. }
  140. ncc, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, port), c.clientConfig)
  141. if err != nil {
  142. logger.Error(util.ErrorWithSourceLine(err))
  143. return
  144. }
  145. client = ssh.NewClient(ncc, chans, reqs)
  146. } else {
  147. client1, err := ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
  148. client = client1
  149. if err != nil {
  150. msg := err.Error()
  151. // use terminal password retry
  152. if strings.Contains(msg, "no supported methods remain") && !strings.Contains(msg, "password") {
  153. fmt.Printf("%s@%s's password:", c.clientConfig.User, host)
  154. var b []byte
  155. b, err = terminal.ReadPassword(int(syscall.Stdin))
  156. if err == nil {
  157. p := string(b)
  158. if p != "" {
  159. c.clientConfig.Auth = append(c.clientConfig.Auth, ssh.Password(p))
  160. }
  161. fmt.Println()
  162. client, err = ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
  163. }
  164. }
  165. }
  166. if err != nil {
  167. logger.Error(util.ErrorWithSourceLine(err))
  168. return
  169. }
  170. }
  171. defer client.Close()
  172. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
  173. logger.Trace("connect server ssh", fmt.Sprintf("-p %d %s@%s version: %s", c.node.port(), c.node.user(), host, string(client.ServerVersion())))
  174. }
  175. session, err := client.NewSession()
  176. if err != nil {
  177. logger.Error(util.ErrorWithSourceLine(err))
  178. return
  179. }
  180. defer session.Close()
  181. fd := int(os.Stdin.Fd())
  182. w, h, err := terminal.GetSize(fd)
  183. if err != nil {
  184. logger.Error(util.ErrorWithSourceLine(err))
  185. return
  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 := terminal.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. }