dongluo1853
2016-02-02 12:52
浏览 141
已采纳

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

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条回答 默认 最新

  • douyi1084 2016-02-28 04:07
    已采纳

    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.

    点赞 评论

相关推荐 更多相似问题