main.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. package main
  2. import (
  3. "crypto/ed25519"
  4. "crypto/rand"
  5. "encoding/pem"
  6. "flag"
  7. "fmt"
  8. "io"
  9. "log"
  10. "net"
  11. "os"
  12. "os/exec"
  13. "sync"
  14. "syscall"
  15. "golang.org/x/crypto/ssh"
  16. "golang.org/x/sys/unix"
  17. )
  18. type ptyRequest struct {
  19. Term string
  20. Cols uint32
  21. Rows uint32
  22. Wpx uint32
  23. Hpx uint32
  24. Modes string
  25. }
  26. type winChange struct {
  27. Cols uint32
  28. Rows uint32
  29. Wpx uint32
  30. Hpx uint32
  31. }
  32. type execRequest struct{ Command string }
  33. type envRequest struct{ Name, Value string }
  34. type session struct {
  35. ch ssh.Channel
  36. reqs <-chan *ssh.Request
  37. shell string
  38. env []string
  39. hasPty bool
  40. cols uint32
  41. rows uint32
  42. mu sync.Mutex
  43. master *os.File
  44. }
  45. func main() {
  46. addr := flag.String("addr", "0.0.0.0:22", "listen address")
  47. user := flag.String("user", "root", "allowed username")
  48. pass := flag.String("pass", "123456", "password")
  49. hostKeyPath := flag.String("hostkey", "/data/dropbear/go_hostkey", "host key file")
  50. shell := flag.String("shell", "/system/bin/sh", "login shell")
  51. flag.Parse()
  52. signer, err := loadOrCreateHostKey(*hostKeyPath)
  53. if err != nil {
  54. log.Fatalf("host key: %v", err)
  55. }
  56. cfg := &ssh.ServerConfig{
  57. PasswordCallback: func(c ssh.ConnMetadata, p []byte) (*ssh.Permissions, error) {
  58. if c.User() == *user && string(p) == *pass {
  59. return nil, nil
  60. }
  61. return nil, fmt.Errorf("password rejected for %q", c.User())
  62. },
  63. }
  64. cfg.AddHostKey(signer)
  65. ln, err := net.Listen("tcp", *addr)
  66. if err != nil {
  67. log.Fatalf("listen %s: %v", *addr, err)
  68. }
  69. log.Printf("rkssh listening on %s (user=%s shell=%s)", *addr, *user, *shell)
  70. for {
  71. conn, err := ln.Accept()
  72. if err != nil {
  73. log.Printf("accept: %v", err)
  74. continue
  75. }
  76. go handleConn(conn, cfg, *shell)
  77. }
  78. }
  79. func handleConn(nConn net.Conn, cfg *ssh.ServerConfig, shell string) {
  80. sshConn, chans, reqs, err := ssh.NewServerConn(nConn, cfg)
  81. if err != nil {
  82. log.Printf("handshake from %s failed: %v", nConn.RemoteAddr(), err)
  83. nConn.Close()
  84. return
  85. }
  86. log.Printf("login: %s from %s", sshConn.User(), sshConn.RemoteAddr())
  87. defer sshConn.Close()
  88. go ssh.DiscardRequests(reqs)
  89. for newChan := range chans {
  90. if newChan.ChannelType() != "session" {
  91. newChan.Reject(ssh.UnknownChannelType, "unknown channel type")
  92. continue
  93. }
  94. ch, chReqs, err := newChan.Accept()
  95. if err != nil {
  96. continue
  97. }
  98. s := &session{ch: ch, reqs: chReqs, shell: shell, cols: 80, rows: 24}
  99. go s.run()
  100. }
  101. }
  102. func (s *session) run() {
  103. defer s.ch.Close()
  104. started := false
  105. for req := range s.reqs {
  106. switch req.Type {
  107. case "pty-req":
  108. var p ptyRequest
  109. if err := ssh.Unmarshal(req.Payload, &p); err != nil {
  110. req.Reply(false, nil)
  111. continue
  112. }
  113. s.mu.Lock()
  114. s.hasPty = true
  115. if p.Cols > 0 {
  116. s.cols = p.Cols
  117. }
  118. if p.Rows > 0 {
  119. s.rows = p.Rows
  120. }
  121. s.mu.Unlock()
  122. if p.Term != "" {
  123. s.env = append(s.env, "TERM="+p.Term)
  124. }
  125. req.Reply(true, nil)
  126. case "window-change":
  127. var w winChange
  128. if err := ssh.Unmarshal(req.Payload, &w); err == nil {
  129. s.resize(w.Cols, w.Rows)
  130. }
  131. req.Reply(false, nil)
  132. case "env":
  133. var e envRequest
  134. if err := ssh.Unmarshal(req.Payload, &e); err == nil {
  135. s.env = append(s.env, e.Name+"="+e.Value)
  136. req.Reply(true, nil)
  137. } else {
  138. req.Reply(false, nil)
  139. }
  140. case "shell":
  141. if started {
  142. req.Reply(false, nil)
  143. continue
  144. }
  145. started = true
  146. req.Reply(true, nil)
  147. go s.exec()
  148. case "exec":
  149. if started {
  150. req.Reply(false, nil)
  151. continue
  152. }
  153. var e execRequest
  154. if err := ssh.Unmarshal(req.Payload, &e); err != nil {
  155. req.Reply(false, nil)
  156. continue
  157. }
  158. started = true
  159. req.Reply(true, nil)
  160. go s.exec("-c", e.Command)
  161. default:
  162. req.Reply(false, nil)
  163. }
  164. }
  165. }
  166. func (s *session) resize(cols, rows uint32) {
  167. s.mu.Lock()
  168. defer s.mu.Unlock()
  169. if cols > 0 {
  170. s.cols = cols
  171. }
  172. if rows > 0 {
  173. s.rows = rows
  174. }
  175. if s.master != nil {
  176. _ = unix.IoctlSetWinsize(int(s.master.Fd()), unix.TIOCSWINSZ,
  177. &unix.Winsize{Row: uint16(s.rows), Col: uint16(s.cols)})
  178. }
  179. }
  180. func (s *session) exec(shellArgs ...string) {
  181. s.mu.Lock()
  182. hasPty, cols, rows := s.hasPty, s.cols, s.rows
  183. s.mu.Unlock()
  184. cmd := exec.Command(s.shell, shellArgs...)
  185. cmd.Env = append(defaultEnv(), s.env...)
  186. cmd.Dir = "/data"
  187. status := uint32(0)
  188. if hasPty {
  189. master, slave, err := openPty(cols, rows)
  190. if err != nil {
  191. fmt.Fprintf(s.ch, "rkssh: pty error: %v\r\n", err)
  192. s.exit(1)
  193. return
  194. }
  195. s.mu.Lock()
  196. s.master = master
  197. s.mu.Unlock()
  198. cmd.Stdin, cmd.Stdout, cmd.Stderr = slave, slave, slave
  199. cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 0}
  200. if err := cmd.Start(); err != nil {
  201. fmt.Fprintf(s.ch, "rkssh: start error: %v\r\n", err)
  202. master.Close()
  203. slave.Close()
  204. s.exit(1)
  205. return
  206. }
  207. slave.Close()
  208. go func() { io.Copy(master, s.ch); master.Close() }()
  209. io.Copy(s.ch, master)
  210. if err := cmd.Wait(); err != nil {
  211. status = exitCode(err)
  212. }
  213. s.exit(status)
  214. return
  215. }
  216. stdin, _ := cmd.StdinPipe()
  217. stdout, _ := cmd.StdoutPipe()
  218. cmd.Stderr = cmd.Stdout
  219. if err := cmd.Start(); err != nil {
  220. fmt.Fprintf(s.ch, "rkssh: start error: %v\n", err)
  221. s.exit(1)
  222. return
  223. }
  224. go func() { io.Copy(stdin, s.ch); stdin.Close() }()
  225. io.Copy(s.ch, stdout)
  226. if err := cmd.Wait(); err != nil {
  227. status = exitCode(err)
  228. }
  229. s.exit(status)
  230. }
  231. func (s *session) exit(code uint32) {
  232. s.ch.SendRequest("exit-status", false, ssh.Marshal(struct{ Status uint32 }{code}))
  233. s.ch.Close()
  234. }
  235. func exitCode(err error) uint32 {
  236. if ee, ok := err.(*exec.ExitError); ok {
  237. if ws, ok := ee.Sys().(syscall.WaitStatus); ok {
  238. return uint32(ws.ExitStatus())
  239. }
  240. }
  241. return 1
  242. }
  243. func defaultEnv() []string {
  244. return []string{
  245. "PATH=/system/bin:/system/xbin:/vendor/bin:/data/dropbear:/data/local/tmp",
  246. "HOME=/data",
  247. "TERM=xterm-256color",
  248. "LANG=C.UTF-8",
  249. }
  250. }
  251. func openPty(cols, rows uint32) (*os.File, *os.File, error) {
  252. if cols == 0 {
  253. cols = 80
  254. }
  255. if rows == 0 {
  256. rows = 24
  257. }
  258. master, err := os.OpenFile("/dev/ptmx", os.O_RDWR|syscall.O_NOCTTY, 0)
  259. if err != nil {
  260. return nil, nil, err
  261. }
  262. if err := unix.IoctlSetPointerInt(int(master.Fd()), unix.TIOCSPTLCK, 0); err != nil {
  263. master.Close()
  264. return nil, nil, err
  265. }
  266. n, err := unix.IoctlGetInt(int(master.Fd()), unix.TIOCGPTN)
  267. if err != nil {
  268. master.Close()
  269. return nil, nil, err
  270. }
  271. _ = unix.IoctlSetWinsize(int(master.Fd()), unix.TIOCSWINSZ,
  272. &unix.Winsize{Row: uint16(rows), Col: uint16(cols)})
  273. slave, err := os.OpenFile(fmt.Sprintf("/dev/pts/%d", n), os.O_RDWR|syscall.O_NOCTTY, 0)
  274. if err != nil {
  275. master.Close()
  276. return nil, nil, err
  277. }
  278. return master, slave, nil
  279. }
  280. func loadOrCreateHostKey(path string) (ssh.Signer, error) {
  281. if data, err := os.ReadFile(path); err == nil {
  282. if signer, err := ssh.ParsePrivateKey(data); err == nil {
  283. return signer, nil
  284. }
  285. }
  286. _, priv, err := ed25519.GenerateKey(rand.Reader)
  287. if err != nil {
  288. return nil, err
  289. }
  290. block, err := ssh.MarshalPrivateKey(priv, "rkssh")
  291. if err != nil {
  292. return nil, err
  293. }
  294. if err := os.WriteFile(path, pem.EncodeToMemory(block), 0o600); err != nil {
  295. log.Printf("warn: cannot persist host key %s: %v", path, err)
  296. }
  297. return ssh.NewSignerFromKey(priv)
  298. }