xsd.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  1. // xsd server
  2. //
  3. // Copyright (c) 2017-2020 Russell Magee
  4. // Licensed under the terms of the MIT license (see LICENSE.mit in this
  5. // distribution)
  6. //
  7. // golang implementation by Russ Magee (rmagee_at_gmail.com)
  8. package main
  9. import (
  10. "bytes"
  11. "crypto/rand"
  12. "encoding/binary"
  13. "encoding/hex"
  14. "errors"
  15. "flag"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "log"
  20. "os"
  21. "os/exec"
  22. "os/signal"
  23. "os/user"
  24. "path"
  25. "strings"
  26. "sync"
  27. "syscall"
  28. "time"
  29. "unsafe"
  30. "blitter.com/go/goutmp"
  31. xs "blitter.com/go/xs"
  32. "blitter.com/go/xs/logger"
  33. "blitter.com/go/xs/xsnet"
  34. "github.com/creack/pty"
  35. )
  36. var (
  37. version string
  38. gitCommit string // set in -ldflags by build
  39. useSysLogin bool
  40. kcpMode string // set to a valid KCP BlockCrypt alg tag to use rather than TCP
  41. // Log - syslog output (with no -d)
  42. Log *logger.Writer
  43. )
  44. func ioctl(fd, request, argp uintptr) error {
  45. if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 {
  46. return e
  47. }
  48. return nil
  49. }
  50. func ptsName(fd uintptr) (string, error) {
  51. var n uintptr
  52. err := ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
  53. if err != nil {
  54. return "", err
  55. }
  56. return fmt.Sprintf("/dev/pts/%d", n), nil
  57. }
  58. /* -------------------------------------------------------------- */
  59. // Perform a client->server copy
  60. func runClientToServerCopyAs(who, ttype string, conn *xsnet.Conn, fpath string, chaffing bool) (exitStatus uint32, err error) {
  61. u, _ := user.Lookup(who) // nolint: gosec
  62. var uid, gid uint32
  63. fmt.Sscanf(u.Uid, "%d", &uid) // nolint: gosec,errcheck
  64. fmt.Sscanf(u.Gid, "%d", &gid) // nolint: gosec,errcheck
  65. log.Println("uid:", uid, "gid:", gid)
  66. // Need to clear server's env and set key vars of the
  67. // target user. This isn't perfect (TERM doesn't seem to
  68. // work 100%; ANSI/xterm colour isn't working even
  69. // if we set "xterm" or "ansi" here; and line count
  70. // reported by 'stty -a' defaults to 24 regardless
  71. // of client shell window used to run client.
  72. // Investigate -- rlm 2018-01-26)
  73. os.Clearenv()
  74. os.Setenv("HOME", u.HomeDir) // nolint: gosec,errcheck
  75. os.Setenv("TERM", ttype) // nolint: gosec,errcheck
  76. os.Setenv("XS_SESSION", "1") // nolint: gosec,errcheck
  77. var c *exec.Cmd
  78. cmdName := xs.GetTool("tar")
  79. var destDir string
  80. if path.IsAbs(fpath) {
  81. destDir = fpath
  82. } else {
  83. destDir = path.Join(u.HomeDir, fpath)
  84. }
  85. cmdArgs := []string{"-xz", "-C", destDir}
  86. // NOTE the lack of quotes around --xform option's sed expression.
  87. // When args are passed in exec() format, no quoting is required
  88. // (as this isn't input from a shell) (right? -rlm 20180823)
  89. //cmdArgs := []string{"-x", "-C", destDir, `--xform=s#.*/\(.*\)#\1#`}
  90. fmt.Println(cmdName, cmdArgs)
  91. c = exec.Command(cmdName, cmdArgs...) // nolint: gosec
  92. c.Dir = destDir
  93. //If os.Clearenv() isn't called by server above these will be seen in the
  94. //client's session env.
  95. //c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
  96. //c.Dir = u.HomeDir
  97. c.SysProcAttr = &syscall.SysProcAttr{}
  98. c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
  99. c.Stdin = conn
  100. c.Stdout = os.Stdout
  101. c.Stderr = os.Stderr
  102. if chaffing {
  103. conn.EnableChaff()
  104. }
  105. defer conn.DisableChaff()
  106. defer conn.ShutdownChaff()
  107. // Start the command (no pty)
  108. log.Printf("[%v %v]\n", cmdName, cmdArgs)
  109. err = c.Start() // returns immediately
  110. /////////////
  111. // NOTE: There is, apparently, a bug in Go stdlib here. Start()
  112. // can actually return immediately, on a command which *does*
  113. // start but exits quickly, with c.Wait() error
  114. // "c.Wait status: exec: not started".
  115. // As in this example, attempting a client->server copy to
  116. // a nonexistent remote dir (it's tar exiting right away, exitStatus
  117. // 2, stderr
  118. // /bin/tar -xz -C /home/someuser/nosuchdir
  119. // stderr: fork/exec /bin/tar: no such file or directory
  120. //
  121. // In this case, c.Wait() won't give us the real
  122. // exit status (is it lost?).
  123. /////////////
  124. if err != nil {
  125. log.Println("cmd exited immediately. Cannot get cmd.Wait().ExitStatus()")
  126. err = errors.New("cmd exited prematurely")
  127. //exitStatus = uint32(254)
  128. exitStatus = xsnet.CSEExecFail
  129. } else {
  130. if err := c.Wait(); err != nil {
  131. //fmt.Println("*** c.Wait() done ***")
  132. if exiterr, ok := err.(*exec.ExitError); ok {
  133. // The program has exited with an exit code != 0
  134. // This works on both Unix and Windows. Although package
  135. // syscall is generally platform dependent, WaitStatus is
  136. // defined for both Unix and Windows and in both cases has
  137. // an ExitStatus() method with the same signature.
  138. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
  139. exitStatus = uint32(status.ExitStatus())
  140. //err = errors.New("cmd returned nonzero status")
  141. log.Printf("Exit Status: %d\n", exitStatus)
  142. }
  143. }
  144. }
  145. log.Println("*** client->server cp finished ***")
  146. }
  147. return
  148. }
  149. // Perform a server->client copy
  150. func runServerToClientCopyAs(who, ttype string, conn *xsnet.Conn, srcPath string, chaffing bool) (exitStatus uint32, err error) {
  151. u, err := user.Lookup(who)
  152. if err != nil {
  153. exitStatus = 1
  154. return
  155. }
  156. var uid, gid uint32
  157. _, _ = fmt.Sscanf(u.Uid, "%d", &uid) // nolint: gosec
  158. _, _ = fmt.Sscanf(u.Gid, "%d", &gid) // nolint: gosec
  159. log.Println("uid:", uid, "gid:", gid)
  160. // Need to clear server's env and set key vars of the
  161. // target user. This isn't perfect (TERM doesn't seem to
  162. // work 100%; ANSI/xterm colour isn't working even
  163. // if we set "xterm" or "ansi" here; and line count
  164. // reported by 'stty -a' defaults to 24 regardless
  165. // of client shell window used to run client.
  166. // Investigate -- rlm 2018-01-26)
  167. os.Clearenv()
  168. _ = os.Setenv("HOME", u.HomeDir) // nolint: gosec
  169. _ = os.Setenv("TERM", ttype) // nolint: gosec
  170. _ = os.Setenv("XS_SESSION", "1") // nolint: gosec
  171. var c *exec.Cmd
  172. cmdName := xs.GetTool("tar")
  173. if !path.IsAbs(srcPath) {
  174. srcPath = fmt.Sprintf("%s%c%s", u.HomeDir, os.PathSeparator, srcPath)
  175. }
  176. srcDir, srcBase := path.Split(srcPath)
  177. cmdArgs := []string{"-cz", "-C", srcDir, "-f", "-", srcBase}
  178. c = exec.Command(cmdName, cmdArgs...) // nolint: gosec
  179. //If os.Clearenv() isn't called by server above these will be seen in the
  180. //client's session env.
  181. //c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
  182. c.Dir = u.HomeDir
  183. c.SysProcAttr = &syscall.SysProcAttr{}
  184. c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
  185. c.Stdout = conn
  186. // Stderr sinkholing (or buffering to something other than stdout)
  187. // is important. Any extraneous output to tarpipe messes up remote
  188. // side as it's expecting pure tar data.
  189. // (For example, if user specifies abs paths, tar outputs
  190. // "Removing leading '/' from path names")
  191. stdErrBuffer := new(bytes.Buffer)
  192. c.Stderr = stdErrBuffer
  193. //c.Stderr = nil
  194. if chaffing {
  195. conn.EnableChaff()
  196. }
  197. //defer conn.Close()
  198. defer conn.DisableChaff()
  199. defer conn.ShutdownChaff()
  200. // Start the command (no pty)
  201. log.Printf("[%v %v]\n", cmdName, cmdArgs)
  202. err = c.Start() // returns immediately
  203. if err != nil {
  204. log.Printf("Command finished with error: %v", err)
  205. return xsnet.CSEExecFail, err // !?
  206. }
  207. if err := c.Wait(); err != nil {
  208. //fmt.Println("*** c.Wait() done ***")
  209. if exiterr, ok := err.(*exec.ExitError); ok {
  210. // The program has exited with an exit code != 0
  211. // This works on both Unix and Windows. Although package
  212. // syscall is generally platform dependent, WaitStatus is
  213. // defined for both Unix and Windows and in both cases has
  214. // an ExitStatus() method with the same signature.
  215. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
  216. exitStatus = uint32(status.ExitStatus())
  217. if len(stdErrBuffer.Bytes()) > 0 {
  218. log.Print(stdErrBuffer)
  219. }
  220. log.Printf("Exit Status: %d", exitStatus)
  221. }
  222. }
  223. }
  224. //fmt.Println("*** server->client cp finished ***")
  225. return
  226. }
  227. // Run a command (via default shell) as a specific user
  228. //
  229. // Uses ptys to support commands which expect a terminal.
  230. // nolint: gocyclo
  231. func runShellAs(who, hname, ttype, cmd string, interactive bool, conn *xsnet.Conn, chaffing bool) (exitStatus uint32, err error) {
  232. var wg sync.WaitGroup
  233. u, err := user.Lookup(who)
  234. if err != nil {
  235. exitStatus = 1
  236. return
  237. }
  238. var uid, gid uint32
  239. _, _ = fmt.Sscanf(u.Uid, "%d", &uid) // nolint: gosec
  240. _, _ = fmt.Sscanf(u.Gid, "%d", &gid) // nolint: gosec
  241. log.Println("uid:", uid, "gid:", gid)
  242. // Need to clear server's env and set key vars of the
  243. // target user. This isn't perfect (TERM doesn't seem to
  244. // work 100%; ANSI/xterm colour isn't working even
  245. // if we set "xterm" or "ansi" here; and line count
  246. // reported by 'stty -a' defaults to 24 regardless
  247. // of client shell window used to run client.
  248. // Investigate -- rlm 2018-01-26)
  249. os.Clearenv()
  250. _ = os.Setenv("HOME", u.HomeDir) // nolint: gosec
  251. _ = os.Setenv("TERM", ttype) // nolint: gosec
  252. _ = os.Setenv("XS_SESSION", "1") // nolint: gosec
  253. var c *exec.Cmd
  254. if interactive {
  255. if useSysLogin {
  256. // Use the server's login binary (post-auth
  257. // which is still done via our own bcrypt file)
  258. // Things UNIX login does, like print the 'motd',
  259. // and use the shell specified by /etc/passwd, will be done
  260. // automagically, at the cost of another external tool
  261. // dependency.
  262. //
  263. c = exec.Command(xs.GetTool("login"), "-f", "-p", who) // nolint: gosec
  264. } else {
  265. c = exec.Command(xs.GetTool("bash"), "-i", "-l") // nolint: gosec
  266. }
  267. } else {
  268. c = exec.Command(xs.GetTool("bash"), "-c", cmd) // nolint: gosec
  269. }
  270. //If os.Clearenv() isn't called by server above these will be seen in the
  271. //client's session env.
  272. //c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
  273. c.Dir = u.HomeDir
  274. c.SysProcAttr = &syscall.SysProcAttr{}
  275. if useSysLogin {
  276. // If using server's login binary, drop to user creds
  277. // is taken care of by it.
  278. c.SysProcAttr.Credential = &syscall.Credential{}
  279. } else {
  280. c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
  281. }
  282. // Start the command with a pty.
  283. ptmx, err := pty.Start(c) // returns immediately with ptmx file
  284. if err != nil {
  285. log.Println(err)
  286. return xsnet.CSEPtyExecFail, err
  287. }
  288. // Make sure to close the pty at the end.
  289. // #gv:s/label=\"runShellAs\$1\"/label=\"deferPtmxClose\"/
  290. defer func() {
  291. //logger.LogDebug(fmt.Sprintf("[Exited process was %d]", c.Process.Pid))
  292. _ = ptmx.Close()
  293. }() // nolint: gosec
  294. // get pty info for system accounting (who, lastlog)
  295. pts, pe := ptsName(ptmx.Fd())
  296. if pe != nil {
  297. return xsnet.CSEPtyGetNameFail, err
  298. }
  299. utmpx := goutmp.Put_utmp(who, pts, hname)
  300. defer func() { goutmp.Unput_utmp(utmpx) }()
  301. goutmp.Put_lastlog_entry("xs", who, pts, hname)
  302. log.Printf("[%s]\n", cmd)
  303. if err != nil {
  304. log.Printf("Command finished with error: %v", err)
  305. } else {
  306. // Watch for term resizes
  307. // #gv:s/label=\"runShellAs\$2\"/label=\"termResizeWatcher\"/
  308. go func() {
  309. for sz := range conn.WinCh {
  310. log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
  311. pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols}) // nolint: gosec,errcheck
  312. }
  313. log.Println("*** WinCh goroutine done ***")
  314. }()
  315. // Copy stdin to the pty.. (bgnd goroutine)
  316. // #gv:s/label=\"runShellAs\$3\"/label=\"stdinToPtyWorker\"/
  317. go func() {
  318. _, e := io.Copy(ptmx, conn)
  319. if e != nil {
  320. log.Println("** stdin->pty ended **:", e.Error())
  321. } else {
  322. log.Println("*** stdin->pty goroutine done ***")
  323. }
  324. }()
  325. if chaffing {
  326. conn.EnableChaff()
  327. }
  328. // #gv:s/label=\"runShellAs\$4\"/label=\"deferChaffShutdown\"/
  329. defer func() {
  330. conn.DisableChaff()
  331. conn.ShutdownChaff()
  332. }()
  333. // ..and the pty to stdout.
  334. // This may take some time exceeding that of the
  335. // actual command's lifetime, so the c.Wait() below
  336. // must synchronize with the completion of this goroutine
  337. // to ensure all stdout data gets to the client before
  338. // connection is closed.
  339. wg.Add(1)
  340. // #gv:s/label=\"runShellAs\$5\"/label=\"ptyToStdoutWorker\"/
  341. go func() {
  342. defer wg.Done()
  343. _, e := io.Copy(conn, ptmx)
  344. if e != nil {
  345. log.Println("** pty->stdout ended **:", e.Error())
  346. } else {
  347. // The above io.Copy() will exit when the command attached
  348. // to the pty exits
  349. log.Println("*** pty->stdout goroutine done ***")
  350. }
  351. }()
  352. if err := c.Wait(); err != nil {
  353. //fmt.Println("*** c.Wait() done ***")
  354. if exiterr, ok := err.(*exec.ExitError); ok {
  355. // The program has exited with an exit code != 0
  356. // This works on both Unix and Windows. Although package
  357. // syscall is generally platform dependent, WaitStatus is
  358. // defined for both Unix and Windows and in both cases has
  359. // an ExitStatus() method with the same signature.
  360. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
  361. exitStatus = uint32(status.ExitStatus())
  362. log.Printf("Exit Status: %d", exitStatus)
  363. }
  364. }
  365. conn.SetStatus(xsnet.CSOType(exitStatus))
  366. } else {
  367. logger.LogDebug("*** Main proc has exited. ***")
  368. // Background jobs still may be running; close the
  369. // pty anyway, so the client can return before
  370. // wg.Wait() below completes (Issue #18)
  371. if interactive {
  372. _ = ptmx.Close()
  373. }
  374. }
  375. wg.Wait() // Wait on pty->stdout completion to client
  376. }
  377. return
  378. }
  379. // GenAuthToken generates a pseudorandom auth token for a specific
  380. // user from a specific host to allow non-interactive logins.
  381. func GenAuthToken(who string, connhost string) string {
  382. //tokenA, e := os.Hostname()
  383. //if e != nil {
  384. // tokenA = "badhost"
  385. //}
  386. tokenA := connhost
  387. tokenB := make([]byte, 64)
  388. _, _ = rand.Read(tokenB) // nolint: gosec
  389. return fmt.Sprintf("%s:%s", tokenA, hex.EncodeToString(tokenB))
  390. }
  391. var (
  392. aKEXAlgs allowedKEXAlgs
  393. aCipherAlgs allowedCipherAlgs
  394. aHMACAlgs allowedHMACAlgs
  395. )
  396. type allowedKEXAlgs []string // TODO
  397. type allowedCipherAlgs []string // TODO
  398. type allowedHMACAlgs []string // TODO
  399. func (a allowedKEXAlgs) allowed(k xsnet.KEXAlg) bool {
  400. for i := 0; i < len(a); i++ {
  401. if a[i] == "KEX_all" || a[i] == k.String() {
  402. return true
  403. }
  404. }
  405. return false
  406. }
  407. func (a *allowedKEXAlgs) String() string {
  408. return fmt.Sprintf("allowedKEXAlgs: %v", *a)
  409. }
  410. func (a *allowedKEXAlgs) Set(value string) error {
  411. *a = append(*a, strings.TrimSpace(value))
  412. return nil
  413. }
  414. func (a allowedCipherAlgs) allowed(c xsnet.CSCipherAlg) bool {
  415. for i := 0; i < len(a); i++ {
  416. if a[i] == "C_all" || a[i] == c.String() {
  417. return true
  418. }
  419. }
  420. return false
  421. }
  422. func (a *allowedCipherAlgs) String() string {
  423. return fmt.Sprintf("allowedCipherAlgs: %v", *a)
  424. }
  425. func (a *allowedCipherAlgs) Set(value string) error {
  426. *a = append(*a, strings.TrimSpace(value))
  427. return nil
  428. }
  429. func (a allowedHMACAlgs) allowed(h xsnet.CSHmacAlg) bool {
  430. for i := 0; i < len(a); i++ {
  431. if a[i] == "H_all" || a[i] == h.String() {
  432. return true
  433. }
  434. }
  435. return false
  436. }
  437. func (a *allowedHMACAlgs) String() string {
  438. return fmt.Sprintf("allowedHMACAlgs: %v", *a)
  439. }
  440. func (a *allowedHMACAlgs) Set(value string) error {
  441. *a = append(*a, strings.TrimSpace(value))
  442. return nil
  443. }
  444. // Main server that listens and spawns goroutines for each
  445. // connecting client to serve interactive or file copy sessions
  446. // and any requested tunnels.
  447. // Note that this server does not do UNIX forks of itself to give
  448. // each client its own separate manager process, so if the main
  449. // daemon dies, all clients will be rudely disconnected.
  450. // Consider this when planning to restart or upgrade in-place an installation.
  451. // TODO: reduce gocyclo
  452. func main() {
  453. var vopt bool
  454. var chaffEnabled bool
  455. var chaffFreqMin uint
  456. var chaffFreqMax uint
  457. var chaffBytesMax uint
  458. var dbg bool
  459. var laddr string
  460. var useSystemPasswd bool
  461. flag.BoolVar(&vopt, "v", false, "show version")
  462. flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen")
  463. flag.StringVar(&kcpMode, "K", "unused", `set to one of ["KCP_NONE","KCP_AES", "KCP_BLOWFISH", "KCP_CAST5", "KCP_SM4", "KCP_SALSA20", "KCP_SIMPLEXOR", "KCP_TEA", "KCP_3DES", "KCP_TWOFISH", "KCP_XTEA"] to use KCP (github.com/xtaci/kcp-go) reliable UDP instead of TCP`)
  464. flag.BoolVar(&useSysLogin, "L", false, "use system login")
  465. flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts")
  466. flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min (msecs)")
  467. flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt freq max (msecs)")
  468. flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt size max (bytes)")
  469. flag.BoolVar(&useSystemPasswd, "s", true, "use system shadow passwds")
  470. flag.BoolVar(&dbg, "d", false, "debug logging")
  471. flag.Var(&aKEXAlgs, "aK", `List of allowed KEX algs (eg. 'KEXAlgA KEXAlgB ... KEXAlgN') (default allow all)`)
  472. flag.Var(&aCipherAlgs, "aC", `List of allowed ciphers (eg. 'CipherAlgA CipherAlgB ... CipherAlgN') (default allow all)`)
  473. flag.Var(&aHMACAlgs, "aH", `List of allowed HMACs (eg. 'HMACAlgA HMACAlgB ... HMACAlgN') (default allow all)`)
  474. flag.Parse()
  475. if vopt {
  476. fmt.Printf("version %s (%s)\n", version, gitCommit)
  477. os.Exit(0)
  478. }
  479. {
  480. me, e := user.Current()
  481. if e != nil || me.Uid != "0" {
  482. log.Fatal("Must run as root.")
  483. }
  484. }
  485. // Enforce some sane min/max vals on chaff flags
  486. if chaffFreqMin < 2 {
  487. chaffFreqMin = 2
  488. }
  489. if chaffFreqMax == 0 {
  490. chaffFreqMax = chaffFreqMin + 1
  491. }
  492. if chaffBytesMax == 0 || chaffBytesMax > 4096 {
  493. chaffBytesMax = 64
  494. }
  495. Log, _ = logger.New(logger.LOG_DAEMON|logger.LOG_DEBUG|logger.LOG_NOTICE|logger.LOG_ERR, "xsd") // nolint: gosec
  496. xsnet.Init(dbg, "xsd", logger.LOG_DAEMON|logger.LOG_DEBUG|logger.LOG_NOTICE|logger.LOG_ERR)
  497. if dbg {
  498. log.SetOutput(Log)
  499. } else {
  500. log.SetOutput(ioutil.Discard)
  501. }
  502. // Set up allowed algs, if specified (default allow all)
  503. if len(aKEXAlgs) == 0 {
  504. aKEXAlgs = []string{"KEX_all"}
  505. }
  506. logger.LogNotice(fmt.Sprintf("Allowed KEXAlgs: %v\n", aKEXAlgs)) // nolint: gosec,errcheck
  507. if len(aCipherAlgs) == 0 {
  508. aCipherAlgs = []string{"C_all"}
  509. }
  510. logger.LogNotice(fmt.Sprintf("Allowed CipherAlgs: %v\n", aCipherAlgs)) // nolint: gosec,errcheck
  511. if len(aHMACAlgs) == 0 {
  512. aHMACAlgs = []string{"H_all"}
  513. }
  514. logger.LogNotice(fmt.Sprintf("Allowed HMACAlgs: %v\n", aHMACAlgs)) // nolint: gosec,errcheck
  515. // Set up handler for daemon signalling
  516. exitCh := make(chan os.Signal, 1)
  517. signal.Notify(exitCh, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGINT), os.Signal(syscall.SIGHUP), os.Signal(syscall.SIGUSR1), os.Signal(syscall.SIGUSR2))
  518. go func() {
  519. for {
  520. sig := <-exitCh
  521. switch sig.String() {
  522. case "terminated":
  523. logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) // nolint: gosec,errcheck
  524. signal.Reset()
  525. syscall.Kill(0, syscall.SIGTERM) // nolint: gosec,errcheck
  526. case "interrupt":
  527. logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) // nolint: gosec,errcheck
  528. signal.Reset()
  529. syscall.Kill(0, syscall.SIGINT) // nolint: gosec,errcheck
  530. case "hangup":
  531. logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig)) // nolint:gosec,errcheck
  532. default:
  533. logger.LogNotice(fmt.Sprintf("[Got signal: %s - ignored]", sig)) // nolint: gosec,errcheck
  534. }
  535. }
  536. }()
  537. proto := "tcp"
  538. if kcpMode != "unused" {
  539. proto = "kcp"
  540. }
  541. l, err := xsnet.Listen(proto, laddr, kcpMode)
  542. if err != nil {
  543. log.Fatal(err)
  544. }
  545. defer l.Close() // nolint: errcheck
  546. log.Println("Serving on", laddr)
  547. for {
  548. // Wait for a connection.
  549. // Then check if client-proposed algs are allowed
  550. conn, err := l.Accept()
  551. if err != nil {
  552. log.Printf("Accept() got error(%v), hanging up.\n", err)
  553. } else if !aKEXAlgs.allowed(conn.KEX()) {
  554. log.Printf("Accept() rejected for banned KEX alg %d, hanging up.\n", conn.KEX())
  555. conn.SetStatus(xsnet.CSEKEXAlgDenied)
  556. conn.Close()
  557. } else if !aCipherAlgs.allowed(conn.CAlg()) {
  558. log.Printf("Accept() rejected for banned Cipher alg %d, hanging up.\n", conn.CAlg())
  559. conn.SetStatus(xsnet.CSECipherAlgDenied)
  560. conn.Close()
  561. } else if !aHMACAlgs.allowed(conn.HAlg()) {
  562. log.Printf("Accept() rejected for banned HMAC alg %d, hanging up.\n", conn.HAlg())
  563. conn.SetStatus(xsnet.CSEHMACAlgDenied)
  564. conn.Close()
  565. } else {
  566. log.Println("Accepted client")
  567. // Set up chaffing to client
  568. // Will only start when runShellAs() is called
  569. // after stdin/stdout are hooked up
  570. conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // configure server->client chaffing
  571. // Handle the connection in a new goroutine.
  572. // The loop then returns to accepting, so that
  573. // multiple connections may be served concurrently.
  574. go func(hc *xsnet.Conn) (e error) {
  575. defer hc.Close() // nolint: errcheck
  576. // Start login timeout here and disconnect if user/pass phase stalls
  577. loginTimeout := time.AfterFunc(30*time.Second, func() {
  578. logger.LogNotice(fmt.Sprintln("Login timed out")) // nolint: errcheck,gosec
  579. hc.Write([]byte{0}) // nolint: gosec,errcheck
  580. hc.Close()
  581. })
  582. //We use io.ReadFull() here to guarantee we consume
  583. //just the data we want for the xs.Session, and no more.
  584. //Otherwise data will be sitting in the channel that isn't
  585. //passed down to the command handlers.
  586. var rec xs.Session
  587. var len1, len2, len3, len4, len5, len6 uint32
  588. n, err := fmt.Fscanf(hc, "%d %d %d %d %d %d\n", &len1, &len2, &len3, &len4, &len5, &len6)
  589. log.Printf("xs.Session read:%d %d %d %d %d %d\n", len1, len2, len3, len4, len5, len6)
  590. if err != nil || n < 6 {
  591. log.Println("[Bad xs.Session fmt]")
  592. return err
  593. }
  594. tmp := make([]byte, len1)
  595. _, err = io.ReadFull(hc, tmp)
  596. if err != nil {
  597. log.Println("[Bad xs.Session.Op]")
  598. return err
  599. }
  600. rec.SetOp(tmp)
  601. tmp = make([]byte, len2)
  602. _, err = io.ReadFull(hc, tmp)
  603. if err != nil {
  604. log.Println("[Bad xs.Session.Who]")
  605. return err
  606. }
  607. rec.SetWho(tmp)
  608. tmp = make([]byte, len3)
  609. _, err = io.ReadFull(hc, tmp)
  610. if err != nil {
  611. log.Println("[Bad xs.Session.ConnHost]")
  612. return err
  613. }
  614. rec.SetConnHost(tmp)
  615. tmp = make([]byte, len4)
  616. _, err = io.ReadFull(hc, tmp)
  617. if err != nil {
  618. log.Println("[Bad xs.Session.TermType]")
  619. return err
  620. }
  621. rec.SetTermType(tmp)
  622. tmp = make([]byte, len5)
  623. _, err = io.ReadFull(hc, tmp)
  624. if err != nil {
  625. log.Println("[Bad xs.Session.Cmd]")
  626. return err
  627. }
  628. rec.SetCmd(tmp)
  629. tmp = make([]byte, len6)
  630. _, err = io.ReadFull(hc, tmp)
  631. if err != nil {
  632. log.Println("[Bad xs.Session.AuthCookie]")
  633. return err
  634. }
  635. rec.SetAuthCookie(tmp)
  636. log.Printf("[xs.Session: op:%c who:%s connhost:%s cmd:%s auth:****]\n",
  637. rec.Op()[0], string(rec.Who()), string(rec.ConnHost()), string(rec.Cmd()))
  638. var valid bool
  639. var allowedCmds string // Currently unused
  640. if xs.AuthUserByToken(xs.NewAuthCtx(), string(rec.Who()), string(rec.ConnHost()), string(rec.AuthCookie(true))) {
  641. valid = true
  642. } else {
  643. if useSystemPasswd {
  644. //var passErr error
  645. valid, _ /*passErr*/ = xs.VerifyPass(xs.NewAuthCtx(), string(rec.Who()), string(rec.AuthCookie(true)))
  646. } else {
  647. valid, allowedCmds = xs.AuthUserByPasswd(xs.NewAuthCtx(), string(rec.Who()), string(rec.AuthCookie(true)), "/etc/xs.passwd")
  648. }
  649. }
  650. _ = loginTimeout.Stop()
  651. // Security scrub
  652. rec.ClearAuthCookie()
  653. // Tell client if auth was valid
  654. if valid {
  655. hc.Write([]byte{1}) // nolint: gosec,errcheck
  656. } else {
  657. logger.LogNotice(fmt.Sprintln("Invalid user", string(rec.Who()))) // nolint: errcheck,gosec
  658. hc.Write([]byte{0}) // nolint: gosec,errcheck
  659. return
  660. }
  661. log.Printf("[allowedCmds:%s]\n", allowedCmds)
  662. if rec.Op()[0] == 'A' {
  663. // Generate automated login token
  664. addr := hc.RemoteAddr()
  665. hname := goutmp.GetHost(addr.String())
  666. logger.LogNotice(fmt.Sprintf("[Generating autologin token for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  667. token := GenAuthToken(string(rec.Who()), string(rec.ConnHost()))
  668. tokenCmd := fmt.Sprintf("echo \"%s\" | tee -a ~/.xs_id", token)
  669. cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), tokenCmd, false, hc, chaffEnabled)
  670. // Returned hopefully via an EOF or exit/logout;
  671. // Clear current op so user can enter next, or EOF
  672. rec.SetOp([]byte{0})
  673. if runErr != nil {
  674. logger.LogErr(fmt.Sprintf("[Error generating autologin token for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  675. } else {
  676. log.Printf("[Autologin token generation completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)
  677. hc.SetStatus(xsnet.CSOType(cmdStatus))
  678. }
  679. } else if rec.Op()[0] == 'c' {
  680. // Non-interactive command
  681. addr := hc.RemoteAddr()
  682. hname := goutmp.GetHost(addr.String())
  683. logger.LogNotice(fmt.Sprintf("[Running command for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  684. cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), string(rec.Cmd()), false, hc, chaffEnabled)
  685. // Returned hopefully via an EOF or exit/logout;
  686. // Clear current op so user can enter next, or EOF
  687. rec.SetOp([]byte{0})
  688. if runErr != nil {
  689. logger.LogErr(fmt.Sprintf("[Error spawning cmd for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  690. } else {
  691. logger.LogNotice(fmt.Sprintf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
  692. hc.SetStatus(xsnet.CSOType(cmdStatus))
  693. }
  694. } else if rec.Op()[0] == 's' {
  695. // Interactive session
  696. addr := hc.RemoteAddr()
  697. hname := goutmp.GetHost(addr.String())
  698. logger.LogNotice(fmt.Sprintf("[Running shell for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  699. cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), string(rec.Cmd()), true, hc, chaffEnabled)
  700. // Returned hopefully via an EOF or exit/logout;
  701. // Clear current op so user can enter next, or EOF
  702. rec.SetOp([]byte{0})
  703. if runErr != nil {
  704. Log.Err(fmt.Sprintf("[Error spawning shell for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  705. } else {
  706. logger.LogNotice(fmt.Sprintf("[Shell completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
  707. hc.SetStatus(xsnet.CSOType(cmdStatus))
  708. }
  709. } else if rec.Op()[0] == 'D' {
  710. // File copy (destination) operation - client copy to server
  711. log.Printf("[Client->Server copy]\n")
  712. addr := hc.RemoteAddr()
  713. hname := goutmp.GetHost(addr.String())
  714. logger.LogNotice(fmt.Sprintf("[Running copy for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  715. cmdStatus, runErr := runClientToServerCopyAs(string(rec.Who()), string(rec.TermType()), hc, string(rec.Cmd()), chaffEnabled)
  716. // Returned hopefully via an EOF or exit/logout;
  717. // Clear current op so user can enter next, or EOF
  718. rec.SetOp([]byte{0})
  719. if runErr != nil {
  720. logger.LogErr(fmt.Sprintf("[Error running cp for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  721. } else {
  722. logger.LogNotice(fmt.Sprintf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
  723. }
  724. // TODO: Test this with huge files.. see Bug #22 - do we need to
  725. // sync w/sender (client) that we've gotten all data?
  726. hc.SetStatus(xsnet.CSOType(cmdStatus))
  727. // Send CSOExitStatus *before* client closes channel
  728. s := make([]byte, 4)
  729. binary.BigEndian.PutUint32(s, cmdStatus)
  730. log.Printf("** cp writing closeStat %d at Close()\n", cmdStatus)
  731. hc.WritePacket(s, xsnet.CSOExitStatus) // nolint: gosec,errcheck
  732. } else if rec.Op()[0] == 'S' {
  733. // File copy (src) operation - server copy to client
  734. log.Printf("[Server->Client copy]\n")
  735. addr := hc.RemoteAddr()
  736. hname := goutmp.GetHost(addr.String())
  737. logger.LogNotice(fmt.Sprintf("[Running copy for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  738. cmdStatus, runErr := runServerToClientCopyAs(string(rec.Who()), string(rec.TermType()), hc, string(rec.Cmd()), chaffEnabled)
  739. if runErr != nil {
  740. logger.LogErr(fmt.Sprintf("[Error spawning cp for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
  741. } else {
  742. // Returned hopefully via an EOF or exit/logout;
  743. logger.LogNotice(fmt.Sprintf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
  744. }
  745. // HACK: Bug #22: (xc) Need to wait for rcvr to get final data
  746. // TODO: Await specific msg from client to inform they have gotten all data from the tarpipe
  747. time.Sleep(time.Duration(900 * time.Millisecond)) // Let rcvr set this on setup?
  748. // Clear current op so user can enter next, or EOF
  749. rec.SetOp([]byte{0})
  750. hc.SetStatus(xsnet.CSOType(cmdStatus))
  751. //fmt.Println("Waiting for EOF from other end.")
  752. //_, _ = hc.Read(nil /*ackByte*/)
  753. //fmt.Println("Got remote end ack.")
  754. } else {
  755. logger.LogErr(fmt.Sprintln("[Bad xs.Session]")) // nolint: gosec,errcheck
  756. }
  757. return
  758. }(&conn) // nolint: errcheck
  759. } // Accept() success
  760. } //endfor
  761. //logger.LogNotice(fmt.Sprintln("[Exiting]")) // nolint: gosec,errcheck
  762. }