tailscale/ipn/ipnauth/ipnauth_windows.go

185 lines
3.9 KiB
Go
Raw Permalink Normal View History

// 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"
"tailscale.com/util/winutil"
)
// 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
}
isMember, err := t.t.IsMember(baSID)
if err != nil {
return false, err
}
if isMember {
return true, nil
}
isLimited, err := winutil.IsTokenLimited(t.t)
if err != nil || !isLimited {
return false, err
}
// Try to obtain a linked token, and if present, check it.
// (This should be the elevated token associated with limited UAC accounts.)
linkedToken, err := t.t.GetLinkedToken()
if err != nil {
return false, err
}
defer linkedToken.Close()
return linkedToken.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
}