1
0
Fork 0
forked from forgejo/forgejo

[Vendor] mssqldb: 2019-11-28 -> 2020-04-28 (#11364)

update go-mssqldb 2019-11-28 (1d7a30a10f73) -> 2020-04-28 (06a60b6afbbc)
This commit is contained in:
6543 2020-05-10 19:42:52 +02:00 committed by GitHub
parent da5e3fa299
commit 6e23a1b843
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 987 additions and 1378 deletions

View file

@ -18,7 +18,7 @@ Other supported formats are listed below.
### Common parameters:
* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used.
* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used. The user domain sensitive to the case which is defined in the connection string.
* `password`
* `database`
* `connection timeout` - in seconds (default is 0 for no timeout), set to 0 for no timeout. Recommended to set to 0 and use context to manage query and connection timeouts.
@ -106,6 +106,26 @@ Other supported formats are listed below.
* `odbc:server=localhost;user id=sa;password={foo{bar}` // Literal `{`, password is "foo{bar"
* `odbc:server=localhost;user id=sa;password={foo}}bar}` // Escaped `} with `}}`, password is "foo}bar"
### Azure Active Directory authentication - preview
The configuration of functionality might change in the future.
Azure Active Directory (AAD) access tokens are relatively short lived and need to be
valid when a new connection is made. Authentication is supported using a callback func that
provides a fresh and valid token using a connector:
``` golang
conn, err := mssql.NewAccessTokenConnector(
"Server=test.database.windows.net;Database=testdb",
tokenProvider)
if err != nil {
// handle errors in DSN
}
db := sql.OpenDB(conn)
```
Where `tokenProvider` is a function that returns a fresh access token or an error. None of these statements
actually trigger the retrieval of a token, this happens when the first statment is issued and a connection
is created.
## Executing Stored Procedures
To run a stored procedure, set the query text to the procedure name:

View file

@ -0,0 +1,51 @@
// +build go1.10
package mssql
import (
"context"
"database/sql/driver"
"errors"
"fmt"
)
var _ driver.Connector = &accessTokenConnector{}
// accessTokenConnector wraps Connector and injects a
// fresh access token when connecting to the database
type accessTokenConnector struct {
Connector
accessTokenProvider func() (string, error)
}
// NewAccessTokenConnector creates a new connector from a DSN and a token provider.
// The token provider func will be called when a new connection is requested and should return a valid access token.
// The returned connector may be used with sql.OpenDB.
func NewAccessTokenConnector(dsn string, tokenProvider func() (string, error)) (driver.Connector, error) {
if tokenProvider == nil {
return nil, errors.New("mssql: tokenProvider cannot be nil")
}
conn, err := NewConnector(dsn)
if err != nil {
return nil, err
}
c := &accessTokenConnector{
Connector: *conn,
accessTokenProvider: tokenProvider,
}
return c, nil
}
// Connect returns a new database connection
func (c *accessTokenConnector) Connect(ctx context.Context) (driver.Conn, error) {
var err error
c.Connector.params.fedAuthAccessToken, err = c.accessTokenProvider()
if err != nil {
return nil, fmt.Errorf("mssql: error retrieving access token: %+v", err)
}
return c.Connector.Connect(ctx)
}

View file

@ -37,6 +37,7 @@ type connectParams struct {
failOverPartner string
failOverPort uint64
packetSize uint16
fedAuthAccessToken string
}
func parseConnectParams(dsn string) (connectParams, error) {

View file

@ -397,7 +397,10 @@ func (s *Stmt) Close() error {
}
func (s *Stmt) SetQueryNotification(id, options string, timeout time.Duration) {
to := uint32(timeout / time.Second)
// 2.2.5.3.1 Query Notifications Header
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/e168d373-a7b7-41aa-b6ca-25985466a7e0
// Timeout in milliseconds in TDS protocol.
to := uint32(timeout / time.Millisecond)
if to < 1 {
to = 1
}

View file

@ -4,11 +4,14 @@ package mssql
import (
"crypto/des"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"strings"
"time"
"unicode/utf16"
"golang.org/x/crypto/md4"
@ -198,86 +201,204 @@ func ntlmSessionResponse(clientNonce [8]byte, serverChallenge [8]byte, password
return response(hash, passwordHash)
}
func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) {
if string(bytes[0:8]) != "NTLMSSP\x00" {
return nil, errorNTLM
}
if binary.LittleEndian.Uint32(bytes[8:12]) != _CHALLENGE_MESSAGE {
return nil, errorNTLM
}
flags := binary.LittleEndian.Uint32(bytes[20:24])
var challenge [8]byte
copy(challenge[:], bytes[24:32])
func ntlmHashNoPadding(val string) []byte {
hash := make([]byte, 16)
h := md4.New()
h.Write(utf16le(val))
h.Sum(hash[:0])
var lm, nt []byte
if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 {
nonce := clientChallenge()
var lm_bytes [24]byte
copy(lm_bytes[:8], nonce[:])
lm = lm_bytes[:]
nt_bytes := ntlmSessionResponse(nonce, challenge, auth.Password)
nt = nt_bytes[:]
} else {
lm_bytes := lmResponse(challenge, auth.Password)
lm = lm_bytes[:]
nt_bytes := ntResponse(challenge, auth.Password)
nt = nt_bytes[:]
return hash
}
func hmacMD5(passwordHash, data []byte) []byte {
hmacEntity := hmac.New(md5.New, passwordHash)
hmacEntity.Write(data)
return hmacEntity.Sum(nil)
}
func getNTLMv2AndLMv2ResponsePayloads(userDomain, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) {
// NTLMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response
ntlmHash := ntlmHashNoPadding(password)
usernameAndTargetBytes := utf16le(strings.ToUpper(username) + userDomain)
ntlmV2Hash := hmacMD5(ntlmHash, usernameAndTargetBytes)
targetInfoLength := len(targetInfoFields)
blob := make([]byte, 32+targetInfoLength)
binary.BigEndian.PutUint32(blob[:4], 0x01010000)
binary.BigEndian.PutUint32(blob[4:8], 0x00000000)
binary.BigEndian.PutUint64(blob[8:16], uint64(timestamp.UnixNano()))
copy(blob[16:24], nonce[:])
binary.BigEndian.PutUint32(blob[24:28], 0x00000000)
copy(blob[28:], targetInfoFields)
binary.BigEndian.PutUint32(blob[28+targetInfoLength:], 0x00000000)
challengeLength := len(challenge)
blobLength := len(blob)
challengeAndBlob := make([]byte, challengeLength+blobLength)
copy(challengeAndBlob[:challengeLength], challenge[:])
copy(challengeAndBlob[challengeLength:], blob)
hashedChallenge := hmacMD5(ntlmV2Hash, challengeAndBlob)
ntlmV2Payload = append(hashedChallenge, blob...)
// LMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theLmv2Response
ntlmV2hash := hmacMD5(ntlmHash, usernameAndTargetBytes)
challengeAndNonce := make([]byte, 16)
copy(challengeAndNonce[:8], challenge[:])
copy(challengeAndNonce[8:], nonce[:])
hashedChallenge = hmacMD5(ntlmV2hash, challengeAndNonce)
lmV2Payload = append(hashedChallenge, nonce[:]...)
return
}
func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password, userDom string) (lm, nt []byte, err error) {
nonce := clientChallenge()
// Official specification: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
// Unofficial walk through referenced by https://www.freetds.org/userguide/domains.htm: http://davenport.sourceforge.net/ntlm.html
if (flags & _NEGOTIATE_TARGET_INFO) != 0 {
targetInfoFields, err := getNTLMv2TargetInfoFields(message)
if err != nil {
return lm, nt, err
}
nt, lm = getNTLMv2AndLMv2ResponsePayloads(userDom, username, password, challenge, nonce, targetInfoFields, time.Now())
return lm, nt, nil
}
var lm_bytes [24]byte
copy(lm_bytes[:8], nonce[:])
lm = lm_bytes[:]
nt_bytes := ntlmSessionResponse(nonce, challenge, password)
nt = nt_bytes[:]
return lm, nt, nil
}
func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, err error) {
type2MessageError := "mssql: while parsing NTLMv2 type 2 message, length %d too small for offset %d"
type2MessageLength := len(type2Message)
if type2MessageLength < 20 {
return nil, fmt.Errorf(type2MessageError, type2MessageLength, 20)
}
targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16])
targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20])
endOfOffset := int(targetNameOffset + uint32(targetNameAllocated))
if type2MessageLength < endOfOffset {
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
}
targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44])
targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48])
endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated))
if type2MessageLength < endOfOffset {
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
}
targetInformationBytes := make([]byte, targetInformationAllocated)
copy(targetInformationBytes, type2Message[targetInformationDataOffset:targetInformationDataOffset+uint32(targetInformationAllocated)])
return targetInformationBytes, nil
}
func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation, username string) ([]byte, error) {
lm_len := len(lm)
nt_len := len(nt)
domain16 := utf16le(auth.Domain)
domain16 := utf16le(domain)
domain_len := len(domain16)
user16 := utf16le(auth.UserName)
user16 := utf16le(username)
user_len := len(user16)
workstation16 := utf16le(auth.Workstation)
workstation16 := utf16le(workstation)
workstation_len := len(workstation16)
msg := make([]byte, 88+lm_len+nt_len+domain_len+user_len+workstation_len)
copy(msg, []byte("NTLMSSP\x00"))
binary.LittleEndian.PutUint32(msg[8:], _AUTHENTICATE_MESSAGE)
// Lm Challenge Response Fields
binary.LittleEndian.PutUint16(msg[12:], uint16(lm_len))
binary.LittleEndian.PutUint16(msg[14:], uint16(lm_len))
binary.LittleEndian.PutUint32(msg[16:], 88)
// Nt Challenge Response Fields
binary.LittleEndian.PutUint16(msg[20:], uint16(nt_len))
binary.LittleEndian.PutUint16(msg[22:], uint16(nt_len))
binary.LittleEndian.PutUint32(msg[24:], uint32(88+lm_len))
// Domain Name Fields
binary.LittleEndian.PutUint16(msg[28:], uint16(domain_len))
binary.LittleEndian.PutUint16(msg[30:], uint16(domain_len))
binary.LittleEndian.PutUint32(msg[32:], uint32(88+lm_len+nt_len))
// User Name Fields
binary.LittleEndian.PutUint16(msg[36:], uint16(user_len))
binary.LittleEndian.PutUint16(msg[38:], uint16(user_len))
binary.LittleEndian.PutUint32(msg[40:], uint32(88+lm_len+nt_len+domain_len))
// Workstation Fields
binary.LittleEndian.PutUint16(msg[44:], uint16(workstation_len))
binary.LittleEndian.PutUint16(msg[46:], uint16(workstation_len))
binary.LittleEndian.PutUint32(msg[48:], uint32(88+lm_len+nt_len+domain_len+user_len))
// Encrypted Random Session Key Fields
binary.LittleEndian.PutUint16(msg[52:], 0)
binary.LittleEndian.PutUint16(msg[54:], 0)
binary.LittleEndian.PutUint32(msg[56:], uint32(88+lm_len+nt_len+domain_len+user_len+workstation_len))
// Negotiate Flags
binary.LittleEndian.PutUint32(msg[60:], flags)
// Version
binary.LittleEndian.PutUint32(msg[64:], 0)
binary.LittleEndian.PutUint32(msg[68:], 0)
// MIC
binary.LittleEndian.PutUint32(msg[72:], 0)
binary.LittleEndian.PutUint32(msg[76:], 0)
binary.LittleEndian.PutUint32(msg[88:], 0)
binary.LittleEndian.PutUint32(msg[84:], 0)
// Payload
copy(msg[88:], lm)
copy(msg[88+lm_len:], nt)
copy(msg[88+lm_len+nt_len:], domain16)
copy(msg[88+lm_len+nt_len+domain_len:], user16)
copy(msg[88+lm_len+nt_len+domain_len+user_len:], workstation16)
return msg, nil
}
func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) {
signature := string(bytes[0:8])
if signature != "NTLMSSP\x00" {
return nil, errorNTLM
}
messageTypeIndicator := binary.LittleEndian.Uint32(bytes[8:12])
if messageTypeIndicator != _CHALLENGE_MESSAGE {
return nil, errorNTLM
}
var challenge [8]byte
copy(challenge[:], bytes[24:32])
flags := binary.LittleEndian.Uint32(bytes[20:24])
if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 {
lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password, auth.Domain)
if err != nil {
return nil, err
}
return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName)
}
lm_bytes := lmResponse(challenge, auth.Password)
lm := lm_bytes[:]
nt_bytes := ntResponse(challenge, auth.Password)
nt := nt_bytes[:]
return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName)
}
func (auth *ntlmAuth) Free() {
}

