Как мне управлять учетными записями пользователей Windows в Go?

Мне нужно иметь возможность управлять учетными записями локальных пользователей Windows из приложения Go, и кажется, что без использования CGo нет собственных привязок.

Мой первоначальный поиск привел меня к людям, которые сказали, что лучше всего использовать команду "exec.Command" для запуска команды "net user", но это кажется грязным и ненадежным, когда дело доходит до парсинга кодов ответов.

Я обнаружил, что функции для обработки этого типа находятся в библиотеке netapi32.dll, но поскольку Go изначально не поддерживает заголовочные файлы Windows, эти функции не так просто вызывать.

Взяв пример из https://github.com/golang/sys/tree/master/windows выясняется, что команда Go переопределяет все в своем коде, а затем вызывает функции DLL.

Мне трудно его обернуть, но у меня есть шаблон низкоуровневого API, к которому я стремлюсь, а затем оборачиваем поверх него высокоуровневый API, как это делает ядро ​​среды выполнения Go.

type LMSTR          ????
type DWORD          ????
type LPBYTE         ????
type LPDWORD        ????
type LPWSTR         ????
type NET_API_STATUS DWORD;

type USER_INFO_1 struct {
    usri1_name              LPWSTR
    usri1_password          LPWSTR
    usri1_password_age      DWORD
    usri1_priv              DWORD
    usri1_home_dir          LPWSTR
    usri1_comment           LPWSTR
    usri1_flags             DWORD
    usri1_script_path       LPWSTR
}

type GROUP_USERS_INFO_0 struct {
    grui0_name              LPWSTR
}

type USER_INFO_1003 struct {
    usri1003_password       LPWSTR
}

const (
    USER_PRIV_GUEST         = ????
    USER_PRIV_USER          = ????
    USER_PRIV_ADMIN         = ????

    UF_SCRIPT               = ????
    UF_ACCOUNTDISABLE       = ????
    UF_HOMEDIR_REQUIRED     = ????
    UF_PASSWD_NOTREQD       = ????
    UF_PASSWD_CANT_CHANGE   = ????
    UF_LOCKOUT              = ????
    UF_DONT_EXPIRE_PASSWD   = ????
    UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = ????
    UF_NOT_DELEGATED        = ????
    UF_SMARTCARD_REQUIRED   = ????
    UF_USE_DES_KEY_ONLY     = ????
    UF_DONT_REQUIRE_PREAUTH = ????
    UF_TRUSTED_FOR_DELEGATION = ????
    UF_PASSWORD_EXPIRED     = ????
    UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = ????

    UF_NORMAL_ACCOUNT       = ????
    UF_TEMP_DUPLICATE_ACCOUNT = ????
    UF_WORKSTATION_TRUST_ACCOUNT = ????
    UF_SERVER_TRUST_ACCOUNT = ????
    UF_INTERDOMAIN_TRUST_ACCOUNT = ????

    NERR_Success            = ????
    NERR_InvalidComputer    = ????
    NERR_NotPrimary         = ????
    NERR_GroupExists        = ????
    NERR_UserExists         = ????
    NERR_PasswordTooShort   = ????
    NERR_UserNotFound       = ????
    NERR_BufTooSmall        = ????
    NERR_InternalError      = ????
    NERR_GroupNotFound      = ????
    NERR_BadPassword        = ????
    NERR_SpeGroupOp         = ????
    NERR_LastAdmin          = ????

    ERROR_ACCESS_DENIED     = ????
    ERROR_INVALID_PASSWORD  = ????
    ERROR_INVALID_LEVEL     = ????
    ERROR_MORE_DATA         = ????
    ERROR_BAD_NETPATH       = ????
    ERROR_INVALID_NAME      = ????
    ERROR_NOT_ENOUGH_MEMORY = ????
    ERROR_INVALID_PARAMETER = ????

    FILTER_TEMP_DUPLICATE_ACCOUNT = ????
    FILTER_NORMAL_ACCOUNT   = ????
    FILTER_INTERDOMAIN_TRUST_ACCOUNT = ????
    FILTER_WORKSTATION_TRUST_ACCOUNT = ????
    FILTER_SERVER_TRUST_ACCOUNT = ????
)

