Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
//go:build linux && !android
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.htmlfunc 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.htmltype 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.htmltype 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}