Add and generate custom system calls in golang - windows

I want to add the system call GetDiskFreeSpaceExW to the go systemcalls and extend the currently available syscalls for hcsshim: https://github.com/microsoft/hcsshim/blob/master/zsyscall_windows.go
I saw that there is a way of generating them, but it is still unclear how the syntax will be for adding another DLL and what struct types have to be used.
The current hcs.go which is used to generate the system calls for hcsshim:
// Shim for the Host Compute Service (HCS) to manage Windows Server
// containers and Hyper-V containers.
package hcs
import (
"syscall"
)
//go:generate go run ../../mksyscall_windows.go -output zsyscall_windows.go hcs.go
//sys hcsEnumerateComputeSystems(query string, computeSystems **uint16, result **uint16) (hr error) = vmcompute.HcsEnumerateComputeSystems?
//sys hcsCreateComputeSystem(id string, configuration string, identity syscall.Handle, computeSystem *hcsSystem, result **uint16) (hr error) = vmcompute.HcsCreateComputeSystem?
//sys hcsOpenComputeSystem(id string, computeSystem *hcsSystem, result **uint16) (hr error) = vmcompute.HcsOpenComputeSystem?
//sys hcsCloseComputeSystem(computeSystem hcsSystem) (hr error) = vmcompute.HcsCloseComputeSystem?
//sys hcsStartComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsStartComputeSystem?
//sys hcsShutdownComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsShutdownComputeSystem?
//sys hcsTerminateComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsTerminateComputeSystem?
//sys hcsPauseComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsPauseComputeSystem?
//sys hcsResumeComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsResumeComputeSystem?
//sys hcsGetComputeSystemProperties(computeSystem hcsSystem, propertyQuery string, properties **uint16, result **uint16) (hr error) = vmcompute.HcsGetComputeSystemProperties?
//sys hcsModifyComputeSystem(computeSystem hcsSystem, configuration string, result **uint16) (hr error) = vmcompute.HcsModifyComputeSystem?
//sys hcsRegisterComputeSystemCallback(computeSystem hcsSystem, callback uintptr, context uintptr, callbackHandle *hcsCallback) (hr error) = vmcompute.HcsRegisterComputeSystemCallback?
//sys hcsUnregisterComputeSystemCallback(callbackHandle hcsCallback) (hr error) = vmcompute.HcsUnregisterComputeSystemCallback?
//sys hcsCreateProcess(computeSystem hcsSystem, processParameters string, processInformation *hcsProcessInformation, process *hcsProcess, result **uint16) (hr error) = vmcompute.HcsCreateProcess?
//sys hcsOpenProcess(computeSystem hcsSystem, pid uint32, process *hcsProcess, result **uint16) (hr error) = vmcompute.HcsOpenProcess?
//sys hcsCloseProcess(process hcsProcess) (hr error) = vmcompute.HcsCloseProcess?
//sys hcsTerminateProcess(process hcsProcess, result **uint16) (hr error) = vmcompute.HcsTerminateProcess?
//sys hcsSignalProcess(process hcsProcess, options string, result **uint16) (hr error) = vmcompute.HcsSignalProcess?
//sys hcsGetProcessInfo(process hcsProcess, processInformation *hcsProcessInformation, result **uint16) (hr error) = vmcompute.HcsGetProcessInfo?
//sys hcsGetProcessProperties(process hcsProcess, processProperties **uint16, result **uint16) (hr error) = vmcompute.HcsGetProcessProperties?
//sys hcsModifyProcess(process hcsProcess, settings string, result **uint16) (hr error) = vmcompute.HcsModifyProcess?
//sys hcsGetServiceProperties(propertyQuery string, properties **uint16, result **uint16) (hr error) = vmcompute.HcsGetServiceProperties?
//sys hcsRegisterProcessCallback(process hcsProcess, callback uintptr, context uintptr, callbackHandle *hcsCallback) (hr error) = vmcompute.HcsRegisterProcessCallback?
//sys hcsUnregisterProcessCallback(callbackHandle hcsCallback) (hr error) = vmcompute.HcsUnregisterProcessCallback?
//sys hcsGetDiskFreeSpaceExW(string lpDirectoryName, **uint16 lpFreeBytesAvailableToCaller, **uint16 lpTotalNumberOfBytes, **uint16 lpTotalNumberOfFreeBytes) =
type hcsSystem syscall.Handle
type hcsProcess syscall.Handle
type hcsCallback syscall.Handle
type hcsProcessInformation struct {
ProcessId uint32
Reserved uint32
StdInput syscall.Handle
StdOutput syscall.Handle
StdError syscall.Handle
}
As result I want to have an working syscall to obtain disk usage and disk space.

