为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
Smart猫小萌 2021-12-07 17:20:12
我需要能够从 Go 应用程序管理 Windows 本地用户帐户,而且似乎不使用 CGo,就没有本机绑定。我最初的搜索使我发现人们说最好使用“exec.Command”来运行“net user”命令,但是在解析响应代码时这似乎很混乱且不可靠。我发现处理这种类型的函数在 netapi32.dll 库中,但是由于 Go 本身不支持 Windows 头文件,所以调用这些函数似乎并不容易。以https://github.com/golang/sys/tree/master/windows为例,Go 团队似乎一直在重新定义其代码中的所有内容,然后调用 DLL 函数。我很难将它包装在一起,但我已经得到了我想要的低级 API 的模板,然后在它上面包装了一个更高级别的 API,就像核心 Go 运行时所做的那样。将它包装在一起的最佳方式是什么?
查看完整描述

1 回答

?
喵喔喔

TA贡献1735条经验 获得超5个赞

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

如果您查看src/syscallGo 安装目录,您可以找到一个名为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 社区版来浏览 Microsoft 定义的大量基本类型,以确定它们的 Go 等价物。


Windows 对字符串使用 UTF16。因此,您会将这些表示为*uint16. 用于syscall.UTF16PtrFromString从 Go 字符串生成一个。


注释要导出的 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 生成的代码。


跑 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包中闲逛- 他们通常已经定义了您可能感兴趣的结构。


ZOMG 认真的??1!2 辛苦了!

它比手工编写代码要好。并且不需要 CGo!


免责声明:我还没有测试上面的代码来验证它确实可以满足您的需求。使用 Win32 API 本身就是一种乐趣。


查看完整回答
反对 回复 2021-12-07
  • 1 回答
  • 0 关注
  • 315 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信