func NetApiBufferFree(Buffer LPVOID) (NET_API_STATUS);

func NetUserAdd(servername LMSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (NET_API_STATUS);

func NetUserChangePassword(domainname LPCWSTR, username LPCWSTR, oldpassword LPCWSTR, newpassword LPCWSTR) (NET_API_STATUS);

func NetUserDel(servername LPCWSTR, username LPCWSTR) (NET_API_STATUS);

func NetUserEnum(servername LPCWSTR, level DWORD, filter DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD, resume_handle LPDWORD) (NET_API_STATUS);

func NetUserGetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD) (NET_API_STATUS);

func NetUserSetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, num_entries DWORD) (NET_API_STATUS);

func NetUserSetInfo(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (NET_API_STATUS);

Каков наилучший способ обернуть это вместе?

1 ответ

Решение

Использование Windows DLL - это (на мой взгляд) лучший способ напрямую использовать Win32 API.

Если вы посмотрите в src/syscall Каталог вашей установки Go, вы можете найти файл с именем mksyscall_windows.go. Это похоже на то, как команда Go управляет всеми своими оболочками DLL.

использование go generate генерировать ваш код

Посмотрите, как syscall_windows.go использует его. В частности это имеет следующее go generate команда:

// go: generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go security_windows.go

Определите типы Win32 API

Затем они определяют свои типы. Вам нужно будет сделать это самостоятельно вручную.

Иногда это сложно, потому что очень важно сохранить размер и выравнивание полей структуры. Я использую Visual Studio Community Edition, чтобы разобраться во множестве основных типов Microsoft, пытаясь определить их эквиваленты Go.

Windows использует UTF16 для строк. Таким образом, вы будете представлять их как *uint16, использование syscall.UTF16PtrFromString генерировать один из строки Go.

Аннотировать Win32 API функции для экспорта

Весь смысл mksyscall_windows.go это сгенерировать весь шаблонный код, чтобы вы получили функцию Go, которая вызывает DLL для вас.

Это достигается путем добавления аннотаций (Перейти комментарии).

Например, в syscall_windows.go у вас есть эти аннотации:

//sys   GetLastError() (lasterr error)
//...
//sys   CreateHardLink(filename *uint16, existingfilename *uint16, reserved uintptr) (err error) [failretval&0xff==0] = CreateHardLinkW

mksyscall_windows.go есть комментарии к документам, чтобы помочь вам понять, как это работает. Вы также можете посмотреть сгенерированный код в zsyscall_windows.go.

Бежать go generate

Это просто, просто запустите:

go generate

Пример:

Для вашего примера создайте файл с именем win32_windows.go:

package win32

//go generate go run mksyscall_windows.go -output zwin32_windows.go win32_windows.go

type (
    LPVOID         uintptr
    LMSTR          *uint16
    DWORD          uint32
    LPBYTE         *byte
    LPDWORD        *uint32
    LPWSTR         *uint16
    NET_API_STATUS DWORD

    USER_INFO_1 struct {
        Usri1_name         LPWSTR
        Usri1_password     LPWSTR
        Usri1_password_age DWORD
        Usri1_priv         DWORD
        Usri1_home_dir     LPWSTR
        Usri1_comment      LPWSTR
        Usri1_flags        DWORD
        Usri1_script_path  LPWSTR
    }

    GROUP_USERS_INFO_0 struct {
        Grui0_name LPWSTR
    }

    USER_INFO_1003 struct {
        Usri1003_password LPWSTR
    }
)

const (
    // from LMaccess.h

    USER_PRIV_GUEST = 0
    USER_PRIV_USER  = 1
    USER_PRIV_ADMIN = 2

    UF_SCRIPT                          = 0x0001
    UF_ACCOUNTDISABLE                  = 0x0002
    UF_HOMEDIR_REQUIRED                = 0x0008
    UF_LOCKOUT                         = 0x0010
    UF_PASSWD_NOTREQD                  = 0x0020
    UF_PASSWD_CANT_CHANGE              = 0x0040
    UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x0080

    UF_TEMP_DUPLICATE_ACCOUNT    = 0x0100
    UF_NORMAL_ACCOUNT            = 0x0200
    UF_INTERDOMAIN_TRUST_ACCOUNT = 0x0800
    UF_WORKSTATION_TRUST_ACCOUNT = 0x1000
    UF_SERVER_TRUST_ACCOUNT      = 0x2000

    UF_ACCOUNT_TYPE_MASK = UF_TEMP_DUPLICATE_ACCOUNT |
        UF_NORMAL_ACCOUNT |
        UF_INTERDOMAIN_TRUST_ACCOUNT |
        UF_WORKSTATION_TRUST_ACCOUNT |
        UF_SERVER_TRUST_ACCOUNT

    UF_DONT_EXPIRE_PASSWD                     = 0x10000
    UF_MNS_LOGON_ACCOUNT                      = 0x20000
    UF_SMARTCARD_REQUIRED                     = 0x40000
    UF_TRUSTED_FOR_DELEGATION                 = 0x80000
    UF_NOT_DELEGATED                          = 0x100000
    UF_USE_DES_KEY_ONLY                       = 0x200000
    UF_DONT_REQUIRE_PREAUTH                   = 0x400000
    UF_PASSWORD_EXPIRED                       = 0x800000
    UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000
    UF_NO_AUTH_DATA_REQUIRED                  = 0x2000000
    UF_PARTIAL_SECRETS_ACCOUNT                = 0x4000000
    UF_USE_AES_KEYS                           = 0x8000000

    UF_SETTABLE_BITS = UF_SCRIPT |
        UF_ACCOUNTDISABLE |
        UF_LOCKOUT |
        UF_HOMEDIR_REQUIRED |
        UF_PASSWD_NOTREQD |
        UF_PASSWD_CANT_CHANGE |
        UF_ACCOUNT_TYPE_MASK |
        UF_DONT_EXPIRE_PASSWD |
        UF_MNS_LOGON_ACCOUNT |
        UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED |
        UF_SMARTCARD_REQUIRED |
        UF_TRUSTED_FOR_DELEGATION |
        UF_NOT_DELEGATED |
        UF_USE_DES_KEY_ONLY |
        UF_DONT_REQUIRE_PREAUTH |
        UF_PASSWORD_EXPIRED |
        UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
        UF_NO_AUTH_DATA_REQUIRED |
        UF_USE_AES_KEYS |
        UF_PARTIAL_SECRETS_ACCOUNT

    FILTER_TEMP_DUPLICATE_ACCOUNT    = (0x0001)
    FILTER_NORMAL_ACCOUNT            = (0x0002)
    FILTER_INTERDOMAIN_TRUST_ACCOUNT = (0x0008)
    FILTER_WORKSTATION_TRUST_ACCOUNT = (0x0010)
    FILTER_SERVER_TRUST_ACCOUNT      = (0x0020)

    LG_INCLUDE_INDIRECT = (0x0001)

    // etc...
)

//sys NetApiBufferFree(Buffer LPVOID) (status NET_API_STATUS) = netapi32.NetApiBufferFree
//sys NetUserAdd(servername LMSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) = netapi32.NetUserAdd
//sys NetUserChangePassword(domainname LPCWSTR, username LPCWSTR, oldpassword LPCWSTR, newpassword LPCWSTR) (status NET_API_STATUS) = netapi32.NetUserChangePassword
//sys NetUserDel(servername LPCWSTR, username LPCWSTR) (status NET_API_STATUS) = netapi32.NetUserDel
//sys NetUserEnum(servername LPCWSTR, level DWORD, filter DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD, resume_handle LPDWORD) (status NET_API_STATUS) = netapi32.NetUserEnum
//sys NetUserGetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD) (status NET_API_STATUS) = netapi32.NetUserGetGroups
//sys NetUserSetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, num_entries DWORD) (status NET_API_STATUS) = netapi32.NetUserSetGroups
//sys NetUserSetInfo(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) = netapi32.NetUserSetInfo

После запуска go generate (пока вы скопировали mksyscall_windows.go в тот же каталог) у вас будет файл с именем "zwin32_windows.go" (что-то вроде этого):

// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT

package win32

import "unsafe"
import "syscall"

var _ unsafe.Pointer

var (
    modnetapi32 = syscall.NewLazyDLL("netapi32.dll")

    procNetApiBufferFree      = modnetapi32.NewProc("NetApiBufferFree")
    procNetUserAdd            = modnetapi32.NewProc("NetUserAdd")
    procNetUserChangePassword = modnetapi32.NewProc("NetUserChangePassword")
    procNetUserDel            = modnetapi32.NewProc("NetUserDel")
    procNetUserEnum           = modnetapi32.NewProc("NetUserEnum")
    procNetUserGetGroups      = modnetapi32.NewProc("NetUserGetGroups")
    procNetUserSetGroups      = modnetapi32.NewProc("NetUserSetGroups")
    procNetUserSetInfo        = modnetapi32.NewProc("NetUserSetInfo")
)

func NetApiBufferFree(Buffer LPVOID) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall(procNetApiBufferFree.Addr(), 1, uintptr(Buffer), 0, 0)
    status = NET_API_STATUS(r0)
    return
}

