import (
"crypto/rand"
"encoding/base64"
+ "encoding/binary"
"errors"
"fmt"
"internal/syscall/windows"
"os/exec"
"runtime"
"strconv"
+ "strings"
"syscall"
"testing"
+ "unicode"
+ "unicode/utf8"
"unsafe"
)
+// addUserAccount creates a local user account.
+// It returns the name and password of the new account.
+// Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory.
+func addUserAccount(t *testing.T) (name, password string) {
+ t.TempDir()
+ pattern := t.Name()
+ // Windows limits the user name to 20 characters,
+ // leave space for a 4 digits random suffix.
+ const maxNameLen, suffixLen = 20, 4
+ pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)]
+ // Drop unusual characters from the account name.
+ mapper := func(r rune) rune {
+ if r < utf8.RuneSelf {
+ if '0' <= r && r <= '9' ||
+ 'a' <= r && r <= 'z' ||
+ 'A' <= r && r <= 'Z' {
+ return r
+ }
+ } else if unicode.IsLetter(r) || unicode.IsNumber(r) {
+ return r
+ }
+ return -1
+ }
+ pattern = strings.Map(mapper, pattern)
+
+ // Generate a long random password.
+ var pwd [33]byte
+ rand.Read(pwd[:])
+ // Add special chars to ensure it satisfies password requirements.
+ password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2"
+ password16, err := syscall.UTF16PtrFromString(password)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ try := 0
+ for {
+ // Calculate a random suffix to append to the user name.
+ var suffix [2]byte
+ rand.Read(suffix[:])
+ suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10)
+ name := pattern + suffixStr[:min(len(suffixStr), suffixLen)]
+ name16, err := syscall.UTF16PtrFromString(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Create user.
+ userInfo := windows.UserInfo1{
+ Name: name16,
+ Password: password16,
+ Priv: windows.USER_PRIV_USER,
+ }
+ err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
+ if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
+ t.Skip("skipping test; don't have permission to create user")
+ }
+ // If the user already exists, try again with a different name.
+ if errors.Is(err, windows.NERR_UserExists) {
+ if try++; try < 1000 {
+ t.Log("user already exists, trying again with a different name")
+ continue
+ }
+ }
+ if err != nil {
+ t.Fatalf("NetUserAdd failed: %v", err)
+ }
+ // Delete the user when the test is done.
+ t.Cleanup(func() {
+ if err := windows.NetUserDel(nil, name16); err != nil {
+ if !errors.Is(err, windows.NERR_UserNotFound) {
+ t.Fatal(err)
+ }
+ }
+ })
+ return name, password
+ }
+}
+
// windowsTestAccount creates a test user and returns a token for that user.
// If the user already exists, it will be deleted and recreated.
// The caller is responsible for closing the token.
// See https://dev.go/issue/70396.
t.Skip("skipping non-hermetic test outside of Go builders")
}
- const testUserName = "GoStdTestUser01"
- var password [33]byte
- rand.Read(password[:])
- // Add special chars to ensure it satisfies password requirements.
- pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2"
- name, err := syscall.UTF16PtrFromString(testUserName)
+ name, password := addUserAccount(t)
+ name16, err := syscall.UTF16PtrFromString(name)
if err != nil {
t.Fatal(err)
}
- pwd16, err := syscall.UTF16PtrFromString(pwd)
+ pwd16, err := syscall.UTF16PtrFromString(password)
if err != nil {
t.Fatal(err)
}
- userInfo := windows.UserInfo1{
- Name: name,
- Password: pwd16,
- Priv: windows.USER_PRIV_USER,
- }
- // Create user.
- err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
- if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
- t.Skip("skipping test; don't have permission to create user")
- }
- if errors.Is(err, windows.NERR_UserExists) {
- // User already exists, delete and recreate.
- if err = windows.NetUserDel(nil, name); err != nil {
- t.Fatal(err)
- }
- if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil {
- t.Fatal(err)
- }
- } else if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err = windows.NetUserDel(nil, name); err != nil {
- if !errors.Is(err, windows.NERR_UserNotFound) {
- t.Fatal(err)
- }
- }
- })
domain, err := syscall.UTF16PtrFromString(".")
if err != nil {
t.Fatal(err)
const LOGON32_PROVIDER_DEFAULT = 0
const LOGON32_LOGON_INTERACTIVE = 2
var token syscall.Token
- if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
+ if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
token.Close()
})
- usr, err := Lookup(testUserName)
+ usr, err := Lookup(name)
if err != nil {
t.Fatal(err)
}