diff options
Diffstat (limited to 'vendor/github.com')
-rw-r--r-- | vendor/github.com/denisenkom/go-mssqldb/README.md | 22 | ||||
-rw-r--r-- | vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go | 51 | ||||
-rw-r--r-- | vendor/github.com/denisenkom/go-mssqldb/conn_str.go | 1 | ||||
-rw-r--r-- | vendor/github.com/denisenkom/go-mssqldb/mssql.go | 5 | ||||
-rw-r--r-- | vendor/github.com/denisenkom/go-mssqldb/ntlm.go | 173 | ||||
-rw-r--r-- | vendor/github.com/denisenkom/go-mssqldb/tds.go | 146 | ||||
-rw-r--r-- | vendor/github.com/denisenkom/go-mssqldb/token.go | 47 |
7 files changed, 391 insertions, 54 deletions
diff --git a/vendor/github.com/denisenkom/go-mssqldb/README.md b/vendor/github.com/denisenkom/go-mssqldb/README.md index b655176bb6..94d87fe092 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/README.md +++ b/vendor/github.com/denisenkom/go-mssqldb/README.md @@ -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: diff --git a/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go b/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go new file mode 100644 index 0000000000..8dbe5099e4 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go @@ -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) +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/conn_str.go b/vendor/github.com/denisenkom/go-mssqldb/conn_str.go index 4ff54b8955..26ac50f38d 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/conn_str.go +++ b/vendor/github.com/denisenkom/go-mssqldb/conn_str.go @@ -37,6 +37,7 @@ type connectParams struct { failOverPartner string failOverPort uint64 packetSize uint16 + fedAuthAccessToken string } func parseConnectParams(dsn string) (connectParams, error) { diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql.go b/vendor/github.com/denisenkom/go-mssqldb/mssql.go index 5d81516919..a74bc7e3fc 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/mssql.go +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql.go @@ -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 } diff --git a/vendor/github.com/denisenkom/go-mssqldb/ntlm.go b/vendor/github.com/denisenkom/go-mssqldb/ntlm.go index 7c0cc4f785..ea9148aed0 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/ntlm.go +++ b/vendor/github.com/denisenkom/go-mssqldb/ntlm.go @@ -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 +func ntlmHashNoPadding(val string) []byte { + hash := make([]byte, 16) + h := md4.New() + h.Write(utf16le(val)) + h.Sum(hash[:0]) + + 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 } - if binary.LittleEndian.Uint32(bytes[8:12]) != _CHALLENGE_MESSAGE { - return nil, errorNTLM + + 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) } - flags := binary.LittleEndian.Uint32(bytes[20:24]) - var challenge [8]byte - copy(challenge[:], bytes[24:32]) - 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[:] + 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() { } diff --git a/vendor/github.com/denisenkom/go-mssqldb/tds.go b/vendor/github.com/denisenkom/go-mssqldb/tds.go index 9419836448..832c4fd23a 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/tds.go +++ b/vendor/github.com/denisenkom/go-mssqldb/tds.go @@ -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 } diff --git a/vendor/github.com/denisenkom/go-mssqldb/token.go b/vendor/github.com/denisenkom/go-mssqldb/token.go index 1acac8a5d2..25385e89dc 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/token.go +++ b/vendor/github.com/denisenkom/go-mssqldb/token.go @@ -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 |