简体   繁体   English

如何在Go中管理Windows用户帐户?

[英]How do I manage Windows User Accounts in Go?

I need to be able to manage Windows Local User Accounts from a Go application and it appears that without using CGo, there are no native bindings. 我需要能够从Go应用程序管理Windows本地用户帐户,并且看起来如果不使用CGo,则没有本机绑定。

My initial search led me to people saying it was best to use "exec.Command" to run the "net user" command, but that seems messy and unreliable when it comes to parsing the response codes. 我最初的搜索让我觉得最好使用“exec.Command”来运行“net user”命令,但在解析响应代码时,这似乎很麻烦且不可靠。

I've found the functions to handle this type of thing are in the netapi32.dll library, but with Go not natively supporting the Windows header files, it doesn't appear easy to call those functions. 我发现处理这类事情的函数都在netapi32.dll库中,但是如果Go本身不支持Windows头文件,那么调用这些函数似乎并不容易。

Taking an example from https://github.com/golang/sys/tree/master/windows it appears the Go team have been redefining everything in their code then calling the DLL functions. https://github.com/golang/sys/tree/master/windows为例,Go团队一直在重新定义代码中的所有内容,然后调用DLL函数。

I'm having a hard time wrapping it together, but I've got this template of the low level API I'm aiming for, then wrapping a higher level API on top of it, much like the core Go runtime does. 我很难将它包装在一起,但是我已经有了这个我想要的低级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);

What is the best way of wrapping this together? 将这些包装在一起的最佳方法是什么?

Using Windows DLLs is (in my opinion) the best way to directly use the Win32 API. 使用Windows DLL(在我看来)是直接使用Win32 API的最佳方式。

If you look in the src/syscall directory of your Go installation, you can find a file called mksyscall_windows.go . 如果查看Go安装的src/syscall目录,可以找到名为mksyscall_windows.go的文件。 This seems to be how the Go team manages all their DLL wrappers. 这似乎是Go团队管理所有DLL包装器的方式。

Use go generate to generate your code 使用go generate生成代码

Take a look at how syscall_windows.go uses it. 看一下syscall_windows.go如何使用它。 Specifically it has the following go generate command: 具体来说,它有以下go generate命令:

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

Define the Win32 API types 定义Win32 API类型

They then define their types. 然后他们定义他们的类型。 You will need to do this yourself manually. 您需要手动执行此操作。

It is a challenge sometimes because it is vital you preserve the size and alignment of the struct fields. 有时候这是一个挑战,因为保持结构域的大小和对齐至关重要。 I use Visual Studio Community Edition to poke around at the plethora of Microsoft's defined basic types in an effort to determine their Go equivalents. 我使用Visual Studio Community Edition来浏览过多的Microsoft定义的基本类型,以确定它们的Go等价物。

Windows uses UTF16 for strings. Windows使用UTF16作为字符串。 So you will be representing these as a *uint16 . 所以你将把它们表示为*uint16 Use syscall.UTF16PtrFromString to generate one from a Go string. 使用syscall.UTF16PtrFromString从Go字符串生成一个。

Annotate Win32 API functions to export 注释要导出的Win32 API函数

The whole point of mksyscall_windows.go is to generate all the boilerplate code so you end up with a Go function that calls the DLL for you. mksyscall_windows.go是生成所有样板代码,以便最终得到一个Go函数,为您调用DLL。

This is accomplished by adding annotations (Go comments). 这是通过添加注释(Go comments)来完成的。

For example, in syscall_windows.go you have these annotations: 例如,在syscall_windows.go您有以下注释:

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

mksyscall_windows.go has doc comments to help you figure out how this works. mksyscall_windows.go有文档评论,可以帮助您弄清楚这是如何工作的。 You can also look at the go-generated code in zsyscall_windows.go . 您还可以在zsyscall_windows.go中查看go生成的代码。

Run go generate 运行go generate

Its easy, just run: 它很简单,只需运行:

go generate

Example: 例:

For your example, create a file called win32_windows.go : 在您的示例中,创建一个名为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

After running go generate (so long as you copied mksyscall_windows.go to the same directory) you will have a file called "zwin32_windows.go" (something like this): 运行后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
}

Obviously most of the work is in translating the Win32 types to their Go equivalents. 显然,大部分工作都是将Win32类型转换为Go等价物。

Feel free to poke around in the syscall package - they often have already defined structs you may be interested in. 随意在syscall包中找到它们 - 它们通常已经定义了您可能感兴趣的结构。

ZOMG sriously??1! ZOMG很吵?? 1! 2 much work! 2多工作!

Its better than writing that code by hand. 它比手工编写代码更好。 And no CGo required! 并且不需要CGo!

Disclamer: I have not tested the above code to verify it actually does what you want. 免责声明:我没有测试上面的代码来验证它实际上做了你想要的。 Working with the Win32 API is its own barrel of fun. 使用Win32 API是它自己的乐趣。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM