Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed reading connection attributes on server side #676

Merged
merged 7 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07
github.com/stretchr/testify v1.7.0
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/text v0.3.6 // indirect
Expand Down
6 changes: 5 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07 h1:oI+RNwuC9jF2g2lP0u0cVEEZrc/AYBCuFdvwrLWM/6Q=
github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
Expand Down Expand Up @@ -112,5 +114,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
161 changes: 161 additions & 0 deletions mocks/Handler.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 17 additions & 5 deletions server/handshake_resp.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (c *Conn) readHandshakeResponse() error {

pos = c.readPluginName(data, pos)

cont, err := c.handleAuthMatch(authData, pos)
cont, err := c.handleAuthMatch()
if err != nil {
return err
}
Expand Down Expand Up @@ -130,7 +130,7 @@ func (c *Conn) readDb(data []byte, pos int) (int, error) {
func (c *Conn) readPluginName(data []byte, pos int) int {
if c.capability&CLIENT_PLUGIN_AUTH != 0 {
c.authPluginName = string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)])
pos += len(c.authPluginName)
pos += len(c.authPluginName) + 1
} else {
// The method used is Native Authentication if both CLIENT_PROTOCOL_41 and CLIENT_SECURE_CONNECTION are set,
// but CLIENT_PLUGIN_AUTH is not set, so we fallback to 'mysql_native_password'
Expand Down Expand Up @@ -191,7 +191,7 @@ func (c *Conn) handlePublicKeyRetrieval(authData []byte) (bool, error) {
return true, nil
}

func (c *Conn) handleAuthMatch(authData []byte, pos int) (bool, error) {
func (c *Conn) handleAuthMatch() (bool, error) {
// if the client responds the handshake with a different auth method, the server will send the AuthSwitchRequest packet
// to the client to ask the client to switch.

Expand All @@ -207,17 +207,29 @@ func (c *Conn) handleAuthMatch(authData []byte, pos int) (bool, error) {
}

func (c *Conn) readAttributes(data []byte, pos int) (int, error) {
attrs := make(map[string]string)
// read length of attribute data
attrLen, isNull, skip := LengthEncodedInt(data[pos:])
pos += skip
if isNull {
return pos, nil
}

if len(data) < pos+int(attrLen) {
return pos, errors.New("corrupt attributes data")
}

i := 0
attrs := make(map[string]string)
var key string

// read until end of data or NUL for atrribute key/values
for {
str, isNull, strLen, err := LengthEncodedString(data[pos:])

if err != nil {
return -1, err
}

// end of data
if isNull {
break
}
Expand Down
118 changes: 117 additions & 1 deletion server/handshake_resp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"bytes"
"testing"

"github.com/go-mysql-org/go-mysql/mocks"
"github.com/go-mysql-org/go-mysql/mysql"
"github.com/stretchr/testify/mock"
)

func TestReadAuthData(t *testing.T) {
Expand Down Expand Up @@ -53,6 +55,120 @@ func TestDecodeFirstPart(t *testing.T) {
}
}

func TestReadDB(t *testing.T) {
handler := &mocks.Handler{}
c := &Conn{
h: handler,
}
c.SetCapability(mysql.CLIENT_CONNECT_WITH_DB)
var dbName string

// when handler's UseDB is called, copy dbName to local variable
handler.On("UseDB", mock.IsType("")).Return(nil).Once().RunFn = func(args mock.Arguments) {
dbName = args[0].(string)
}

// example data from
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse41
data := []byte{
0x54, 0x00, 0x00, 0x01, 0x8d, 0xa6, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x01,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x70, 0x61, 0x6d, 0x00, 0x14, 0xab, 0x09, 0xee, 0xf6, 0xbc, 0xb1, 0x32,
0x3e, 0x61, 0x14, 0x38, 0x65, 0xc0, 0x99, 0x1d, 0x95, 0x7d, 0x75, 0xd4,
0x47, 0x74, 0x65, 0x73, 0x74, 0x00, 0x6d, 0x79, 0x73, 0x71, 0x6c, 0x5f,
0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x00,
}
pos := 61

var err error
pos, err = c.readDb(data, pos)
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}

if pos != 66 { // 61 + len("test") + 1
t.Fatalf("unexpected pos, got %d", pos)
}

if dbName != "test" {
t.Fatalf("unexpected db, got %s", dbName)
}

handler.AssertExpectations(t)
}

func TestReadPluginName(t *testing.T) {
// example data from
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse41
mysqlNativePassword := []byte{
0x54, 0x00, 0x00, 0x01, 0x8d, 0xa6, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x70, 0x61, 0x6d, 0x00, 0x14, 0xab, 0x09, 0xee,
0xf6, 0xbc, 0xb1, 0x32, 0x3e, 0x61, 0x14, 0x38, 0x65, 0xc0, 0x99,
0x1d, 0x95, 0x7d, 0x75, 0xd4, 0x47, 0x74, 0x65, 0x73, 0x74, 0x00,
0x6d, 0x79, 0x73, 0x71, 0x6c, 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76,
0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x00,
}

// altered example data so it has different auth plugin
otherPlugin := []byte{
0x54, 0x00, 0x00, 0x01, 0x8d, 0xa6, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x70, 0x61, 0x6d, 0x00, 0x14, 0xab, 0x09, 0xee,
0xf6, 0xbc, 0xb1, 0x32, 0x3e, 0x61, 0x14, 0x38, 0x65, 0xc0, 0x99,
0x1d, 0x95, 0x7d, 0x75, 0xd4, 0x47, 0x74, 0x65, 0x73, 0x74, 0x00,
0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x00,
}

t.Run("mysql_native_password from plugin name", func(t *testing.T) {
c := &Conn{}
c.SetCapability(mysql.CLIENT_PLUGIN_AUTH)
pos := 66

pos = c.readPluginName(mysqlNativePassword, pos)
if pos != 88 { // 66 + len("mysql_native_password") + 1
t.Fatalf("unexpected pos, got %d", pos)
}

if c.authPluginName != "mysql_native_password" {
t.Fatalf("unexpected plugin name, got %s", c.authPluginName)
}
})

t.Run("other plugin", func(t *testing.T) {
c := &Conn{}
c.SetCapability(mysql.CLIENT_PLUGIN_AUTH)
pos := 66

pos = c.readPluginName(otherPlugin, pos)
if pos != 73 { // 66 + len("foobar") + 1
t.Fatalf("unexpected pos, got %d", pos)
}

if c.authPluginName != "foobar" {
t.Fatalf("unexpected plugin name, got %s", c.authPluginName)
}
})

t.Run("mysql_native_password as default", func(t *testing.T) {
c := &Conn{}
pos := 123 // can be anything

pos = c.readPluginName(mysqlNativePassword, pos)
if pos != 123 { // capability not set, so same as initial pos
t.Fatalf("unexpected pos, got %d", pos)
}

if c.authPluginName != mysql.AUTH_NATIVE_PASSWORD {
t.Fatalf("unexpected plugin name, got %s", c.authPluginName)
}
})
}

func TestReadAttributes(t *testing.T) {
var err error
// example data from
Expand All @@ -75,7 +191,7 @@ func TestReadAttributes(t *testing.T) {
0x6d, 0x06, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x03, 0x66, 0x6f, 0x6f,
0x03, 0x62, 0x61, 0x72,
}
pos := 85
pos := 84

c := &Conn{}

Expand Down