[英]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? 将这些包装在一起的最佳方法是什么?
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包装器的方式。
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
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字符串生成一个。
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生成的代码。
go generate
go generate
Its easy, just run: 它很简单,只需运行:
go generate
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
包中找到它们 - 它们通常已经定义了您可能感兴趣的结构。
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.