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

huangapple go评论94阅读模式
英文:

How do I manage Windows User Accounts in Go?

问题

我需要能够从Go应用程序中管理Windows本地用户账户,但似乎在不使用CGo的情况下,没有本地绑定。

我的初步搜索结果显示,最好使用"exec.Command"来运行"net user"命令,但是在解析响应代码时,这种方法似乎很混乱且不可靠。

我发现处理这种情况的函数位于netapi32.dll库中,但由于Go不原生支持Windows头文件,调用这些函数似乎并不容易。

从https://github.com/golang/sys/tree/master/windows的示例中可以看出,Go团队一直在重新定义所有内容,然后调用DLL函数。

我很难将它们整合在一起,但我有一个低级API的模板,然后在其上封装一个更高级的API,就像核心Go运行时所做的那样。

以下是我所追求的低级API的模板,然后在其上封装一个更高级的API:

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);

如何最好地将这些内容整合在一起?

英文:

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.

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.

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.

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.

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.

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?

答案1

得分: 7

使用Windows DLL是直接使用Win32 API的最佳方式(在我看来)。

如果你查看Go安装目录下的src/syscall目录,你会找到一个名为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来查看微软定义的各种基本类型,以确定它们在Go中的等效类型。

Windows使用UTF16编码的字符串。所以你将把它们表示为*uint16。使用syscall.UTF16PtrFromString从Go字符串生成一个UTF16字符串。

注释Win32 API函数以导出

mksyscall_windows.go的整个目的是生成所有样板代码,以便你最终得到一个调用DLL的Go函数。

这是通过添加注释(Go注释)来实现的。

例如,在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包中的内容-它们通常已经定义了你可能感兴趣的结构体。

天哪,真的吗?太多工作了!

这比手动编写代码要好。而且不需要CGo!

**免责声明:**我没有测试上述代码以验证它是否真正实现了你想要的功能。与Win32 API一起工作是一种有趣的体验。

英文:

Using Windows DLLs is (in my opinion) the best way to directly use the Win32 API.

If you look in the src/syscall directory of your Go installation, you can find a file called mksyscall_windows.go. This seems to be how the Go team manages all their DLL wrappers.

Use go generate to generate your code

Take a look at how syscall_windows.go uses it. Specifically it has the following go generate command:

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

Define the Win32 API types

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.

Windows uses UTF16 for strings. So you will be representing these as a *uint16. Use syscall.UTF16PtrFromString to generate one from a Go string.

Annotate Win32 API functions to export

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.

This is accomplished by adding annotations (Go comments).

For example, in syscall_windows.go you have these annotations:

//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. You can also look at the go-generated code in zsyscall_windows.go.

Run go generate

Its easy, just run:

go generate

Example:

For your example, create a file called 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):

// 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.

Feel free to poke around in the syscall package - they often have already defined structs you may be interested in.

ZOMG sriously??1! 2 much work!

Its better than writing that code by hand. And no CGo required!

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.

huangapple
  • 本文由 发表于 2016年2月2日 20:52:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/35154074.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定