1// Copyright 2018 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4// +build windows
5
6package libkb
7
8import (
9	"syscall"
10	"unsafe"
11
12	"github.com/keybase/client/go/logger"
13	"golang.org/x/sys/windows"
14)
15
16var (
17	modkernel32        = windows.NewLazySystemDLL("kernel32.dll")
18	procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
19)
20
21const ERROR_PIPE_BUSY = 231
22
23type _PipeBusyError struct{}
24
25var PipeBusyError _PipeBusyError
26
27func (e _PipeBusyError) Error() string {
28	return "All pipe instances are busy"
29}
30
31func waitNamedPipe(name string, timeout uint32) (err error) {
32	rawName, e1 := syscall.UTF16PtrFromString(name)
33	if e1 != nil {
34		return e1
35	}
36
37	r1, _, e2 := procWaitNamedPipeW.Call(uintptr(unsafe.Pointer(rawName)), uintptr(timeout))
38	if r1 == 0 {
39		return e2
40	}
41	return
42}
43
44// currentProcessUserSid is a utility to get the
45// SID of the current user running the process.
46func currentProcessUserSid() (*windows.SID, error) {
47	tok, err := windows.OpenCurrentProcessToken()
48	if err != nil {
49		return nil, err
50	}
51	defer tok.Close()
52	tokUser, err := tok.GetTokenUser()
53	if err != nil {
54		return nil, err
55	}
56	return (*windows.SID)(tokUser.User.Sid), nil
57}
58
59// currentProcessUserSid is a utility to get the
60// SID of the named pipe
61func GetFileUserSid(name string) (*windows.SID, error) {
62	var userSID *windows.SID
63	var secDesc windows.Handle
64
65	err := GetNamedSecurityInfo(name, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &userSID, nil, nil, nil, &secDesc)
66	if err != nil {
67		return nil, err
68	}
69	return userSID, nil
70}
71
72type AccountInfo struct {
73	Account string `json:"account"`
74	Domain  string `json:"domain"`
75	Type    uint32 `json:"type"`
76	SID     string `json:"SID"`
77	Err     error  `json:"error"`
78}
79
80type PipeOwnerInfo struct {
81	IsOwner     bool        `json:"isOwner"`
82	PipeAccount AccountInfo `json:"pipe"`
83	UserAccount AccountInfo `json:"user"`
84}
85
86func IsPipeowner(log logger.Logger, name string) (owner PipeOwnerInfo, err error) {
87	log.Debug("+ IsPipeowner(%s)", name)
88	defer func() {
89		log.Debug("- IsPiperowner -> (%v, %v)", owner, err)
90	}()
91	userSid, err := currentProcessUserSid()
92	if err != nil {
93		return owner, err
94	}
95
96	pipeSid, err := GetFileUserSid(name)
97	if err == PipeBusyError {
98		// If at least one instance of the pipe has been created, this function
99		// will wait timeout milliseconds for it to become available.
100		// It will return immediately regardless of timeout, if no instances
101		// of the named pipe have been created yet.
102		// If this returns with no error, there is a pipe available.
103		err2 := waitNamedPipe(name, 1000)
104		if err2 != nil {
105			return owner, err // return original busy error
106		}
107		pipeSid, err = GetFileUserSid(name)
108	}
109	if err != nil {
110		return owner, err
111	}
112	owner.IsOwner = windows.EqualSid(pipeSid, userSid)
113	owner.PipeAccount.Account, owner.PipeAccount.Domain, owner.PipeAccount.Type, owner.PipeAccount.Err = pipeSid.LookupAccount("")
114	owner.PipeAccount.SID, err = pipeSid.String()
115	if err != nil {
116		log.Errorf("error getting owner SID: %s", err.Error())
117	}
118	owner.UserAccount.Account, owner.UserAccount.Domain, owner.UserAccount.Type, owner.UserAccount.Err = userSid.LookupAccount("")
119	owner.UserAccount.SID, err = userSid.String()
120	if err != nil {
121		log.Errorf("error getting user SID: %s", err.Error())
122	}
123
124	if !owner.IsOwner {
125		// If the pipe is served by an admin, let local security policies control access
126		// https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
127		if owner.PipeAccount.SID == "S-1-5-32-544" && owner.PipeAccount.Type == syscall.SidTypeAlias {
128			owner.IsOwner = true
129		}
130	}
131	log.Debug("%v", owner)
132	return owner, nil
133}
134