client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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/odbserver/matrix/logger"
  18. "golang.org/x/crypto/ssh"
  19. "golang.org/x/crypto/ssh/terminal"
  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(err)
  53. return nil
  54. }
  55. var authMethods []ssh.AuthMethod
  56. var pemBytes []byte
  57. if node.KeyPath == "" {
  58. pemBytes, err = ioutil.ReadFile(path.Join(u.HomeDir, ".ssh/id_rsa"))
  59. } else {
  60. pemBytes, err = ioutil.ReadFile(node.KeyPath)
  61. }
  62. if err != nil && !os.IsNotExist(err) {
  63. logger.Error(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(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 := terminal.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(err)
  132. return
  133. }
  134. conn, err := proxyClient.Dial("tcp", net.JoinHostPort(host, port))
  135. if err != nil {
  136. logger.Error(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(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 = terminal.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(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(err)
  177. return
  178. }
  179. defer session.Close()
  180. fd := int(os.Stdin.Fd())
  181. w, h, err := terminal.GetSize(fd)
  182. if err != nil {
  183. logger.Error(err)
  184. return
  185. }
  186. modes := ssh.TerminalModes{
  187. ssh.ECHO: 0,
  188. ssh.TTY_OP_ISPEED: 14400,
  189. ssh.TTY_OP_OSPEED: 14400,
  190. }
  191. err = session.RequestPty("xterm", h, w, modes)
  192. if err != nil {
  193. logger.Error(err)
  194. return
  195. }
  196. stdinPipe, err := session.StdinPipe()
  197. if err != nil {
  198. logger.Error(err)
  199. session.Stdin = os.Stdin
  200. }
  201. stdoutPipe, err := session.StdoutPipe()
  202. if err != nil {
  203. logger.Error(err)
  204. session.Stdout = os.Stdout
  205. }
  206. stderrPipe, err := session.StderrPipe()
  207. if err != nil {
  208. logger.Error(err)
  209. session.Stderr = os.Stderr
  210. }
  211. err = session.Shell()
  212. if err != nil {
  213. logger.Error(err)
  214. return
  215. }
  216. cmdidx := 0
  217. outputproc := func(stdoutbtr *BTReader, localstdout io.Writer, stdinPipe io.Writer) {
  218. as := ""
  219. change := false
  220. for {
  221. bs, err := stdoutbtr.ReadTimeout(10 * time.Millisecond)
  222. if err != nil {
  223. if err == io.EOF {
  224. return
  225. }
  226. logger.Error(err)
  227. return
  228. }
  229. if len(bs) > 0 {
  230. change = true
  231. s := string(bs)
  232. localstdout.Write([]byte(s))
  233. as += s
  234. } else if change {
  235. change = false
  236. if cmdidx < len(c.node.Commands) {
  237. if msi := c.node.Commands[cmdidx].Endregx.FindStringSubmatchIndex(as); msi != nil {
  238. match := as[msi[0]:msi[1]]
  239. if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
  240. logger.Trace("match end:", "'"+match+"'")
  241. }
  242. as = "" // 全清,开始新的命令
  243. cmdidx++
  244. if cmdidx >= len(c.node.Commands) {
  245. continue
  246. }
  247. if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
  248. logger.Trace("command:", c.node.Commands[cmdidx].Cmd)
  249. }
  250. localstdout.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
  251. stdinPipe.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
  252. continue
  253. }
  254. for _, regex := range c.node.Commands[cmdidx].Regexps {
  255. if regex == nil {
  256. continue
  257. }
  258. if regex.Regexp == nil {
  259. // like match all
  260. if regex.Output != "" {
  261. localstdout.Write([]byte(regex.Output))
  262. stdinPipe.Write([]byte(regex.Output))
  263. }
  264. } else if msi := regex.Regexp.FindStringSubmatchIndex(as); msi != nil {
  265. match := as[msi[0]:msi[1]]
  266. if len(msi) >= 4 {
  267. match = as[msi[2]:msi[3]]
  268. as = as[msi[3]:] // 清除已处理完的内容
  269. } else {
  270. as = as[msi[1]:] // 清除已处理完的内容
  271. }
  272. if strings.Index(regex.Debug, "m") >= 0 || strings.Index(regex.Debug, "1") >= 0 {
  273. logger.Trace("match regex:", "'"+match+"'")
  274. }
  275. if regex.Output != "" {
  276. localstdout.Write([]byte(regex.Output))
  277. stdinPipe.Write([]byte(regex.Output))
  278. }
  279. }
  280. }
  281. }
  282. if msi := regxyesno.FindStringSubmatchIndex(as); msi != nil {
  283. match := as[msi[0]:msi[1]]
  284. if len(msi) >= 4 {
  285. as = as[msi[3]:] // 清除已处理完的内容
  286. } else {
  287. as = as[msi[1]:] // 清除已处理完的内容
  288. }
  289. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
  290. cmdidx < len(c.node.Commands) &&
  291. (strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
  292. logger.Trace("match yesno:", "'"+match+"'")
  293. }
  294. os.Stdout.Write([]byte("yes\n"))
  295. stdinPipe.Write([]byte("yes\n"))
  296. }
  297. if msi := regxpassword.FindStringSubmatchIndex(as); msi != nil {
  298. match := as[msi[0]:msi[1]]
  299. if len(msi) >= 4 {
  300. as = as[msi[3]:] // 清除已处理完的内容
  301. } else {
  302. as = as[msi[1]:] // 清除已处理完的内容
  303. }
  304. p := c.node.Commands[0].Password
  305. if cmdidx < len(c.node.Commands) {
  306. p = c.node.Commands[cmdidx].Password
  307. }
  308. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
  309. cmdidx < len(c.node.Commands) &&
  310. (strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
  311. logger.Trace("match password:", "'"+match+"'")
  312. }
  313. if p != "" {
  314. if p[0:1] == "=" {
  315. p = p[1:]
  316. } else {
  317. x, e := base64.RawStdEncoding.DecodeString(p)
  318. if e == nil {
  319. p = string(x)
  320. }
  321. // else 不是Base64编码,保持原值
  322. }
  323. // don't echo password
  324. if c.node.Commands[0].Regexps[0].Debug != "" || cmdidx < len(c.node.Commands) && c.node.Commands[cmdidx].Regexps[0].Debug != "" {
  325. os.Stdout.Write([]byte(p + "\n"))
  326. }
  327. stdinPipe.Write([]byte(p + "\n"))
  328. }
  329. }
  330. if len(as) > 1024 {
  331. as = as[len(as)-1024:]
  332. }
  333. }
  334. }
  335. }
  336. go outputproc(&BTReader{Reader: bufio.NewReader(stdoutPipe)}, os.Stdout, stdinPipe)
  337. go outputproc(&BTReader{Reader: bufio.NewReader(stderrPipe)}, os.Stderr, stdinPipe)
  338. go func() {
  339. for {
  340. bs := make([]byte, 1024)
  341. n, err := os.Stdin.Read(bs)
  342. if err != nil {
  343. if err == io.EOF {
  344. return
  345. }
  346. logger.Error(err)
  347. return
  348. }
  349. s := string(bs[:n])
  350. stdinPipe.Write([]byte(s))
  351. }
  352. }()
  353. // interval get terminal size
  354. // fix resize issue
  355. go func() {
  356. var (
  357. ow = w
  358. oh = h
  359. )
  360. for {
  361. cw, ch, err := terminal.GetSize(fd)
  362. if err != nil {
  363. break
  364. }
  365. if cw != ow || ch != oh {
  366. err = session.WindowChange(ch, cw)
  367. if err != nil {
  368. break
  369. }
  370. ow = cw
  371. oh = ch
  372. }
  373. time.Sleep(time.Second)
  374. }
  375. }()
  376. // send keepalive
  377. go func() {
  378. for {
  379. time.Sleep(time.Second * 10)
  380. client.SendRequest("nop", false, nil)
  381. }
  382. }()
  383. session.Wait()
  384. if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
  385. logger.Trace("disconnected")
  386. }
  387. }
  388. type BTReader struct {
  389. *bufio.Reader
  390. bufop int32
  391. chbs chan []byte
  392. }
  393. func (me *BTReader) ReadTimeout(d time.Duration) (rbs []byte, err error) {
  394. if me.chbs == nil {
  395. me.chbs = make(chan []byte)
  396. go func() {
  397. n := 0
  398. bs := make([]byte, me.Size())
  399. for {
  400. _, err = me.ReadByte()
  401. if err != nil {
  402. return
  403. }
  404. err = me.UnreadByte()
  405. if err != nil {
  406. return
  407. }
  408. n, err = me.Read(bs[0:me.Buffered()])
  409. if err != nil {
  410. return
  411. }
  412. me.chbs <- bs[0:n]
  413. }
  414. }()
  415. }
  416. t := time.NewTimer(d)
  417. select {
  418. case rbs = <-me.chbs:
  419. return
  420. case <-t.C:
  421. return
  422. }
  423. }