mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +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
 | |
| }
 |