163 lines
3.4 KiB
Go
163 lines
3.4 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package ipnauth
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"runtime"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/safesocket"
|
|
"tailscale.com/types/logger"
|
|
)
|
|
|
|
// GetConnIdentity extracts the identity information from the connection
|
|
// based on the user who owns the other end of the connection.
|
|
// If c is not backed by a named pipe, an error is returned.
|
|
func GetConnIdentity(logf logger.Logf, c net.Conn) (ci *ConnIdentity, err error) {
|
|
ci = &ConnIdentity{conn: c}
|
|
wcc, ok := c.(*safesocket.WindowsClientConn)
|
|
if !ok {
|
|
return nil, fmt.Errorf("not a WindowsClientConn: %T", c)
|
|
}
|
|
ci.pid, err = wcc.ClientPID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ci, nil
|
|
}
|
|
|
|
type token struct {
|
|
t windows.Token
|
|
}
|
|
|
|
func (t *token) UID() (ipn.WindowsUserID, error) {
|
|
sid, err := t.uid()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to look up user from token: %w", err)
|
|
}
|
|
|
|
return ipn.WindowsUserID(sid.String()), nil
|
|
}
|
|
|
|
func (t *token) Username() (string, error) {
|
|
sid, err := t.uid()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to look up user from token: %w", err)
|
|
}
|
|
|
|
username, domain, _, err := sid.LookupAccount("")
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to look up username from SID: %w", err)
|
|
}
|
|
|
|
return fmt.Sprintf(`%s\%s`, domain, username), nil
|
|
}
|
|
|
|
func (t *token) IsAdministrator() (bool, error) {
|
|
baSID, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return t.t.IsMember(baSID)
|
|
}
|
|
|
|
func (t *token) IsElevated() bool {
|
|
return t.t.IsElevated()
|
|
}
|
|
|
|
func (t *token) UserDir(folderID string) (string, error) {
|
|
guid, err := windows.GUIDFromString(folderID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return t.t.KnownFolderPath((*windows.KNOWNFOLDERID)(unsafe.Pointer(&guid)), 0)
|
|
}
|
|
|
|
func (t *token) Close() error {
|
|
if t.t == 0 {
|
|
return nil
|
|
}
|
|
if err := t.t.Close(); err != nil {
|
|
return err
|
|
}
|
|
t.t = 0
|
|
runtime.SetFinalizer(t, nil)
|
|
return nil
|
|
}
|
|
|
|
func (t *token) EqualUIDs(other WindowsToken) bool {
|
|
if t != nil && other == nil || t == nil && other != nil {
|
|
return false
|
|
}
|
|
ot, ok := other.(*token)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if t == ot {
|
|
return true
|
|
}
|
|
uid, err := t.uid()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
oUID, err := ot.uid()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return uid.Equals(oUID)
|
|
}
|
|
|
|
func (t *token) uid() (*windows.SID, error) {
|
|
tu, err := t.t.GetTokenUser()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return tu.User.Sid, nil
|
|
}
|
|
|
|
func (t *token) IsUID(uid ipn.WindowsUserID) bool {
|
|
tUID, err := t.UID()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return tUID == uid
|
|
}
|
|
|
|
// WindowsToken returns the WindowsToken representing the security context
|
|
// of the connection's client.
|
|
func (ci *ConnIdentity) WindowsToken() (WindowsToken, error) {
|
|
var wcc *safesocket.WindowsClientConn
|
|
var ok bool
|
|
if wcc, ok = ci.conn.(*safesocket.WindowsClientConn); !ok {
|
|
return nil, fmt.Errorf("not a WindowsClientConn: %T", ci.conn)
|
|
}
|
|
|
|
// We duplicate the token's handle so that the WindowsToken we return may have
|
|
// a lifetime independent from the original connection.
|
|
var h windows.Handle
|
|
if err := windows.DuplicateHandle(
|
|
windows.CurrentProcess(),
|
|
windows.Handle(wcc.Token()),
|
|
windows.CurrentProcess(),
|
|
&h,
|
|
0,
|
|
false,
|
|
windows.DUPLICATE_SAME_ACCESS,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &token{t: windows.Token(h)}
|
|
runtime.SetFinalizer(result, func(t *token) { t.Close() })
|
|
return result, nil
|
|
}
|