incubator_linux.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux
  4. package tailssh
  5. import (
  6. "context"
  7. "fmt"
  8. "os"
  9. "syscall"
  10. "time"
  11. "unsafe"
  12. "github.com/godbus/dbus/v5"
  13. "tailscale.com/types/logger"
  14. )
  15. func init() {
  16. ptyName = ptyNameLinux
  17. maybeStartLoginSession = maybeStartLoginSessionLinux
  18. }
  19. func ptyNameLinux(f *os.File) (string, error) {
  20. var n uint32
  21. _, _, e := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
  22. if e != 0 {
  23. return "", e
  24. }
  25. return fmt.Sprintf("pts/%d", n), nil
  26. }
  27. // callLogin1 invokes the provided method of the "login1" service over D-Bus.
  28. // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
  29. func callLogin1(method string, flags dbus.Flags, args ...any) (*dbus.Call, error) {
  30. conn, err := dbus.SystemBus()
  31. if err != nil {
  32. // DBus probably not running.
  33. return nil, err
  34. }
  35. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  36. defer cancel()
  37. name, objectPath := "org.freedesktop.login1", "/org/freedesktop/login1"
  38. obj := conn.Object(name, dbus.ObjectPath(objectPath))
  39. call := obj.CallWithContext(ctx, method, flags, args...)
  40. if call.Err != nil {
  41. return nil, call.Err
  42. }
  43. return call, nil
  44. }
  45. // createSessionArgs is a wrapper struct for the Login1.Manager.CreateSession args.
  46. // The CreateSession API arguments and response types are defined here:
  47. // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
  48. type createSessionArgs struct {
  49. uid uint32 // User ID being logged in.
  50. pid uint32 // Process ID for the session, 0 means current process.
  51. service string // Service creating the session.
  52. typ string // Type of login (oneof unspecified, tty, x11).
  53. class string // Type of session class (oneof user, greeter, lock-screen).
  54. desktop string // the desktop environment.
  55. seat string // the seat this session belongs to, empty otherwise.
  56. vtnr uint32 // the virtual terminal number of the session if there is any, 0 otherwise.
  57. tty string // the kernel TTY path of the session if this is a text login, empty otherwise.
  58. display string // the X11 display name if this is a graphical login, empty otherwise.
  59. remote bool // whether the session is remote.
  60. remoteUser string // the remote user if this is a remote session, empty otherwise.
  61. remoteHost string // the remote host if this is a remote session, empty otherwise.
  62. properties []struct { // This is unused and exists just to make the marshaling work
  63. S string
  64. V dbus.Variant
  65. }
  66. }
  67. func (a createSessionArgs) args() []any {
  68. return []any{
  69. a.uid,
  70. a.pid,
  71. a.service,
  72. a.typ,
  73. a.class,
  74. a.desktop,
  75. a.seat,
  76. a.vtnr,
  77. a.tty,
  78. a.display,
  79. a.remote,
  80. a.remoteUser,
  81. a.remoteHost,
  82. a.properties,
  83. }
  84. }
  85. // createSessionResp is a wrapper struct for the Login1.Manager.CreateSession response.
  86. // The CreateSession API arguments and response types are defined here:
  87. // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
  88. type createSessionResp struct {
  89. sessionID string
  90. objectPath dbus.ObjectPath
  91. runtimePath string
  92. fifoFD dbus.UnixFD
  93. uid uint32
  94. seatID string
  95. vtnr uint32
  96. existing bool // whether a new session was created.
  97. }
  98. // createSession creates a tty user login session for the provided uid.
  99. func createSession(uid uint32, remoteUser, remoteHost, tty string) (createSessionResp, error) {
  100. a := createSessionArgs{
  101. uid: uid,
  102. service: "tailscaled",
  103. typ: "tty",
  104. class: "user",
  105. tty: tty,
  106. remote: true,
  107. remoteUser: remoteUser,
  108. remoteHost: remoteHost,
  109. }
  110. call, err := callLogin1("org.freedesktop.login1.Manager.CreateSession", 0, a.args()...)
  111. if err != nil {
  112. return createSessionResp{}, err
  113. }
  114. return createSessionResp{
  115. sessionID: call.Body[0].(string),
  116. objectPath: call.Body[1].(dbus.ObjectPath),
  117. runtimePath: call.Body[2].(string),
  118. fifoFD: call.Body[3].(dbus.UnixFD),
  119. uid: call.Body[4].(uint32),
  120. seatID: call.Body[5].(string),
  121. vtnr: call.Body[6].(uint32),
  122. existing: call.Body[7].(bool),
  123. }, nil
  124. }
  125. // releaseSession releases the session identified by sessionID.
  126. func releaseSession(sessionID string) error {
  127. // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
  128. _, err := callLogin1("org.freedesktop.login1.Manager.ReleaseSession", dbus.FlagNoReplyExpected, sessionID)
  129. return err
  130. }
  131. // maybeStartLoginSessionLinux is the linux implementation of maybeStartLoginSession.
  132. func maybeStartLoginSessionLinux(dlogf logger.Logf, ia incubatorArgs) func() error {
  133. if os.Geteuid() != 0 {
  134. return nil
  135. }
  136. dlogf("starting session for user %d", ia.uid)
  137. // The only way we can actually start a new session is if we are
  138. // running outside one and are root, which is typically the case
  139. // for systemd managed tailscaled.
  140. resp, err := createSession(uint32(ia.uid), ia.remoteUser, ia.remoteIP, ia.ttyName)
  141. if err != nil {
  142. // TODO(maisem): figure out if we are running in a session.
  143. // We can look at the DBus GetSessionByPID API.
  144. // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
  145. // For now best effort is fine.
  146. dlogf("ssh: failed to CreateSession for user %q (%d) %v", ia.localUser, ia.uid, err)
  147. return nil
  148. }
  149. os.Setenv("DBUS_SESSION_BUS_ADDRESS", fmt.Sprintf("unix:path=%v/bus", resp.runtimePath))
  150. if !resp.existing {
  151. return func() error {
  152. return releaseSession(resp.sessionID)
  153. }
  154. }
  155. return nil
  156. }