| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- //go:build linux
- package tailssh
- import (
- "context"
- "fmt"
- "os"
- "syscall"
- "time"
- "unsafe"
- "github.com/godbus/dbus/v5"
- "tailscale.com/types/logger"
- )
- func init() {
- ptyName = ptyNameLinux
- maybeStartLoginSession = maybeStartLoginSessionLinux
- }
- func ptyNameLinux(f *os.File) (string, error) {
- var n uint32
- _, _, e := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
- if e != 0 {
- return "", e
- }
- return fmt.Sprintf("pts/%d", n), nil
- }
- // callLogin1 invokes the provided method of the "login1" service over D-Bus.
- // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
- func callLogin1(method string, flags dbus.Flags, args ...any) (*dbus.Call, error) {
- conn, err := dbus.SystemBus()
- if err != nil {
- // DBus probably not running.
- return nil, err
- }
- ctx, cancel := context.WithTimeout(context.Background(), time.Second)
- defer cancel()
- name, objectPath := "org.freedesktop.login1", "/org/freedesktop/login1"
- obj := conn.Object(name, dbus.ObjectPath(objectPath))
- call := obj.CallWithContext(ctx, method, flags, args...)
- if call.Err != nil {
- return nil, call.Err
- }
- return call, nil
- }
- // createSessionArgs is a wrapper struct for the Login1.Manager.CreateSession args.
- // The CreateSession API arguments and response types are defined here:
- // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
- type createSessionArgs struct {
- uid uint32 // User ID being logged in.
- pid uint32 // Process ID for the session, 0 means current process.
- service string // Service creating the session.
- typ string // Type of login (oneof unspecified, tty, x11).
- class string // Type of session class (oneof user, greeter, lock-screen).
- desktop string // the desktop environment.
- seat string // the seat this session belongs to, empty otherwise.
- vtnr uint32 // the virtual terminal number of the session if there is any, 0 otherwise.
- tty string // the kernel TTY path of the session if this is a text login, empty otherwise.
- display string // the X11 display name if this is a graphical login, empty otherwise.
- remote bool // whether the session is remote.
- remoteUser string // the remote user if this is a remote session, empty otherwise.
- remoteHost string // the remote host if this is a remote session, empty otherwise.
- properties []struct { // This is unused and exists just to make the marshaling work
- S string
- V dbus.Variant
- }
- }
- func (a createSessionArgs) args() []any {
- return []any{
- a.uid,
- a.pid,
- a.service,
- a.typ,
- a.class,
- a.desktop,
- a.seat,
- a.vtnr,
- a.tty,
- a.display,
- a.remote,
- a.remoteUser,
- a.remoteHost,
- a.properties,
- }
- }
- // createSessionResp is a wrapper struct for the Login1.Manager.CreateSession response.
- // The CreateSession API arguments and response types are defined here:
- // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
- type createSessionResp struct {
- sessionID string
- objectPath dbus.ObjectPath
- runtimePath string
- fifoFD dbus.UnixFD
- uid uint32
- seatID string
- vtnr uint32
- existing bool // whether a new session was created.
- }
- // createSession creates a tty user login session for the provided uid.
- func createSession(uid uint32, remoteUser, remoteHost, tty string) (createSessionResp, error) {
- a := createSessionArgs{
- uid: uid,
- service: "tailscaled",
- typ: "tty",
- class: "user",
- tty: tty,
- remote: true,
- remoteUser: remoteUser,
- remoteHost: remoteHost,
- }
- call, err := callLogin1("org.freedesktop.login1.Manager.CreateSession", 0, a.args()...)
- if err != nil {
- return createSessionResp{}, err
- }
- return createSessionResp{
- sessionID: call.Body[0].(string),
- objectPath: call.Body[1].(dbus.ObjectPath),
- runtimePath: call.Body[2].(string),
- fifoFD: call.Body[3].(dbus.UnixFD),
- uid: call.Body[4].(uint32),
- seatID: call.Body[5].(string),
- vtnr: call.Body[6].(uint32),
- existing: call.Body[7].(bool),
- }, nil
- }
- // releaseSession releases the session identified by sessionID.
- func releaseSession(sessionID string) error {
- // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
- _, err := callLogin1("org.freedesktop.login1.Manager.ReleaseSession", dbus.FlagNoReplyExpected, sessionID)
- return err
- }
- // maybeStartLoginSessionLinux is the linux implementation of maybeStartLoginSession.
- func maybeStartLoginSessionLinux(dlogf logger.Logf, ia incubatorArgs) func() error {
- if os.Geteuid() != 0 {
- return nil
- }
- dlogf("starting session for user %d", ia.uid)
- // The only way we can actually start a new session is if we are
- // running outside one and are root, which is typically the case
- // for systemd managed tailscaled.
- resp, err := createSession(uint32(ia.uid), ia.remoteUser, ia.remoteIP, ia.ttyName)
- if err != nil {
- // TODO(maisem): figure out if we are running in a session.
- // We can look at the DBus GetSessionByPID API.
- // https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html
- // For now best effort is fine.
- dlogf("ssh: failed to CreateSession for user %q (%d) %v", ia.localUser, ia.uid, err)
- return nil
- }
- os.Setenv("DBUS_SESSION_BUS_ADDRESS", fmt.Sprintf("unix:path=%v/bus", resp.runtimePath))
- if !resp.existing {
- return func() error {
- return releaseSession(resp.sessionID)
- }
- }
- return nil
- }
|