I am trying to connect to SQL Server DB with integrated auth while impersonating a user who has DB access. The logon and impersonation Windows API call works fine, but the database connection fails with error
unable to open tcp connection with host '10.199.25.69:1433': dial tcp 10.199.25.69:1433: socket: winapi error #10106
On looking deeper into the windows package, I could see the call failing on function internal\syscall\windows\zsyscall_windows.go.WSASocket on the below line
r0, _, e1 := syscall.Syscall6(procWSASocketW.Addr(), 6, uintptr(af), uintptr(typ), uintptr(protocol), uintptr(unsafe.Pointer(protinfo)), uintptr(group), uintptr(flags))
where procWSASocketW = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll")).NewProc("WSASocketW")
The call to procWSASocketW fails with error
golang.org/x/sys/windows.WSAEPROVIDERFAILEDINIT (10106) = 0x277a
I tried connecting to the DB with SQLServer Auth and surprisingly it fails as well with the same error. I am able to connect to database outside of impersonation block. It is only while impersonating the connection fails.
Also, I did not see any difference in the parameters of procWSASocketW function in a failed vs a successful call, which makes me think that there is something in the procWSASocketW that does not like the process being impersonated.
Not sure what I am doing wrong. I did check the access token returned from the logon call and it looks valid. Below is a sample code that I have been playing with. Any help is appreciated.
package main
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"os/signal"
"runtime"
"sync"
"syscall"
"unsafe"
_ "github.com/microsoft/go-mssqldb"
"golang.org/x/sys/windows"
)
const (
user = "username"
pass = "password"
domain = "domain"
)
var (
advapi32 = syscall.NewLazyDLL("advapi32.dll")
logonProc = advapi32.NewProc("LogonUserW")
impersonateProc = advapi32.NewProc("ImpersonateLoggedOnUser")
revertSelfProc = advapi32.NewProc("RevertToSelf")
)
func main() {
log.SetFlags(0)
//connectToDB()
var wg sync.WaitGroup
wg.Add(1)
go func(user, pass string) {
defer wg.Done()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
log.Println("In a goroutine")
token, err := logonUser(user, pass, domain)
if err != nil {
fmt.Println(err)
}
u, _ := token.GetTokenUser() //the sid returned matches with whoami /user
log.Println(u)
r, _ := token.IsRestricted() // returns false
log.Println(fmt.Printf("token is restricted: %t", r))
e := token.IsElevated() // returns true
log.Println(fmt.Printf("token is elevated: %t", e))
defer closeHandle(token)
err = impersonateUser(token)
if err != nil {
log.Println(fmt.Println(err))
}
connectToDB()
defer mustRevertToSelf()
log.Println("Impersonated")
}(user, pass)
wg.Wait()
log.Println("OK")
}
func logonUser(user, pass, domain string) (windows.Token, error) {
const (
// Taken from WinBase.h (SDK 7.1):
LOGON32_LOGON_NETWORK = uintptr(3)
LOGON32_PROVIDER_DEFAULT = uintptr(0)
LOGON32_LOGON_SERVICE = uintptr(5)
LOGON32_LOGON_NETWORK_CLEARTEXT = uintptr(8)
)
pUsername, _ := windows.UTF16PtrFromString(user)
pDomain, _ := windows.UTF16PtrFromString(domain)
pPassword, _ := windows.UTF16PtrFromString(pass)
hToken := uintptr(0)
res, _, err := logonProc.Call(
uintptr(unsafe.Pointer(pUsername)),
uintptr(unsafe.Pointer(pDomain)),
uintptr(unsafe.Pointer(pPassword)),
uintptr(LOGON32_LOGON_NETWORK_CLEARTEXT),
uintptr(LOGON32_PROVIDER_DEFAULT),
uintptr(unsafe.Pointer(&hToken)),
)
if res != 0 {
return windows.Token(hToken), nil
}
return 0, err
}
func impersonateUser(token windows.Token) error {
res, _, err := impersonateProc.Call(uintptr(token))
if res == 0 {
return error(err)
}
return nil
}
func revertToSelf() error {
rc, _, ec := syscall.SyscallN(revertSelfProc.Addr(), 0, 0, 0, 0)
if rc == 0 {
return error(ec)
}
return nil
}
func mustRevertToSelf() {
err := revertToSelf()
if err != nil {
panic(err)
}
}
func closeHandle(token windows.Token) {
err := token.Close()
if err != nil {
panic(err)
}
}
func connectToDB() {
connectionStr := "Data Source=<host>;Initial Catalog=<Database>;Integrated Security=true;User ID=<user>;Password=<pwd>;Encrypt=False;TrustServerCertificate=True"
db, err := sql.Open("mssql", connectionStr)
if err != nil {
fmt.Println(err)
}
err = db.Ping()
if err != nil {
log.Println(err.Error())
}
}