Create a file like this:
//go:generate mkwinsyscall -output zfree.go free.go
//sys getDiskFreeSpace(directoryName string, availableToCaller *int, numberOfBytes *int, numberOfFreeBytes *int) (err error) = GetDiskFreeSpaceExW
package main
func main() {
var availableToCaller, numberOfBytes, numberOfFreeBytes int
getDiskFreeSpace(
`C:\`, &availableToCaller, &numberOfBytes, &numberOfFreeBytes,
)
println("availableToCaller", availableToCaller)
println("numberOfBytes", numberOfBytes)
println("numberOfFreeBytes", numberOfFreeBytes)
}
Then build [1]:
go mod init free
go generate
go mod tidy
go build
Alternatively, you can use the premade wrapper [2], but I prefer mine, as you don't have to worry about the string conversion.
https://github.com/golang/sys/tree/master/windows/mkwinsyscall
https://pkg.go.dev/golang.org/x/sys/windows#GetDiskFreeSpaceEx

Related

How to support Enum value in Gorm

Good day guys, I am new to go and Gorm, I am building a simple web service using Postgres, I had an issue getting my model to support enum values, after some research I found a way to to do it. But I began to get new errors which I can't seem to fix after researching resources online.
whenever I try to fetch a user from the database I get this error:
panic: interface conversion: interface {} is string, not []uint8
-> userservice/models.(*RoleAllowed).Scan
-> /Users/Cipher/work/frontendlabs/shopvending/userservice/models/user.model.go:22
database/sql.convertAssignRows
/usr/local/go/src/database/sql/convert.go:385
database/sql.convertAssignRows
/usr/local/go/src/database/sql/convert.go:428
database/sql.(*Rows).Scan
/usr/local/go/src/database/sql/sql.go:3287
gorm.io/gorm.(*DB).scanIntoStruct
/Users/Cipher/go/pkg/mod/gorm.io/gorm#v1.23.8/scan.go:67
gorm.io/gorm.Scan
/Users/Cipher/go/pkg/mod/gorm.io/gorm#v1.23.8/scan.go:293
gorm.io/gorm/callbacks.Query
/Users/Cipher/go/pkg/mod/gorm.io/gorm#v1.23.8/callbacks/query.go:26
gorm.io/gorm.(*processor).Execute
/Users/Cipher/go/pkg/mod/gorm.io/gorm#v1.23.8/callbacks.go:130
gorm.io/gorm.(*DB).First
/Users/Cipher/go/pkg/mod/gorm.io/gorm#v1.23.8/finisher_api.go:129
userservice/services.UserService.FindUserByEmail
/Users/Cipher/work/frontendlabs/shopvending/userservice/services/user.service.go:25
userservice/handlers.ManualLogin
/Users/Cipher/work/frontendlabs/shopvending/userservice/handlers/auth.handler.go:24
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5.(*Mux).routeHTTP
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/mux.go:442
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5.(*Mux).ServeHTTP
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/mux.go:71
github.com/go-chi/chi/v5.(*Mux).Mount.func1
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/mux.go:314
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5.(*Mux).routeHTTP
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/mux.go:442
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5/middleware.Timeout.func1.1
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/middleware/timeout.go:45
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
userservice/middlewares.CommonMiddleware.func1
/Users/Cipher/work/frontendlabs/shopvending/userservice/middlewares/contentType.go:8
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5/middleware.Recoverer.func1
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/middleware/recoverer.go:38
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5/middleware.RequestLogger.func1.1
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/middleware/logger.go:57
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5/middleware.RealIP.func1
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/middleware/realip.go:35
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5/middleware.RequestID.func1
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/middleware/request_id.go:76
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2109
github.com/go-chi/chi/v5.(*Mux).ServeHTTP
/Users/Cipher/go/pkg/mod/github.com/go-chi/chi/v5#v5.0.7/mux.go:88
net/http.serverHandler.ServeHTTP
/usr/local/go/src/net/http/server.go:2947
net/http.(*conn).serve
/usr/local/go/src/net/http/server.go:1991
created by net/http.(*Server).Serve
/usr/local/go/src/net/http/server.go:3102
I have tried to convert the byte to string, I still get the error.
my user service file:
package services
import (
"github.com/sirupsen/logrus"
"userservice/database"
"userservice/dto"
"userservice/models"
)
type UserService struct{}
//func (u UserService) FindUserById(id int) *models.User {
// user := u.User
// if err := database.Instance.Where("id = ?", id).First(&user); err != nil {
// logrus.Errorf("Can not find user with id %d", id)
// return nil
//
// }
// return user
//
//}
func (u UserService) FindUserByEmail(email string) *models.User {
var user models.User
if err := database.Instance.Where("email = ?", email).First(&user); err.Error != nil {
logrus.Errorf("Can not find user with email %s", err.Error)
return nil
}
return &user
}
func (u UserService) CreateUser(newUser dto.CreateNewUserDTO) *models.User {
createNewUser := models.User{
Email: newUser.Email,
Password: newUser.Password,
}
createNewUser.HashPassword(createNewUser.Password)
err := database.Instance.Create(&createNewUser)
if err.Error != nil {
logrus.Errorf("Error saving food to the database: %s", err.Error)
return nil
}
return &createNewUser
}
the issue is when I call "FindUserByEmail" for some reason it can't serialise.
it was working, until I introduced the enum value integration. now I get the error above.
my user model file:
package models
import (
"database/sql/driver"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"time"
)
type RoleAllowed string
const (
admin RoleAllowed = "admin"
seller RoleAllowed = "seller"
rider RoleAllowed = "rider"
regular RoleAllowed = "regular"
)
func (st *RoleAllowed) Scan(value interface{}) error {
*st = RoleAllowed(value.([]byte))
return nil
}
func (st RoleAllowed) Value() (driver.Value, error) {
return string(st), nil
}
type StatusAllowed string
const (
suspended StatusAllowed = "suspended"
active StatusAllowed = "active"
inactive StatusAllowed = "inactive"
)
func (st *StatusAllowed) Scan(value interface{}) error {
*st = StatusAllowed(value.([]byte))
return nil
}
func (st StatusAllowed) Value() (driver.Value, error) {
return string(st), nil
}
type User struct {
ID uint `json:"id" gorm:"type:bigserial;primaryKey;autoIncrement"`
Email string `json:"email" gorm:"type:varchar(255);unique;not null"`
Password string `json:"password" gorm:"type:varchar(255);unique;not null""`
OTPCode int `json:"otp_code"`
Role RoleAllowed `json:"role" gorm:"type:role_allowed;default:'regular'"`
Status StatusAllowed `json:"status" gorm:"type:status_allowed;default:'active'"`
IsEmailVerified bool `json:"isEmailVerified" gorm:"default:false"`
OTPExpireTime time.Time `json:"otp_expire_time"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func (u *User) HashPassword(password string) error {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
if err != nil {
return err
}
u.Password = string(bytes)
return nil
}
func (u *User) CheckPasswordHash(password string) error {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
if err != nil {
return err
}
return nil
}
please any assistance on how to resolve this issue?
I edited my code below
package models
import (
"database/sql/driver"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"time"
)
type RoleAllowed string
const (
admin RoleAllowed = "admin"
seller RoleAllowed = "seller"
rider RoleAllowed = "rider"
regular RoleAllowed = "regular"
)
func (st *RoleAllowed) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
*st = RoleAllowed(b)
}
return nil
}
func (st RoleAllowed) Value() (driver.Value, error) {
return string(st), nil
}
type StatusAllowed string
const (
suspended StatusAllowed = "suspended"
active StatusAllowed = "active"
inactive StatusAllowed = "inactive"
)
func (st *StatusAllowed) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
*st = StatusAllowed(b)
}
return nil
}
func (st StatusAllowed) Value() (driver.Value, error) {
return string(st), nil
}
type User struct {
ID uint `json:"id" gorm:"type:bigserial;primaryKey;autoIncrement"`
Email string `json:"email" gorm:"type:varchar(255);unique;not null"`
Password string `json:"password" gorm:"type:varchar(255);unique;not null""`
OTPCode int `json:"otp_code"`
Role RoleAllowed `json:"role" gorm:"type:role_allowed;default:'regular'"`
Status StatusAllowed `json:"status" gorm:"type:status_allowed;default:'active'"`
IsEmailVerified bool `json:"isEmailVerified" gorm:"default:false"`
OTPExpireTime time.Time `json:"otp_expire_time"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func (u *User) HashPassword(password string) error {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
if err != nil {
return err
}
u.Password = string(bytes)
return nil
}
func (u *User) CheckPasswordHash(password string) error {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
if err != nil {
return err
}
return nil
}
I had to check the type then assign the right value I need. thanks #mkopriva

Issue with Sqlc auto generated code golang

/ Code generated by sqlc. DO NOT EDIT.
package db
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx, // here the error is being displayed
}
}
ERROR
cannot use tx (variable of type *sql.Tx) as DBTX value in struct literal: wrong type for method ExecContext (have func(ctx context.Context, query string, args ...interface{}) (database/sql.Result, error), want func(context.Context, string, ...interface{}) (database/sql.Result, error)) compilerInvalidIfaceAssign
It was an error of VS CODE, I highly recommend to use GOLAND by jetbrains for golang

How to mock functions from libraries like amqp.Dial

I'm working on a small AMQP consumer and i want to test my consumer code, but im struggleing to mock the amqp.Dial. I have added some interface so i can mock Connection and Channel and added a property so i can control the dial-function:
//consumer.go
type AmqpChannel interface {
ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args amqp.Table) error
QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args amqp.Table) (amqp.Queue, error)
QueueBind(name, key, exchange string, noWait bool, args amqp.Table) error
Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args amqp.Table) (<-chan amqp.Delivery, error)
Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error
}
type AmqpConnection interface {
Channel() (AmqpChannel, error)
Close() error
}
type AmqpDial func(url string) (AmqpConnection, error)
type MyConsumer struct {
HostDsn string
channel AmqpChannel
queue amqp.Queue
connection AmqpConnection
DialFunc AmqpDial
}
func (c *MyConsumer) Connect() error {
var err error
c.connection, err = c.DialFunc(c.HostDsn)
...
This seems to be close to what i want to achive, i can specify my test like this:
func TestConsumer(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
var myConsumer = consumer.MyConsumer{
HostDsn: "test",
DialFunc: func(url string) (consumer.AmqpConnection, error) {
return mocks.NewMockAmqpConnection(mockCtrl), nil
},
}
_ = myConsumer.Connect()
}
But i cannot pass the original amqp.Dial as dial-func on the main routine:
myConsumer := consumer.MyConsumer{
HostDsn: fmt.Sprintf(
"amqp://%s:%s#rabbitmq:5672/?heartbeat=5s",
os.Getenv("RABBITMQ_USER"),
url.QueryEscape(os.Getenv("RABBITMQ_PASSWORD")),
),
DialFunc: amqp.Dial,
}
gives
./main.go:28:9: cannot use amqp.Dial (type func(string) (*amqp.Connection, error)) as type consumer.AmqpDial in field value
I hoped/thought that, as amqp.Connection fulfills the AmqpConnection interface, this would work :/
What is the proper way of mocking methods like amqp.Dial?
P.S.: im aware of https://github.com/NeowayLabs/wabbit but i would prefer to solve this on a lower level :)
Update: #mkopriva suggested to use another wrapper method, so i tried:
func getDialer(url string) (consumer.AmqpConnection, error) {
var connection, err = amqp.Dial(url)
return connection, err
}
myConsumer := consumer.myConsumer{
HostDsn: fmt.Sprintf(
"amqp://%s:%s#rabbitmq:5672/?heartbeat=5s",
os.Getenv("RABBITMQ_USER"),
url.QueryEscape(os.Getenv("RABBITMQ_PASSWORD")),
),
DialFunc: getDialer,
}
But this results in:
cannot use connection (type *amqp.Connection) as type consumer.AmqpConnection in return argument:
*amqp.Connection does not implement consumer.AmqpConnection (wrong type for Channel method)
have Channel() (*amqp.Channel, error)
want Channel() (consumer.AmqpChannel, error)
make: *** [Makefile:4: build-consumer] Error 2
Given the following types:
type AmqpChannel interface {
ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args amqp.Table) error
QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args amqp.Table) (amqp.Queue, error)
QueueBind(name, key, exchange string, noWait bool, args amqp.Table) error
Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args amqp.Table) (<-chan amqp.Delivery, error)
Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error
}
type AmqpConnection interface {
Channel() (AmqpChannel, error)
Close() error
}
type AmqpDial func(url string) (AmqpConnection, error)
You can create simple wrappers that delegate to the actual code:
func AmqpDialWrapper(url string) (AmqpConnection, error) {
conn, err := amqp.Dial(url)
if err != nil {
return nil, err
}
return AmqpConnectionWrapper{conn}, nil
}
type AmqpConnectionWrapper struct {
conn *amqp.Connection
}
// If *amqp.Channel does not satisfy the consumer.AmqpChannel interface
// then you'll need another wrapper, a AmqpChannelWrapper, that implements
// the consumer.AmqpChannel interface and delegates to *amqp.Channel.
func (w AmqpConnectionWrapper) Channel() (AmqpChannel, error) {
return w.conn.Channel()
}
func (w AmqpConnectionWrapper) Close() error {
return w.conn.Close()
}

Inserting custom time, Scanner and Valuer implemented — but still errs

I have a custom time format that is the result of some custom unmarshalling:
type customTime struct {
time.Time
}
I have implemented the Scanner and Valuer interface on this customTime like so:
func (ct *customTime) Scan(value interface{}) error {
ct.Time = value.(time.Time)
return nil
}
func (ct *customTime) Value() (driver.Value, error) {
return ct.Time, nil
}
But it still errs when I try to do the insert:
sql: converting Exec argument $3 type: unsupported type main.customTime, a struct
What am I missing?
Found the solution, Scanner and Valuer should be implemented on the actual value and not a pointer to the customTime
func (ct customTime) Scan(value interface{}) error {
ct.Time = value.(time.Time)
return nil
}
func (ct customTime) Value() (driver.Value, error) {
return ct.Time, nil
}

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

Resources