func NetUserAdd(servername LMSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall6(procNetUserAdd.Addr(), 4, uintptr(servername), uintptr(level), uintptr(buf), uintptr(parm_err), 0, 0)
    status = NET_API_STATUS(r0)
    return
}

func NetUserChangePassword(domainname LPCWSTR, username LPCWSTR, oldpassword LPCWSTR, newpassword LPCWSTR) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall6(procNetUserChangePassword.Addr(), 4, uintptr(domainname), uintptr(username), uintptr(oldpassword), uintptr(newpassword), 0, 0)
    status = NET_API_STATUS(r0)
    return
}

func NetUserDel(servername LPCWSTR, username LPCWSTR) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall(procNetUserDel.Addr(), 2, uintptr(servername), uintptr(username), 0)
    status = NET_API_STATUS(r0)
    return
}

func NetUserEnum(servername LPCWSTR, level DWORD, filter DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD, resume_handle LPDWORD) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall9(procNetUserEnum.Addr(), 8, uintptr(servername), uintptr(level), uintptr(filter), uintptr(unsafe.Pointer(bufptr)), uintptr(prefmaxlen), uintptr(entriesread), uintptr(totalentries), uintptr(resume_handle), 0)
    status = NET_API_STATUS(r0)
    return
}

func NetUserGetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall9(procNetUserGetGroups.Addr(), 7, uintptr(servername), uintptr(username), uintptr(level), uintptr(unsafe.Pointer(bufptr)), uintptr(prefmaxlen), uintptr(entriesread), uintptr(totalentries), 0, 0)
    status = NET_API_STATUS(r0)
    return
}

func NetUserSetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, num_entries DWORD) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall6(procNetUserSetGroups.Addr(), 5, uintptr(servername), uintptr(username), uintptr(level), uintptr(buf), uintptr(num_entries), 0)
    status = NET_API_STATUS(r0)
    return
}

func NetUserSetInfo(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) {
    r0, _, _ := syscall.Syscall6(procNetUserSetInfo.Addr(), 5, uintptr(servername), uintptr(username), uintptr(level), uintptr(buf), uintptr(parm_err), 0)
    status = NET_API_STATUS(r0)
    return
}

Очевидно, большая часть работы заключается в переводе типов Win32 в их эквиваленты Go.

Не стесняйтесь ковыряться в syscall пакет - они часто уже определили структуры, которые могут вас заинтересовать.

Зомг яростно??1! 2 много работы!

Это лучше, чем писать этот код вручную. И никакой CGo не требуется!

Отказ от ответственности: я не проверял приведенный выше код, чтобы убедиться, что он действительно выполняет то, что вы хотите. Работа с Win32 API - это своеобразное удовольствие.

Другие вопросы по тегам