View file

@ -100,13 +100,15 @@ const (
// prelogin fields
// http://msdn.microsoft.com/en-us/library/dd357559.aspx
const (
preloginVERSION = 0
preloginENCRYPTION = 1
preloginINSTOPT = 2
preloginTHREADID = 3
preloginMARS = 4
preloginTRACEID = 5
preloginTERMINATOR = 0xff
preloginVERSION = 0
preloginENCRYPTION = 1
preloginINSTOPT = 2
preloginTHREADID = 3
preloginMARS = 4
preloginTRACEID = 5
preloginFEDAUTHREQUIRED = 6
preloginNONCEOPT = 7
preloginTERMINATOR = 0xff
)
const (
@ -245,6 +247,12 @@ const (
fReadOnlyIntent = 32
)
// OptionFlags3
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/773a62b6-ee89-4c02-9e5e-344882630aac
const (
fExtension = 0x10
)
type login struct {
TDSVersion uint32
PacketSize uint32
@ -269,6 +277,89 @@ type login struct {
SSPI []byte
AtchDBFile string
ChangePassword string
FeatureExt featureExts
}
type featureExts struct {
features map[byte]featureExt
}
type featureExt interface {
featureID() byte
toBytes() []byte
}
func (e *featureExts) Add(f featureExt) error {
if f == nil {
return nil
}
id := f.featureID()
if _, exists := e.features[id]; exists {
f := "Login error: Feature with ID '%v' is already present in FeatureExt block."
return fmt.Errorf(f, id)
}
if e.features == nil {
e.features = make(map[byte]featureExt)
}
e.features[id] = f
return nil
}
func (e featureExts) toBytes() []byte {
if len(e.features) == 0 {
return nil
}
var d []byte
for featureID, f := range e.features {
featureData := f.toBytes()
hdr := make([]byte, 5)
hdr[0] = featureID // FedAuth feature extension BYTE
binary.LittleEndian.PutUint32(hdr[1:], uint32(len(featureData))) // FeatureDataLen DWORD
d = append(d, hdr...)
d = append(d, featureData...) // FeatureData *BYTE
}
if d != nil {
d = append(d, 0xff) // Terminator
}
return d
}
type featureExtFedAuthSTS struct {
FedAuthEcho bool
FedAuthToken string
Nonce []byte
}
func (e *featureExtFedAuthSTS) featureID() byte {
return 0x02
}
func (e *featureExtFedAuthSTS) toBytes() []byte {
if e == nil {
return nil
}
options := byte(0x01) << 1 // 0x01 => STS bFedAuthLibrary 7BIT
if e.FedAuthEcho {
options |= 1 // fFedAuthEcho
}
d := make([]byte, 5)
d[0] = options
// looks like string in
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/f88b63bb-b479-49e1-a87b-deda521da508
tokenBytes := str2ucs2(e.FedAuthToken)
binary.LittleEndian.PutUint32(d[1:], uint32(len(tokenBytes))) // Should be a signed int32, but since the length is relatively small, this should work
d = append(d, tokenBytes...)
if len(e.Nonce) == 32 {
d = append(d, e.Nonce...)
}
return d
}
type loginHeader struct {
@ -295,7 +386,7 @@ type loginHeader struct {
ServerNameOffset uint16
ServerNameLength uint16
ExtensionOffset uint16
ExtensionLenght uint16
ExtensionLength uint16
CtlIntNameOffset uint16
CtlIntNameLength uint16
LanguageOffset uint16
@ -357,6 +448,8 @@ func sendLogin(w *tdsBuffer, login login) error {
database := str2ucs2(login.Database)
atchdbfile := str2ucs2(login.AtchDBFile)
changepassword := str2ucs2(login.ChangePassword)
featureExt := login.FeatureExt.toBytes()
hdr := loginHeader{
TDSVersion: login.TDSVersion,
PacketSize: login.PacketSize,
@ -405,7 +498,18 @@ func sendLogin(w *tdsBuffer, login login) error {
offset += uint16(len(atchdbfile))
hdr.ChangePasswordOffset = offset
offset += uint16(len(changepassword))
hdr.Length = uint32(offset)
featureExtOffset := uint32(0)
featureExtLen := len(featureExt)
if featureExtLen > 0 {
hdr.OptionFlags3 |= fExtension
hdr.ExtensionOffset = offset
hdr.ExtensionLength = 4
offset += hdr.ExtensionLength // DWORD
featureExtOffset = uint32(offset)
}
hdr.Length = uint32(offset) + uint32(featureExtLen)
var err error
err = binary.Write(w, binary.LittleEndian, &hdr)
if err != nil {
@ -455,6 +559,16 @@ func sendLogin(w *tdsBuffer, login login) error {
if err != nil {
return err
}
if featureExtOffset > 0 {
err = binary.Write(w, binary.LittleEndian, featureExtOffset)
if err != nil {
return err
}
_, err = w.Write(featureExt)
if err != nil {
return err
}
}
return w.FinishPacket()
}
@ -844,15 +958,23 @@ initiate_connection:
AppName: p.appname,
TypeFlags: p.typeFlags,
}
auth, auth_ok := getAuth(p.user, p.password, p.serverSPN, p.workstation)
if auth_ok {
auth, authOk := getAuth(p.user, p.password, p.serverSPN, p.workstation)
switch {
case p.fedAuthAccessToken != "": // accesstoken ignores user/password
featurext := &featureExtFedAuthSTS{
FedAuthEcho: len(fields[preloginFEDAUTHREQUIRED]) > 0 && fields[preloginFEDAUTHREQUIRED][0] == 1,
FedAuthToken: p.fedAuthAccessToken,
Nonce: fields[preloginNONCEOPT],
}
login.FeatureExt.Add(featurext)
case authOk:
login.SSPI, err = auth.InitialBytes()
if err != nil {
return nil, err
}
login.OptionFlags2 |= fIntSecurity
defer auth.Free()
} else {
default:
login.UserName = p.user
login.Password = p.password
}

View file

@ -17,20 +17,21 @@ type token byte
// token ids
const (
tokenReturnStatus token = 121 // 0x79
tokenColMetadata token = 129 // 0x81
tokenOrder token = 169 // 0xA9
tokenError token = 170 // 0xAA
tokenInfo token = 171 // 0xAB
tokenReturnValue token = 0xAC
tokenLoginAck token = 173 // 0xad
tokenRow token = 209 // 0xd1
tokenNbcRow token = 210 // 0xd2
tokenEnvChange token = 227 // 0xE3
tokenSSPI token = 237 // 0xED
tokenDone token = 253 // 0xFD
tokenDoneProc token = 254
tokenDoneInProc token = 255
tokenReturnStatus token = 121 // 0x79
tokenColMetadata token = 129 // 0x81
tokenOrder token = 169 // 0xA9
tokenError token = 170 // 0xAA
tokenInfo token = 171 // 0xAB
tokenReturnValue token = 0xAC
tokenLoginAck token = 173 // 0xad
tokenFeatureExtAck token = 174 // 0xae
tokenRow token = 209 // 0xd1
tokenNbcRow token = 210 // 0xd2
tokenEnvChange token = 227 // 0xE3
tokenSSPI token = 237 // 0xED
tokenDone token = 253 // 0xFD
tokenDoneProc token = 254
tokenDoneInProc token = 255
)
// done flags
@ -447,6 +448,22 @@ func parseLoginAck(r *tdsBuffer) loginAckStruct {
return res
}
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/2eb82f8e-11f0-46dc-b42d-27302fa4701a
func parseFeatureExtAck(r *tdsBuffer) {
// at most 1 featureAck per feature in featureExt
// go-mssqldb will add at most 1 feature, the spec defines 7 different features
for i := 0; i < 8; i++ {
featureID := r.byte() // FeatureID
if featureID == 0xff {
return
}
size := r.uint32() // FeatureAckDataLen
d := make([]byte, size)
r.ReadFull(d)
}
panic("parsed more than 7 featureAck's, protocol implementation error?")
}
// http://msdn.microsoft.com/en-us/library/dd357363.aspx
func parseColMetadata72(r *tdsBuffer) (columns []columnStruct) {
count := r.uint16()
@ -577,6 +594,8 @@ func processSingleResponse(sess *tdsSession, ch chan tokenStruct, outs map[strin
case tokenLoginAck:
loginAck := parseLoginAck(sess.buf)
ch <- loginAck
case tokenFeatureExtAck:
parseFeatureExtAck(sess.buf)
case tokenOrder:
order := parseOrder(sess.buf)
ch <- order