mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 10:11:18 +01:00 
			
		
		
		
	As noted in #16048, the ./ssh/tailssh package failed to build on Android, because GOOS=android also matches the "linux" build tag. Exclude Android like iOS is excluded from macOS (darwin). This now works: $ GOOS=android go install ./ipn/ipnlocal ./ssh/tailssh The original PR at #16048 is also fine, but this stops the problem earlier. Updates #16048 Change-Id: Ie4a6f6966a012e510c9cb11dd0d1fa88c48fac37 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			174 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Tailscale Inc & AUTHORS
 | 
						|
// 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.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
 | 
						|
}
 |