incubator_linux.go 5.6 KB

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