@@ -0,0 +1,16 @@
|
||||
//go:build !windows
|
||||
|
||||
package windowsx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/example/sessionguard/internal/model"
|
||||
)
|
||||
|
||||
var ErrUnsupported = errors.New("Windows functionality is only available on Windows")
|
||||
|
||||
func Sessions() ([]model.Session, error) { return nil, ErrUnsupported }
|
||||
func Server() (model.ServerInfo, error) { return model.ServerInfo{}, ErrUnsupported }
|
||||
func ProfilePath(string) (string, error) { return "", ErrUnsupported }
|
||||
func DeleteProfile(string) error { return ErrUnsupported }
|
||||
func MachineID() (string, error) { return "nonwindows", nil }
|
||||
@@ -0,0 +1,191 @@
|
||||
//go:build windows
|
||||
|
||||
package windowsx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/example/sessionguard/internal/model"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
wtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll")
|
||||
procWTSEnumerateSessionsW = wtsapi32.NewProc("WTSEnumerateSessionsW")
|
||||
procWTSFreeMemory = wtsapi32.NewProc("WTSFreeMemory")
|
||||
procWTSQuerySessionInformationW = wtsapi32.NewProc("WTSQuerySessionInformationW")
|
||||
procWTSQueryUserToken = wtsapi32.NewProc("WTSQueryUserToken")
|
||||
userenv = windows.NewLazySystemDLL("userenv.dll")
|
||||
procDeleteProfileW = userenv.NewProc("DeleteProfileW")
|
||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procGetTickCount64 = kernel32.NewProc("GetTickCount64")
|
||||
procGlobalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx")
|
||||
procExpandEnvironmentStringsW = kernel32.NewProc("ExpandEnvironmentStringsW")
|
||||
)
|
||||
|
||||
type wtsSessionInfo struct {
|
||||
SessionID uint32
|
||||
WinStationName *uint16
|
||||
State uint32
|
||||
}
|
||||
|
||||
const (
|
||||
wtsUserName = 5
|
||||
wtsWinStationName = 6
|
||||
wtsDomainName = 7
|
||||
wtsClientName = 10
|
||||
)
|
||||
|
||||
var stateNames = map[uint32]string{
|
||||
0: "Active", 1: "Connected", 2: "ConnectQuery", 3: "Shadow", 4: "Disconnected",
|
||||
5: "Idle", 6: "Listen", 7: "Reset", 8: "Down", 9: "Init",
|
||||
}
|
||||
|
||||
func Sessions() ([]model.Session, error) {
|
||||
var buf uintptr
|
||||
var count uint32
|
||||
r1, _, e := procWTSEnumerateSessionsW.Call(0, 0, 1, uintptr(unsafe.Pointer(&buf)), uintptr(unsafe.Pointer(&count)))
|
||||
if r1 == 0 {
|
||||
return nil, fmt.Errorf("WTSEnumerateSessionsW: %w", e)
|
||||
}
|
||||
defer procWTSFreeMemory.Call(buf)
|
||||
rows := unsafe.Slice((*wtsSessionInfo)(unsafe.Pointer(buf)), int(count))
|
||||
out := make([]model.Session, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
s := model.Session{ID: row.SessionID, State: stateNames[row.State]}
|
||||
if s.State == "" {
|
||||
s.State = fmt.Sprintf("State%d", row.State)
|
||||
}
|
||||
if row.WinStationName != nil {
|
||||
s.StationName = windows.UTF16PtrToString(row.WinStationName)
|
||||
}
|
||||
s.User, _ = queryString(row.SessionID, wtsUserName)
|
||||
s.Domain, _ = queryString(row.SessionID, wtsDomainName)
|
||||
s.ClientName, _ = queryString(row.SessionID, wtsClientName)
|
||||
if s.StationName == "" {
|
||||
s.StationName, _ = queryString(row.SessionID, wtsWinStationName)
|
||||
}
|
||||
if s.User != "" {
|
||||
var token windows.Token
|
||||
r, _, _ := procWTSQueryUserToken.Call(uintptr(row.SessionID), uintptr(unsafe.Pointer(&token)))
|
||||
if r != 0 {
|
||||
if tu, err := token.GetTokenUser(); err == nil && tu.User.Sid != nil {
|
||||
s.SID = tu.User.Sid.String()
|
||||
}
|
||||
_ = token.Close()
|
||||
}
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func queryString(sessionID uint32, class uintptr) (string, error) {
|
||||
var p uintptr
|
||||
var bytes uint32
|
||||
r1, _, e := procWTSQuerySessionInformationW.Call(0, uintptr(sessionID), class, uintptr(unsafe.Pointer(&p)), uintptr(unsafe.Pointer(&bytes)))
|
||||
if r1 == 0 {
|
||||
return "", e
|
||||
}
|
||||
defer procWTSFreeMemory.Call(p)
|
||||
if p == 0 || bytes < 2 {
|
||||
return "", nil
|
||||
}
|
||||
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(p))), nil
|
||||
}
|
||||
|
||||
type memoryStatusEx struct {
|
||||
Length uint32
|
||||
MemoryLoad uint32
|
||||
TotalPhys uint64
|
||||
AvailPhys uint64
|
||||
TotalPageFile uint64
|
||||
AvailPageFile uint64
|
||||
TotalVirtual uint64
|
||||
AvailVirtual uint64
|
||||
AvailExtendedVirtual uint64
|
||||
}
|
||||
|
||||
func Server() (model.ServerInfo, error) {
|
||||
host, _ := os.Hostname()
|
||||
m := memoryStatusEx{Length: uint32(unsafe.Sizeof(memoryStatusEx{}))}
|
||||
r, _, e := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&m)))
|
||||
if r == 0 {
|
||||
return model.ServerInfo{}, fmt.Errorf("GlobalMemoryStatusEx: %w", e)
|
||||
}
|
||||
ticks, _, _ := procGetTickCount64.Call()
|
||||
info := model.ServerInfo{Hostname: host, OS: "Windows", UptimeSeconds: uint64(ticks) / 1000, MemoryTotal: m.TotalPhys, MemoryAvailable: m.AvailPhys}
|
||||
if k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE); err == nil {
|
||||
defer k.Close()
|
||||
if v, _, err := k.GetStringValue("ProductName"); err == nil {
|
||||
info.OS = v
|
||||
}
|
||||
if v, _, err := k.GetStringValue("DisplayVersion"); err == nil {
|
||||
info.Version = v
|
||||
}
|
||||
if v, _, err := k.GetStringValue("CurrentBuildNumber"); err == nil {
|
||||
info.Build = v
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func ProfilePath(sid string) (string, error) {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+sid, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer k.Close()
|
||||
p, _, err := k.GetStringValue("ProfileImagePath")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return expandEnv(p), nil
|
||||
}
|
||||
|
||||
func expandEnv(s string) string {
|
||||
in, err := windows.UTF16PtrFromString(s)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
n, _, _ := procExpandEnvironmentStringsW.Call(uintptr(unsafe.Pointer(in)), 0, 0)
|
||||
if n == 0 {
|
||||
return s
|
||||
}
|
||||
buf := make([]uint16, n)
|
||||
n2, _, _ := procExpandEnvironmentStringsW.Call(uintptr(unsafe.Pointer(in)), uintptr(unsafe.Pointer(&buf[0])), uintptr(n))
|
||||
if n2 == 0 || n2 > n {
|
||||
return s
|
||||
}
|
||||
return windows.UTF16ToString(buf)
|
||||
}
|
||||
|
||||
func DeleteProfile(sid string) error {
|
||||
p, err := windows.UTF16PtrFromString(sid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, _, e := procDeleteProfileW.Call(uintptr(unsafe.Pointer(p)), 0, 0)
|
||||
if r == 0 {
|
||||
if e == syscall.Errno(0) {
|
||||
return fmt.Errorf("DeleteProfileW failed")
|
||||
}
|
||||
return fmt.Errorf("DeleteProfileW(%s): %w", sid, e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MachineID() (string, error) {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Cryptography`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer k.Close()
|
||||
v, _, err := k.GetStringValue("MachineGuid")
|
||||
return strings.TrimSpace(v), err
|
||||
}
|
||||
Reference in New Issue
Block a user