client.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  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/logger"
  17. "golang.org/x/crypto/ssh"
  18. "golang.org/x/crypto/ssh/terminal"
  19. )
  20. var (
  21. DefaultCiphers = []string{
  22. "aes128-ctr",
  23. "aes192-ctr",
  24. "aes256-ctr",
  25. "aes128-gcm@openssh.com",
  26. "chacha20-poly1305@openssh.com",
  27. "arcfour256",
  28. "arcfour128",
  29. "arcfour",
  30. "aes128-cbc",
  31. "3des-cbc",
  32. "blowfish-cbc",
  33. "cast128-cbc",
  34. "aes192-cbc",
  35. "aes256-cbc",
  36. }
  37. )
  38. type Client interface {
  39. Login()
  40. }
  41. type defaultClient struct {
  42. clientConfig *ssh.ClientConfig
  43. node *Node
  44. }
  45. func genSSHConfig(node *Node) *defaultClient {
  46. u, err := user.Current()
  47. if err != nil {
  48. logger.Error(err)
  49. return nil
  50. }
  51. var authMethods []ssh.AuthMethod
  52. var pemBytes []byte
  53. if node.KeyPath == "" {
  54. pemBytes, err = ioutil.ReadFile(path.Join(u.HomeDir, ".ssh/id_rsa"))
  55. } else {
  56. pemBytes, err = ioutil.ReadFile(node.KeyPath)
  57. }
  58. if err != nil && !os.IsNotExist(err) {
  59. logger.Error(err)
  60. } else if len(pemBytes) > 0 {
  61. var signer ssh.Signer
  62. if node.Passphrase != "" {
  63. signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(node.Passphrase))
  64. } else {
  65. signer, err = ssh.ParsePrivateKey(pemBytes)
  66. }
  67. if err != nil {
  68. logger.Error(err)
  69. } else {
  70. authMethods = append(authMethods, ssh.PublicKeys(signer))
  71. }
  72. }
  73. password := node.password()
  74. if password != nil {
  75. authMethods = append(authMethods, password)
  76. }
  77. authMethods = append(authMethods, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
  78. answers := make([]string, 0, len(questions))
  79. for i, q := range questions {
  80. fmt.Print(q)
  81. if echos[i] {
  82. scan := bufio.NewScanner(os.Stdin)
  83. if scan.Scan() {
  84. answers = append(answers, scan.Text())
  85. }
  86. err := scan.Err()
  87. if err != nil {
  88. return nil, err
  89. }
  90. } else {
  91. b, err := terminal.ReadPassword(int(syscall.Stdin))
  92. if err != nil {
  93. return nil, err
  94. }
  95. fmt.Println()
  96. answers = append(answers, string(b))
  97. }
  98. }
  99. return answers, nil
  100. }))
  101. config := &ssh.ClientConfig{
  102. User: node.user(),
  103. Auth: authMethods,
  104. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  105. Timeout: time.Second * 10,
  106. }
  107. config.SetDefaults()
  108. config.Ciphers = append(config.Ciphers, DefaultCiphers...)
  109. return &defaultClient{
  110. clientConfig: config,
  111. node: node,
  112. }
  113. }
  114. func NewClient(node *Node) Client {
  115. return genSSHConfig(node)
  116. }
  117. func (c *defaultClient) Login() {
  118. host := c.node.Host
  119. port := strconv.Itoa(c.node.port())
  120. jNodes := c.node.Jump
  121. var client *ssh.Client
  122. if len(jNodes) > 0 {
  123. jNode := jNodes[0]
  124. jc := genSSHConfig(jNode)
  125. proxyClient, err := ssh.Dial("tcp", net.JoinHostPort(jNode.Host, strconv.Itoa(jNode.port())), jc.clientConfig)
  126. if err != nil {
  127. logger.Error(err)
  128. return
  129. }
  130. conn, err := proxyClient.Dial("tcp", net.JoinHostPort(host, port))
  131. if err != nil {
  132. logger.Error(err)
  133. return
  134. }
  135. ncc, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, port), c.clientConfig)
  136. if err != nil {
  137. logger.Error(err)
  138. return
  139. }
  140. client = ssh.NewClient(ncc, chans, reqs)
  141. } else {
  142. client1, err := ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
  143. client = client1
  144. if err != nil {
  145. msg := err.Error()
  146. // use terminal password retry
  147. if strings.Contains(msg, "no supported methods remain") && !strings.Contains(msg, "password") {
  148. fmt.Printf("%s@%s's password:", c.clientConfig.User, host)
  149. var b []byte
  150. b, err = terminal.ReadPassword(int(syscall.Stdin))
  151. if err == nil {
  152. p := string(b)
  153. if p != "" {
  154. c.clientConfig.Auth = append(c.clientConfig.Auth, ssh.Password(p))
  155. }
  156. fmt.Println()
  157. client, err = ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
  158. }
  159. }
  160. }
  161. if err != nil {
  162. logger.Error(err)
  163. return
  164. }
  165. }
  166. defer client.Close()
  167. logger.Warnf("connect server ssh -p %d %s@%s version: %s\n", c.node.port(), c.node.user(), host, string(client.ServerVersion()))
  168. session, err := client.NewSession()
  169. if err != nil {
  170. logger.Error(err)
  171. return
  172. }
  173. defer session.Close()
  174. fd := int(os.Stdin.Fd())
  175. w, h, err := terminal.GetSize(fd)
  176. if err != nil {
  177. logger.Error(err)
  178. return
  179. }
  180. modes := ssh.TerminalModes{
  181. ssh.ECHO: 0,
  182. ssh.TTY_OP_ISPEED: 14400,
  183. ssh.TTY_OP_OSPEED: 14400,
  184. }
  185. err = session.RequestPty("xterm", h, w, modes)
  186. if err != nil {
  187. logger.Error(err)
  188. return
  189. }
  190. stdinPipe, err := session.StdinPipe()
  191. if err != nil {
  192. logger.Error(err)
  193. session.Stdin = os.Stdin
  194. }
  195. stdoutPipe, err := session.StdoutPipe()
  196. if err != nil {
  197. logger.Error(err)
  198. session.Stdout = os.Stdout
  199. }
  200. stderrPipe, err := session.StderrPipe()
  201. if err != nil {
  202. logger.Error(err)
  203. session.Stderr = os.Stderr
  204. }
  205. err = session.Shell()
  206. if err != nil {
  207. logger.Error(err)
  208. return
  209. }
  210. cmdidx := 0
  211. regxpassword := regexp.MustCompile(`.*[Pp]assword: *$`)
  212. regxprompt := regexp.MustCompile(`.*[%#\$\>]\s*$`)
  213. outputproc := func(stdoutbtr *BTReader, localstdout io.Writer, stdinPipe io.Writer) {
  214. as := ""
  215. change := false
  216. for {
  217. bs, err := stdoutbtr.ReadTimeout(10 * time.Millisecond)
  218. if err != nil {
  219. if err == io.EOF {
  220. return
  221. }
  222. logger.Error(err)
  223. return
  224. }
  225. if len(bs) > 0 {
  226. change = true
  227. s := string(bs)
  228. localstdout.Write([]byte(s))
  229. as += s
  230. } else if change {
  231. change = false
  232. if regxpassword.MatchString(as) {
  233. p := c.node.Commands[cmdidx].Password
  234. if p == "" {
  235. p = c.node.Commands[0].Password
  236. }
  237. // don't echo password, os.Stdout.Write([]byte(p + "\n"))
  238. stdinPipe.Write([]byte(p + "\n"))
  239. }
  240. if regxprompt.MatchString(as) {
  241. cmdidx++
  242. if cmdidx >= len(c.node.Commands) {
  243. localstdout.Write([]byte("exit" + "\n"))
  244. stdinPipe.Write([]byte("exit" + "\n"))
  245. } else {
  246. localstdout.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
  247. stdinPipe.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
  248. }
  249. }
  250. if len(as) > 1024 {
  251. as = as[len(as)-1024:]
  252. }
  253. }
  254. }
  255. }
  256. go outputproc(&BTReader{Reader: bufio.NewReader(stdoutPipe)}, os.Stdout, stdinPipe)
  257. go outputproc(&BTReader{Reader: bufio.NewReader(stderrPipe)}, os.Stderr, stdinPipe)
  258. go func() {
  259. for {
  260. bs := make([]byte, 1024)
  261. n, err := os.Stdin.Read(bs)
  262. if err != nil {
  263. if err == io.EOF {
  264. return
  265. }
  266. logger.Error(err)
  267. return
  268. }
  269. s := string(bs[:n])
  270. stdinPipe.Write([]byte(s))
  271. }
  272. }()
  273. // interval get terminal size
  274. // fix resize issue
  275. go func() {
  276. var (
  277. ow = w
  278. oh = h
  279. )
  280. for {
  281. cw, ch, err := terminal.GetSize(fd)
  282. if err != nil {
  283. break
  284. }
  285. if cw != ow || ch != oh {
  286. err = session.WindowChange(ch, cw)
  287. if err != nil {
  288. break
  289. }
  290. ow = cw
  291. oh = ch
  292. }
  293. time.Sleep(time.Second)
  294. }
  295. }()
  296. // send keepalive
  297. go func() {
  298. for {
  299. time.Sleep(time.Second * 10)
  300. client.SendRequest("nop", false, nil)
  301. }
  302. }()
  303. session.Wait()
  304. logger.Warnf("disconnected")
  305. }
  306. type BTReader struct {
  307. *bufio.Reader
  308. bufop int32
  309. chbs chan []byte
  310. }
  311. func (me *BTReader) ReadTimeout(d time.Duration) (rbs []byte, err error) {
  312. if me.chbs == nil {
  313. me.chbs = make(chan []byte)
  314. go func() {
  315. n := 0
  316. bs := make([]byte, me.Size())
  317. for {
  318. _, err = me.ReadByte()
  319. if err != nil {
  320. return
  321. }
  322. err = me.UnreadByte()
  323. if err != nil {
  324. return
  325. }
  326. n, err = me.Read(bs[0:me.Buffered()])
  327. if err != nil {
  328. return
  329. }
  330. me.chbs <- bs[0:n]
  331. }
  332. }()
  333. }
  334. t := time.NewTimer(d)
  335. select {
  336. case rbs = <-me.chbs:
  337. return
  338. case <-t.C:
  339. return
  340. }
  341. }