|
|
@@ -6,53 +6,157 @@ package ipnauth
|
|
|
import (
|
|
|
"fmt"
|
|
|
"net"
|
|
|
- "syscall"
|
|
|
+ "runtime"
|
|
|
"unsafe"
|
|
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
"tailscale.com/ipn"
|
|
|
+ "tailscale.com/safesocket"
|
|
|
"tailscale.com/types/logger"
|
|
|
- "tailscale.com/util/pidowner"
|
|
|
)
|
|
|
|
|
|
-var (
|
|
|
- kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
|
- procGetNamedPipeClientProcessId = kernel32.NewProc("GetNamedPipeClientProcessId")
|
|
|
-)
|
|
|
-
|
|
|
-func getNamedPipeClientProcessId(h windows.Handle) (pid uint32, err error) {
|
|
|
- r1, _, err := procGetNamedPipeClientProcessId.Call(uintptr(h), uintptr(unsafe.Pointer(&pid)))
|
|
|
- if r1 > 0 {
|
|
|
- return pid, nil
|
|
|
- }
|
|
|
- return 0, err
|
|
|
-}
|
|
|
-
|
|
|
// 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}
|
|
|
- h, ok := c.(interface {
|
|
|
- Fd() uintptr
|
|
|
- })
|
|
|
+ wcc, ok := c.(*safesocket.WindowsClientConn)
|
|
|
if !ok {
|
|
|
- return ci, fmt.Errorf("not a windows handle: %T", c)
|
|
|
+ return nil, fmt.Errorf("not a WindowsClientConn: %T", c)
|
|
|
}
|
|
|
- pid, err := getNamedPipeClientProcessId(windows.Handle(h.Fd()))
|
|
|
+ ci.pid, err = wcc.ClientPID()
|
|
|
if err != nil {
|
|
|
- return ci, fmt.Errorf("getNamedPipeClientProcessId: %v", err)
|
|
|
+ return nil, err
|
|
|
}
|
|
|
- ci.pid = int(pid)
|
|
|
- uid, err := pidowner.OwnerOfPID(ci.pid)
|
|
|
+ return ci, nil
|
|
|
+}
|
|
|
+
|
|
|
+type token struct {
|
|
|
+ t windows.Token
|
|
|
+}
|
|
|
+
|
|
|
+func (t *token) UID() (ipn.WindowsUserID, error) {
|
|
|
+ sid, err := t.uid()
|
|
|
if err != nil {
|
|
|
- return ci, fmt.Errorf("failed to map connection's pid to a user (WSL?): %w", err)
|
|
|
+ return "", fmt.Errorf("failed to look up user from token: %w", err)
|
|
|
}
|
|
|
- ci.userID = ipn.WindowsUserID(uid)
|
|
|
- u, err := LookupUserFromID(logf, uid)
|
|
|
+
|
|
|
+ return ipn.WindowsUserID(sid.String()), nil
|
|
|
+}
|
|
|
+
|
|
|
+func (t *token) Username() (string, error) {
|
|
|
+ sid, err := t.uid()
|
|
|
if err != nil {
|
|
|
- return ci, fmt.Errorf("failed to look up user from userid: %w", err)
|
|
|
+ return "", fmt.Errorf("failed to look up user from token: %w", err)
|
|
|
}
|
|
|
- ci.user = u
|
|
|
- return ci, nil
|
|
|
+
|
|
|
+ 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
|
|
|
}
|