internal/accounts/syscall_windows.go (179 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package accounts import ( "fmt" "syscall" "unsafe" "golang.org/x/sys/windows" ) var ( netAPI32 = windows.NewLazySystemDLL("netapi32.dll") // User-related syscalls. // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuseradd procNetUserAdd = netAPI32.NewProc("NetUserAdd") // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuserdel procNetUserDel = netAPI32.NewProc("NetUserDel") // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netusergetinfo procNetUserGetInfo = netAPI32.NewProc("NetUserGetInfo") // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netusersetinfo procNetUserSetInfo = netAPI32.NewProc("NetUserSetInfo") // Group-related syscalls. // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netlocalgroupadd procNetLocalGroupAdd = netAPI32.NewProc("NetLocalGroupAdd") // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netlocalgroupdel procNetLocalGroupDel = netAPI32.NewProc("NetLocalGroupDel") // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netlocalgroupaddmembers procNetLocalGroupAddMembers = netAPI32.NewProc("NetLocalGroupAddMembers") // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netlocalgroupdelmembers procNetLocalGroupDelMembers = netAPI32.NewProc("NetLocalGroupDelMembers") // The following is stubbed out for testing. syscallN = syscall.SyscallN ) type ( // DWORD is a uint32 in the Windows syscall API. DWORD uint32 // LPWSTR is a 32-bit pointer to a UTF16 string in the Windows syscall API. LPWSTR *uint16 ) // UserInfo1 is Microsoft's representation of a user for syscalls. // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-user_info_1 type UserInfo1 struct { Name LPWSTR Password LPWSTR PasswordAge DWORD Priv DWORD HomeDir LPWSTR Comment LPWSTR Flags DWORD ScriptPath LPWSTR } // UserInfo1003 is Microsoft's representation of a user for setting passwords. // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-user_info_1003 type UserInfo1003 struct { Password LPWSTR } // LocalGroupMembersInfo0 is Microsoft's representation of a group member for // syscalls. // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-local_group_members_0 type LocalGroupMembersInfo0 struct { SID *syscall.SID } // LocalGroupInfo0 is Microsoft's representation of a group for syscalls. // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-localgroup_info_0 type LocalGroupInfo0 struct { Name LPWSTR } // LocalGroupInfo1 is Microsoft's representation of a group for syscalls. // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-localgroup_info_1 type LocalGroupInfo1 struct { Name LPWSTR Comment LPWSTR } const ( // Levels of privilege. userPrivUser = 1 // User account flags. ufScript = 0x0001 ufNormalAccount = 0x0200 ufDontExpirePassword = 0x10000 ) // defaultNetUserAdd adds a user to the local machine, assigns a password, and // sets its privileges. func defaultNetUserAdd(username, password string) error { uPtr, err := syscall.UTF16PtrFromString(username) if err != nil { return fmt.Errorf("error encoding username to UTF16: %w", err) } pPtr, err := syscall.UTF16PtrFromString(password) if err != nil { return fmt.Errorf("error encoding password to UTF16: %w", err) } uInfo1 := UserInfo1{ Name: uPtr, Password: pPtr, Priv: userPrivUser, Flags: ufScript | ufNormalAccount | ufDontExpirePassword, } ret, _, _ := syscallN(procNetUserAdd.Addr(), 0, 1, uintptr(unsafe.Pointer(&uInfo1)), 0) // Error 2236 is returned when the user already belongs to the group. No // action is required; see: // https://learn.microsoft.com/en-us/troubleshoot/windows-server/remote/terminal-server-error-messages-2200-to-2299#error-2236 if ret != 0 && ret != 2236 { return fmt.Errorf("nonzero return code(%v) from NetUserAdd: %w", ret, syscall.Errno(ret)) } return nil } // defaultNetUserDel deletes a user from the local machine. func defaultNetUserDel(username string) error { uPtr, err := syscall.UTF16PtrFromString(username) if err != nil { return fmt.Errorf("error encoding username to UTF16: %w", err) } ret, _, _ := syscallN(procNetUserDel.Addr(), 0, uintptr(unsafe.Pointer(uPtr))) if ret != 0 { return fmt.Errorf("nonzero return code(%v) from NetUserDel: %w", ret, syscall.Errno(ret)) } return nil } // defaultNetUserGetInfo gets a user's information. func defaultNetUserGetInfo(username string) (*UserInfo1, error) { uPtr, err := syscall.UTF16PtrFromString(username) if err != nil { return &UserInfo1{}, fmt.Errorf("error encoding username to UTF16: %w", err) } uInfo := &UserInfo1{} ret, _, _ := syscallN(procNetUserGetInfo.Addr(), 0, uintptr(unsafe.Pointer(uPtr)), 1, uintptr(unsafe.Pointer(uInfo))) if ret != 0 { return &UserInfo1{}, fmt.Errorf("nonzero return code(%v) from NetUserGetInfo: %v", ret, syscall.Errno(ret)) } return uInfo, nil } // defaultNetUserSetInfo sets a user's password. func defaultNetUserSetPassword(username, password string) error { uPtr, err := syscall.UTF16PtrFromString(username) if err != nil { return fmt.Errorf("error encoding username to UTF16: %w", err) } pPtr, err := syscall.UTF16PtrFromString(password) if err != nil { return fmt.Errorf("error encoding password to UTF16: %w", err) } ret, _, _ := syscallN( procNetUserSetInfo.Addr(), uintptr(0), uintptr(unsafe.Pointer(uPtr)), uintptr(1003), uintptr(unsafe.Pointer(&UserInfo1003{pPtr})), uintptr(0), ) if ret != 0 { return fmt.Errorf("nonzero return code(%v) from NetUserSetInfo: %w", ret, syscall.Errno(ret)) } return nil } // defaultNetLocalGroupAdd creates a new local group. func defaultNetLocalGroupAdd(group string) error { gPtr, err := syscall.UTF16PtrFromString(group) if err != nil { return fmt.Errorf("error encoding username to UTF16: %v", err) } groupInfo := &LocalGroupInfo0{gPtr} ret, _, _ := syscallN(procNetLocalGroupAdd.Addr(), 0, 0, uintptr(unsafe.Pointer(groupInfo)), 0) // Ignore 2223 (group already exists). // https://learn.microsoft.com/en-us/troubleshoot/windows-server/remote/terminal-server-error-messages-2200-to-2299#error-2223 if ret != 0 && ret != 2223 { return fmt.Errorf("nonzero return code(%v) from NetGroupAdd: %w", ret, syscall.Errno(ret)) } return nil } // defaultNetLocalGroupDel deletes a local group. func defaultNetLocalGroupDel(group string) error { gPtr, err := syscall.UTF16PtrFromString(group) if err != nil { return fmt.Errorf("error encoding username to UTF16: %v", err) } ret, _, _ := syscallN(procNetLocalGroupDel.Addr(), 0, uintptr(unsafe.Pointer(gPtr))) if ret != 0 { return fmt.Errorf("nonzero return code(%v) from NetLocalGroupDel: %w", ret, syscall.Errno(ret)) } return nil } // defaultNetLocalGroupAddMembers adds a user to the provided local group. func defaultNetLocalGroupAddMembers(SID *syscall.SID, group string) error { gPtr, err := syscall.UTF16PtrFromString(group) if err != nil { return fmt.Errorf("error encoding username to UTF16: %w", err) } sArray := []LocalGroupMembersInfo0{{SID}} ret, _, _ := syscallN( procNetLocalGroupAddMembers.Addr(), 0, uintptr(unsafe.Pointer(gPtr)), 0, uintptr(unsafe.Pointer(&sArray[0])), 1, ) // Ignore ERROR_MEMBER_IN_ALIAS (1378). if ret != 0 && ret != 1378 { return fmt.Errorf("nonzero return code(%v) from NetLocalGroupAddMembers: %w", ret, syscall.Errno(ret)) } return nil } // defaultNetLocalGroupDelMembers removes a user from the provided local group. func defaultNetLocalGroupDelMembers(SID *syscall.SID, group string) error { gPtr, err := syscall.UTF16PtrFromString(group) if err != nil { return fmt.Errorf("error encoding username to UTF16: %w", err) } sArray := []LocalGroupMembersInfo0{{SID}} ret, _, _ := syscallN( procNetLocalGroupDelMembers.Addr(), 0, uintptr(unsafe.Pointer(gPtr)), 0, uintptr(unsafe.Pointer(&sArray[0])), 1, ) // Ignore ERROR_MEMBER_IN_ALIAS (1378). if ret != 0 && ret != 1378 { return fmt.Errorf("nonzero return code(%v) from NetLocalGroupDelMembers: %w", ret, syscall.Errno(ret)) } return nil }