2022-11-09 11:27:54 -08:00
|
|
|
package platform
|
2020-11-04 23:56:12 -08:00
|
|
|
|
|
|
|
import (
|
2022-02-07 09:50:52 -08:00
|
|
|
"errors"
|
2022-11-22 23:01:46 -08:00
|
|
|
"fmt"
|
2022-01-26 01:23:18 -08:00
|
|
|
"oh-my-posh/regex"
|
2022-11-15 03:43:45 -08:00
|
|
|
"reflect"
|
2020-11-04 23:56:12 -08:00
|
|
|
"strings"
|
|
|
|
"syscall"
|
2022-02-07 09:50:52 -08:00
|
|
|
"unicode/utf16"
|
2020-11-04 23:56:12 -08:00
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
)
|
|
|
|
|
|
|
|
// win32 specific code
|
|
|
|
|
|
|
|
// win32 dll load and function definitions
|
|
|
|
var (
|
|
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
|
|
procEnumWindows = user32.NewProc("EnumWindows")
|
|
|
|
procGetWindowTextW = user32.NewProc("GetWindowTextW")
|
|
|
|
procGetWindowThreadProcessID = user32.NewProc("GetWindowThreadProcessId")
|
2022-03-05 23:41:02 -08:00
|
|
|
|
|
|
|
psapi = syscall.NewLazyDLL("psapi.dll")
|
|
|
|
getModuleBaseNameA = psapi.NewProc("GetModuleBaseNameA")
|
2022-09-12 03:55:57 -07:00
|
|
|
|
|
|
|
iphlpapi = syscall.NewLazyDLL("iphlpapi.dll")
|
|
|
|
hGetIfTable2 = iphlpapi.NewProc("GetIfTable2")
|
2020-11-04 23:56:12 -08:00
|
|
|
)
|
|
|
|
|
2022-03-05 23:41:02 -08:00
|
|
|
// enumWindows call enumWindows from user32 and returns all active windows
|
2020-11-14 00:49:40 -08:00
|
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows
|
2022-03-05 23:41:02 -08:00
|
|
|
func enumWindows(enumFunc, lparam uintptr) (err error) {
|
2022-03-20 23:17:33 -07:00
|
|
|
r1, _, e1 := syscall.SyscallN(procEnumWindows.Addr(), enumFunc, lparam, 0)
|
2020-11-04 23:56:12 -08:00
|
|
|
if r1 == 0 {
|
|
|
|
if e1 != 0 {
|
|
|
|
err = error(e1)
|
|
|
|
} else {
|
|
|
|
err = syscall.EINVAL
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-05 23:41:02 -08:00
|
|
|
// getWindowText returns the title and text of a window from a window handle
|
2020-11-14 00:49:40 -08:00
|
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw
|
2022-03-05 23:41:02 -08:00
|
|
|
func getWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (length int32, err error) {
|
2022-03-20 23:17:33 -07:00
|
|
|
r0, _, e1 := syscall.SyscallN(procGetWindowTextW.Addr(), uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
|
2020-11-12 00:43:32 -08:00
|
|
|
length = int32(r0)
|
|
|
|
if length == 0 {
|
2020-11-04 23:56:12 -08:00
|
|
|
if e1 != 0 {
|
|
|
|
err = error(e1)
|
|
|
|
} else {
|
|
|
|
err = syscall.EINVAL
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-05 23:41:02 -08:00
|
|
|
func getWindowFileName(handle syscall.Handle) (string, error) {
|
|
|
|
var pid int
|
2022-04-05 12:15:48 -07:00
|
|
|
_, _, _ = procGetWindowThreadProcessID.Call(uintptr(handle), uintptr(unsafe.Pointer(&pid)))
|
2022-03-05 23:41:02 -08:00
|
|
|
const query = windows.PROCESS_QUERY_INFORMATION | windows.PROCESS_VM_READ
|
|
|
|
h, err := windows.OpenProcess(query, false, uint32(pid))
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.New("unable to open window process")
|
|
|
|
}
|
|
|
|
buf := [1024]byte{}
|
2022-04-05 12:15:48 -07:00
|
|
|
length, _, _ := getModuleBaseNameA.Call(uintptr(h), 0, uintptr(unsafe.Pointer(&buf)), 1024)
|
2022-03-05 23:41:02 -08:00
|
|
|
filename := string(buf[:length])
|
|
|
|
return strings.ToLower(filename), nil
|
|
|
|
}
|
|
|
|
|
2020-11-12 00:43:32 -08:00
|
|
|
// GetWindowTitle searches for a window attached to the pid
|
2022-03-05 23:41:02 -08:00
|
|
|
func queryWindowTitles(processName, windowTitleRegex string) (string, error) {
|
2020-11-04 23:56:12 -08:00
|
|
|
var title string
|
2022-03-05 23:41:02 -08:00
|
|
|
// callback for EnumWindows
|
|
|
|
cb := syscall.NewCallback(func(handle syscall.Handle, pointer uintptr) uintptr {
|
|
|
|
fileName, err := getWindowFileName(handle)
|
|
|
|
if err != nil {
|
|
|
|
// ignore the error and continue enumeration
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
if processName != fileName {
|
|
|
|
// ignore the error and continue enumeration
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
b := make([]uint16, 200)
|
|
|
|
_, err = getWindowText(handle, &b[0], int32(len(b)))
|
|
|
|
if err != nil {
|
|
|
|
// ignore the error and continue enumeration
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
title = syscall.UTF16ToString(b)
|
|
|
|
if regex.MatchString(windowTitleRegex, title) {
|
|
|
|
// will cause EnumWindows to return 0 (error)
|
|
|
|
// but we don't want to enumerate all windows since we got what we want
|
|
|
|
return 0
|
2020-11-04 23:56:12 -08:00
|
|
|
}
|
|
|
|
return 1 // continue enumeration
|
|
|
|
})
|
|
|
|
// Enumerates all top-level windows on the screen
|
2020-11-14 00:49:40 -08:00
|
|
|
// The error is not checked because if EnumWindows is stopped bofere enumerating all windows
|
|
|
|
// it returns 0(error occurred) instead of 1(success)
|
|
|
|
// In our case, title will equal "" or the title of the window anyway
|
2022-04-04 22:53:57 -07:00
|
|
|
err := enumWindows(cb, 0)
|
2022-03-05 23:41:02 -08:00
|
|
|
if len(title) == 0 {
|
2022-04-05 04:11:23 -07:00
|
|
|
var message string
|
|
|
|
if err != nil {
|
|
|
|
message = err.Error()
|
|
|
|
}
|
|
|
|
return "", errors.New("no matching window title found\n" + message)
|
2022-03-05 23:41:02 -08:00
|
|
|
}
|
|
|
|
return title, nil
|
2020-11-04 23:56:12 -08:00
|
|
|
}
|
2021-11-24 04:47:30 -08:00
|
|
|
|
2022-08-04 23:33:02 -07:00
|
|
|
type REPARSE_DATA_BUFFER struct { //nolint: revive
|
2022-02-07 09:50:52 -08:00
|
|
|
ReparseTag uint32
|
|
|
|
ReparseDataLength uint16
|
|
|
|
Reserved uint16
|
|
|
|
DUMMYUNIONNAME byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type GenericDataBuffer struct {
|
|
|
|
DataBuffer [1]uint8
|
|
|
|
}
|
|
|
|
|
|
|
|
type AppExecLinkReparseBuffer struct {
|
|
|
|
Version uint32
|
|
|
|
StringList [1]uint16
|
|
|
|
}
|
|
|
|
|
2022-02-08 11:20:30 -08:00
|
|
|
func (rb *AppExecLinkReparseBuffer) Path() (string, error) {
|
2022-02-07 09:50:52 -08:00
|
|
|
UTF16ToStringPosition := func(s []uint16) (string, int) {
|
|
|
|
for i, v := range s {
|
|
|
|
if v == 0 {
|
|
|
|
s = s[0:i]
|
|
|
|
return string(utf16.Decode(s)), i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", 0
|
|
|
|
}
|
2022-02-08 11:20:30 -08:00
|
|
|
stringList := (*[0xffff]uint16)(unsafe.Pointer(&rb.StringList[0]))[0:]
|
2022-02-07 09:50:52 -08:00
|
|
|
var link string
|
2022-02-08 11:20:30 -08:00
|
|
|
var position int
|
2022-02-07 09:50:52 -08:00
|
|
|
for i := 0; i <= 2; i++ {
|
2022-02-08 11:20:30 -08:00
|
|
|
link, position = UTF16ToStringPosition(stringList)
|
2022-02-07 09:50:52 -08:00
|
|
|
position++
|
2022-02-08 11:20:30 -08:00
|
|
|
if position >= len(stringList) {
|
|
|
|
return "", errors.New("invalid AppExecLinkReparseBuffer")
|
|
|
|
}
|
|
|
|
stringList = stringList[position:]
|
2022-02-07 09:50:52 -08:00
|
|
|
}
|
2022-02-08 11:20:30 -08:00
|
|
|
return link, nil
|
2022-02-07 09:50:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// openSymlink calls CreateFile Windows API with FILE_FLAG_OPEN_REPARSE_POINT
|
|
|
|
// parameter, so that Windows does not follow symlink, if path is a symlink.
|
|
|
|
// openSymlink returns opened file handle.
|
|
|
|
func openSymlink(path string) (syscall.Handle, error) {
|
|
|
|
p, err := syscall.UTF16PtrFromString(path)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
|
|
|
|
// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
|
|
|
|
// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
|
|
|
|
attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
|
|
|
|
h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func readWinAppLink(path string) (string, error) {
|
|
|
|
h, err := openSymlink(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2022-08-04 23:33:02 -07:00
|
|
|
defer syscall.CloseHandle(h) //nolint: errcheck
|
2022-02-07 09:50:52 -08:00
|
|
|
|
|
|
|
rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
|
|
|
|
var bytesReturned uint32
|
|
|
|
err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
rdb := (*REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
|
|
|
|
rb := (*GenericDataBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME))
|
|
|
|
appExecLink := (*AppExecLinkReparseBuffer)(unsafe.Pointer(&rb.DataBuffer))
|
|
|
|
if appExecLink.Version != 3 {
|
2022-09-12 03:55:57 -07:00
|
|
|
return "", errors.New("unknown AppExecLink version")
|
2022-02-07 09:50:52 -08:00
|
|
|
}
|
2022-02-08 11:20:30 -08:00
|
|
|
return appExecLink.Path()
|
2022-02-07 09:50:52 -08:00
|
|
|
}
|
2022-11-15 03:43:45 -08:00
|
|
|
|
|
|
|
var (
|
|
|
|
advapi = syscall.NewLazyDLL("advapi32.dll")
|
|
|
|
procGetAce = advapi.NewProc("GetAce")
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ACCESS_DENIED_ACE_TYPE = 1 //nolint: revive
|
|
|
|
)
|
|
|
|
|
2022-11-21 06:46:59 -08:00
|
|
|
type accessMask uint32
|
|
|
|
|
|
|
|
func (m accessMask) canWrite() bool {
|
|
|
|
allowMask := ^(windows.GENERIC_WRITE | windows.GENERIC_ALL | windows.WRITE_DAC | windows.WRITE_OWNER)
|
|
|
|
return m&accessMask(allowMask) != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m accessMask) permissions() string {
|
|
|
|
var permissions []string
|
|
|
|
if m&windows.GENERIC_READ != 0 {
|
|
|
|
permissions = append(permissions, "GENERIC_READ")
|
|
|
|
}
|
|
|
|
if m&windows.GENERIC_WRITE != 0 {
|
|
|
|
permissions = append(permissions, "GENERIC_WRITE")
|
|
|
|
}
|
|
|
|
if m&windows.GENERIC_EXECUTE != 0 {
|
|
|
|
permissions = append(permissions, "GENERIC_EXECUTE")
|
|
|
|
}
|
|
|
|
if m&windows.GENERIC_ALL != 0 {
|
|
|
|
permissions = append(permissions, "GENERIC_ALL")
|
|
|
|
}
|
|
|
|
if m&windows.WRITE_DAC != 0 {
|
|
|
|
permissions = append(permissions, "WRITE_DAC")
|
|
|
|
}
|
|
|
|
if m&windows.WRITE_OWNER != 0 {
|
|
|
|
permissions = append(permissions, "WRITE_OWNER")
|
|
|
|
}
|
|
|
|
if m&windows.SYNCHRONIZE != 0 {
|
|
|
|
permissions = append(permissions, "SYNCHRONIZE")
|
|
|
|
}
|
|
|
|
if m&windows.DELETE != 0 {
|
|
|
|
permissions = append(permissions, "DELETE")
|
|
|
|
}
|
|
|
|
if m&windows.READ_CONTROL != 0 {
|
|
|
|
permissions = append(permissions, "READ_CONTROL")
|
|
|
|
}
|
|
|
|
if m&windows.ACCESS_SYSTEM_SECURITY != 0 {
|
|
|
|
permissions = append(permissions, "ACCESS_SYSTEM_SECURITY")
|
|
|
|
}
|
|
|
|
if m&windows.MAXIMUM_ALLOWED != 0 {
|
|
|
|
permissions = append(permissions, "MAXIMUM_ALLOWED")
|
|
|
|
}
|
|
|
|
return strings.Join(permissions, "\n")
|
|
|
|
}
|
|
|
|
|
2022-11-15 03:43:45 -08:00
|
|
|
type AccessAllowedAce struct {
|
|
|
|
AceType uint8
|
|
|
|
AceFlags uint8
|
|
|
|
AceSize uint16
|
2022-11-21 06:46:59 -08:00
|
|
|
AccessMask accessMask
|
2022-11-15 03:43:45 -08:00
|
|
|
SidStart uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCurrentUser() (sid *windows.SID, err error) {
|
|
|
|
token := windows.GetCurrentProcessToken()
|
|
|
|
defer token.Close()
|
|
|
|
|
|
|
|
tokenuser, err := token.GetTokenUser()
|
|
|
|
sid = tokenuser.User.Sid
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-21 06:46:59 -08:00
|
|
|
func (env *Shell) isWriteable(folder string) bool {
|
2022-11-15 03:43:45 -08:00
|
|
|
cu, err := getCurrentUser()
|
|
|
|
if err != nil {
|
|
|
|
// unable to get current user
|
2022-11-21 06:46:59 -08:00
|
|
|
env.Error("isWriteable", err)
|
2022-11-15 03:43:45 -08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
si, err := windows.GetNamedSecurityInfo(folder, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION)
|
|
|
|
if err != nil {
|
2022-11-21 06:46:59 -08:00
|
|
|
env.Error("isWriteable", err)
|
2022-11-15 03:43:45 -08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
dacl, _, err := si.DACL()
|
|
|
|
if err != nil || dacl == nil {
|
|
|
|
// no dacl implies full access
|
2022-11-21 06:46:59 -08:00
|
|
|
env.Debug("isWriteable", "no dacl")
|
2022-11-15 03:43:45 -08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
rs := reflect.ValueOf(dacl).Elem()
|
|
|
|
aceCount := rs.Field(3).Uint()
|
|
|
|
|
|
|
|
for i := uint64(0); i < aceCount; i++ {
|
|
|
|
ace := &AccessAllowedAce{}
|
|
|
|
|
|
|
|
ret, _, _ := procGetAce.Call(uintptr(unsafe.Pointer(dacl)), uintptr(i), uintptr(unsafe.Pointer(&ace)))
|
|
|
|
if ret == 0 {
|
2022-11-21 06:46:59 -08:00
|
|
|
env.Debug("isWriteable", "no ace found")
|
2022-11-15 03:43:45 -08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
aceSid := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
|
|
|
|
|
2022-11-22 23:01:46 -08:00
|
|
|
env.debugF("isWriteable", func() string { return fmt.Sprintf("ace SID: %s", aceSid.String()) })
|
|
|
|
if !aceSid.Equals(cu) && !aceSid.IsWellKnown(windows.WinWorldSid) {
|
|
|
|
env.Debug("isWriteable", "not current user or world")
|
2022-11-15 03:43:45 -08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-11-21 06:46:59 -08:00
|
|
|
// this gets priority over the other access types
|
|
|
|
if ace.AceType == ACCESS_DENIED_ACE_TYPE {
|
|
|
|
env.Debug("isWriteable", "ACCESS_DENIED_ACE_TYPE")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-11-22 23:01:46 -08:00
|
|
|
env.debugF("isWriteable", func() string { return ace.AccessMask.permissions() })
|
2022-11-21 06:46:59 -08:00
|
|
|
if ace.AccessMask.canWrite() {
|
2022-11-15 03:43:45 -08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2022-11-22 23:01:46 -08:00
|
|
|
env.Debug("isWriteable", "no access control on the folder")
|
2022-11-15 03:43:45 -08:00
|
|
|
return false
|
|
|
|
}
|