forked from tailscale/tailscale
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathincubator_linux.go
175 lines (158 loc) · 5.6 KB
/
incubator_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux
// +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(logf logger.Logf, ia incubatorArgs) (func() error, error) {
if os.Geteuid() != 0 {
return nil, nil
}
logf("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.
logf("ssh: failed to CreateSession for user %q (%d) %v", ia.localUser, ia.uid, err)
return nil, 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)
}, nil
}
return nil, nil
}