From d33b929e34011e3580069e97a257aa36b3b0cc0b Mon Sep 17 00:00:00 2001 From: Alain Jobart Date: Wed, 29 Mar 2017 11:38:49 -0700 Subject: [PATCH] Adding AuthServer support for dialog. Mostly the refactoring of the AuthServer interface is in here. There is no real test for the dialog plugin method. --- go/mysqlconn/auth_server.go | 95 ++++++++++++++--- go/mysqlconn/auth_server_none.go | 21 ++-- go/mysqlconn/auth_server_static.go | 38 ++++--- go/mysqlconn/client.go | 8 +- go/mysqlconn/constants.go | 12 ++- go/mysqlconn/doc.go | 14 +-- go/mysqlconn/handshake_test.go | 13 ++- .../ldapauthserver/auth_server_ldap.go | 42 +++++--- .../ldapauthserver/auth_server_ldap_test.go | 11 +- go/mysqlconn/query_benchmark_test.go | 2 +- go/mysqlconn/server.go | 100 +++++++----------- go/mysqlconn/server_test.go | 2 +- 12 files changed, 219 insertions(+), 139 deletions(-) diff --git a/go/mysqlconn/auth_server.go b/go/mysqlconn/auth_server.go index 06d03fad96..22d697a43c 100644 --- a/go/mysqlconn/auth_server.go +++ b/go/mysqlconn/auth_server.go @@ -3,8 +3,10 @@ package mysqlconn import ( "crypto/rand" "crypto/sha1" + "fmt" log "github.com/golang/glog" + "github.com/youtube/vitess/go/sqldb" ) // AuthServer is the interface that servers must implement to validate @@ -21,26 +23,40 @@ import ( // password is sent in the clear. That may not be suitable for some // use cases. type AuthServer interface { - // UseClearText returns true is Cleartext auth is used. - // - If it is not set, Salt() and ValidateHash() are called. - // The server starts up in mysql_native_password mode. - // (but ValidateClearText can also be called, if client - // switched to Cleartext). - // - If it is set, ValidateClearText() is called. - // The server starts up in mysql_clear_password mode. - UseClearText() bool + // AuthMethod returns the authentication method to use for the + // given user. If this returns MysqlNativePassword + // (mysql_native_password), then ValidateHash() will be + // called, and no further roundtrip with the client is + // expected. If anything else is returned, Negotiate() + // will be called on the connection, and the AuthServer + // needs to handle the packets. + AuthMethod(user string) (string, error) // Salt returns the salt to use for a connection. // It should be 20 bytes of data. + // Most implementations should just use mysqlconn.NewSalt(). + // (this is meant to support a plugin that would use an + // existing MySQL server as the source of auth, and just forward + // the salt generated by that server). + // Do not return zero bytes, as a known salt can be the source + // of a crypto attack. Salt() ([]byte, error) // ValidateHash validates the data sent by the client matches // what the server computes. It also returns the user data. ValidateHash(salt []byte, user string, authResponse []byte) (Getter, error) - // ValidateClearText validates a user / password is correct. - // It also returns the user data. - ValidateClearText(user, password string) (Getter, error) + // Negotiate is called if AuthMethod returns anything else + // than MysqlNativePassword. It is handed the connection after the + // AuthSwitchRequest packet is sent. + // - If the negotiation fails, it should just return an error + // (should be a sqldb.SQLError if possible). + // The framework is responsible for writing the Error packet + // and closing the connection in that case. + // - If the negotiation works, it should return the Getter, + // and no error. The framework is responsible for writing the + // OK packet. + Negotiate(c *Conn, user string) (Getter, error) } // authServers is a registry of AuthServer implementations. @@ -63,8 +79,8 @@ func GetAuthServer(name string) AuthServer { return authServer } -// newSalt returns a 20 character salt. -func newSalt() ([]byte, error) { +// NewSalt returns a 20 character salt. +func NewSalt() ([]byte, error) { salt := make([]byte, 20) if _, err := rand.Read(salt); err != nil { return nil, err @@ -109,3 +125,56 @@ func scramblePassword(salt, password []byte) []byte { } return scramble } + +// AuthServerWritePacketString is a helper method to write a null +// terminated string into a packet, mainly to be used with the dialog +// client plugin. +func AuthServerWritePacketString(c *Conn, message string) error { + data := c.startEphemeralPacket(len(message) + 1) + pos := writeNullString(data, 0, message) + if pos != len(data) { + return fmt.Errorf("error building AuthServerWritePacketString: got %v bytes expected %v", pos, len(data)) + } + if err := c.writeEphemeralPacket(true); err != nil { + return sqldb.NewSQLError(CRServerGone, SSUnknownSQLState, err.Error()) + } + return nil +} + +// AuthServerReadPacketString is a helper method to read a packet +// as a null terminated string. It is used by the mysql_clear_password +// and dialog plugins. +func AuthServerReadPacketString(c *Conn) (string, error) { + // Read a packet, the password is the payload, as a + // zero terminated string. + data, err := c.ReadPacket() + if err != nil { + return "", err + } + if len(data) == 0 || data[len(data)-1] != 0 { + return "", fmt.Errorf("received invalid response packet") + } + return string(data[:len(data)-1]), nil +} + +// AuthServerNegotiateClearOrDialog will finish a negotiation based on +// the method type for the connection. Only supports +// MysqlClearPassword and MysqlDialog. +func AuthServerNegotiateClearOrDialog(c *Conn, method string) (string, error) { + switch method { + case MysqlClearPassword: + // The password is the next packet in plain text. + return AuthServerReadPacketString(c) + + case MysqlDialog: + // Send a question packet. + if err := AuthServerWritePacketString(c, "Enter Password:"); err != nil { + return "", err + } + + // Read the password as the response. + return AuthServerReadPacketString(c) + default: + return "", fmt.Errorf("unrecognized method: %v", method) + } +} diff --git a/go/mysqlconn/auth_server_none.go b/go/mysqlconn/auth_server_none.go index 3378e2fcbc..42262d17af 100644 --- a/go/mysqlconn/auth_server_none.go +++ b/go/mysqlconn/auth_server_none.go @@ -8,18 +8,18 @@ import ( // It's meant to be used for testing and prototyping. // With this config, you can connect to a local vtgate using // the following command line: 'mysql -P port -h ::'. -type AuthServerNone struct { - ClearText bool -} +// It only uses MysqlNativePassword method. +type AuthServerNone struct{} -// UseClearText reports clear text status -func (a *AuthServerNone) UseClearText() bool { - return a.ClearText +// AuthMethod is part of the AuthServer interface. +// We always return MysqlNativePassword. +func (a *AuthServerNone) AuthMethod(user string) (string, error) { + return MysqlNativePassword, nil } // Salt makes salt func (a *AuthServerNone) Salt() ([]byte, error) { - return make([]byte, 20), nil + return NewSalt() } // ValidateHash validates hash @@ -27,9 +27,10 @@ func (a *AuthServerNone) ValidateHash(salt []byte, user string, authResponse []b return &NoneGetter{}, nil } -// ValidateClearText validates clear text -func (a *AuthServerNone) ValidateClearText(user, password string) (Getter, error) { - return &NoneGetter{}, nil +// Negotiate is part of the AuthServer interface. +// It will never be called. +func (a *AuthServerNone) Negotiate(c *Conn, user string) (Getter, error) { + panic("Negotiate should not be called as AuthMethod returned mysql_native_password") } func init() { diff --git a/go/mysqlconn/auth_server_static.go b/go/mysqlconn/auth_server_static.go index ac9bf19f17..af6513342e 100644 --- a/go/mysqlconn/auth_server_static.go +++ b/go/mysqlconn/auth_server_static.go @@ -13,14 +13,18 @@ import ( ) var ( - mysqlAuthServerStaticFile = flag.String("mysql_auth_server_static_file", "", "JSON File to read the users/passwords from.") - mysqlAuthServerStaticString = flag.String("mysql_auth_server_static_string", "", "JSON representation of the users/passwords config.") + mysqlAuthServerStaticFile = flag.String("mysql_auth_server_static_file", "", "JSON File to read the users/passwords from.") + mysqlAuthServerStaticString = flag.String("mysql_auth_server_static_string", "", "JSON representation of the users/passwords config.") ) // AuthServerStatic implements AuthServer using a static configuration. type AuthServerStatic struct { - // ClearText can be set to force the use of ClearText auth. - ClearText bool + // Method can be set to: + // - MysqlNativePassword + // - MysqlClearPassword + // - MysqlDialog + // It defaults to MysqlNativePassword. + Method string // Entries contains the users, passwords and user data. Entries map[string]*AuthServerStaticEntry @@ -32,7 +36,7 @@ type AuthServerStaticEntry struct { UserData string } -// Init Handles initializing the AuthServerStatic if necessary. +// InitAuthServerStatic Handles initializing the AuthServerStatic if necessary. func InitAuthServerStatic() { // Check parameters. if *mysqlAuthServerStaticFile == "" && *mysqlAuthServerStaticString == "" { @@ -52,8 +56,8 @@ func InitAuthServerStatic() { // NewAuthServerStatic returns a new empty AuthServerStatic. func NewAuthServerStatic() *AuthServerStatic { return &AuthServerStatic{ - ClearText: false, - Entries: make(map[string]*AuthServerStaticEntry), + Method: MysqlNativePassword, + Entries: make(map[string]*AuthServerStaticEntry), } } @@ -81,14 +85,14 @@ func RegisterAuthServerStaticFromParams(file, str string) { RegisterAuthServerImpl("static", authServerStatic) } -// UseClearText is part of the AuthServer interface. -func (a *AuthServerStatic) UseClearText() bool { - return a.ClearText +// AuthMethod is part of the AuthServer interface. +func (a *AuthServerStatic) AuthMethod(user string) (string, error) { + return a.Method, nil } // Salt is part of the AuthServer interface. func (a *AuthServerStatic) Salt() ([]byte, error) { - return newSalt() + return NewSalt() } // ValidateHash is part of the AuthServer interface. @@ -108,8 +112,16 @@ func (a *AuthServerStatic) ValidateHash(salt []byte, user string, authResponse [ return &StaticUserData{entry.UserData}, nil } -// ValidateClearText is part of the AuthServer interface. -func (a *AuthServerStatic) ValidateClearText(user, password string) (Getter, error) { +// Negotiate is part of the AuthServer interface. +// It will be called if Method is anything else than MysqlNativePassword. +// We only recognize MysqlClearPassword and MysqlDialog here. +func (a *AuthServerStatic) Negotiate(c *Conn, user string) (Getter, error) { + // Finish the negotiation. + password, err := AuthServerNegotiateClearOrDialog(c, a.Method) + if err != nil { + return nil, err + } + // Find the entry. entry, ok := a.Entries[user] if !ok { diff --git a/go/mysqlconn/client.go b/go/mysqlconn/client.go index fe2305bb7c..7c6e5f910e 100644 --- a/go/mysqlconn/client.go +++ b/go/mysqlconn/client.go @@ -262,7 +262,7 @@ func (c *Conn) clientHandshake(characterSet uint8, params *sqldb.ConnParams) err if err != nil { return sqldb.NewSQLError(CRServerHandshakeErr, SSUnknownSQLState, "cannot parse auth switch request: %v", err) } - if pluginName != mysqlClearPassword { + if pluginName != MysqlClearPassword { return sqldb.NewSQLError(CRServerHandshakeErr, SSUnknownSQLState, "server asked for unsupported auth method: %v", pluginName) } @@ -437,8 +437,8 @@ func (c *Conn) parseInitialHandshakePacket(data []byte) (uint32, []byte, error) authPluginName = string(data[pos : len(data)-1]) } - if authPluginName != mysqlNativePassword { - return 0, nil, sqldb.NewSQLError(CRMalformedPacket, SSUnknownSQLState, "parseInitialHandshakePacket: only support %v auth plugin name, but got %v", mysqlNativePassword, authPluginName) + if authPluginName != MysqlNativePassword { + return 0, nil, sqldb.NewSQLError(CRMalformedPacket, SSUnknownSQLState, "parseInitialHandshakePacket: only support %v auth plugin name, but got %v", MysqlNativePassword, authPluginName) } } @@ -572,7 +572,7 @@ func (c *Conn) writeHandshakeResponse41(capabilities uint32, salt []byte, charac } // Assume native client during response - pos = writeNullString(data, pos, mysqlNativePassword) + pos = writeNullString(data, pos, MysqlNativePassword) // Sanity-check the length. if pos != len(data) { diff --git a/go/mysqlconn/constants.go b/go/mysqlconn/constants.go index 9afe9d4781..fa4c56d7e8 100644 --- a/go/mysqlconn/constants.go +++ b/go/mysqlconn/constants.go @@ -14,11 +14,15 @@ const ( // Supported auth forms. const ( - // mysqlNativePassword uses a salt and transmits a hash on the wire. - mysqlNativePassword = "mysql_native_password" + // MysqlNativePassword uses a salt and transmits a hash on the wire. + MysqlNativePassword = "mysql_native_password" - // mysqlClearPassword transmits the password in the clear. - mysqlClearPassword = "mysql_clear_password" + // MysqlClearPassword transmits the password in the clear. + MysqlClearPassword = "mysql_clear_password" + + // MysqlDialog uses the dialog plugin on the client side. + // It transmits data in the clear. + MysqlDialog = "dialog" ) // Capability flags. diff --git a/go/mysqlconn/doc.go b/go/mysqlconn/doc.go index 5720feb29c..d104e5fd11 100644 --- a/go/mysqlconn/doc.go +++ b/go/mysqlconn/doc.go @@ -46,17 +46,13 @@ Our client will expect the server to always use mysql_native_password in its initial handshake. This is what a real server always does, even though it's not technically mandatory. -Our server can then use the client's auth methods right away: -- mysql_native_password -- mysql_clear_password - -If our server's AuthServer UseClearText() returns true, and the -client's auth method is not mysql_clear_password, we will +The server's AuthServer plugin method AuthMethod() will then return +what auth method the server wants to use. If it is +mysql_native_password, and the client already returned the data, we +use it. Otherwise we switch the auth to what the server wants (by +sending an Authentication Method Switch Request packet) and re-negotiate. -If any of these methods doesn't work for the server, it will re-negotiate -by sending an Authentication Method Switch Request Packet. -The client will then handle that if it can. -- Maximum Packet Size: diff --git a/go/mysqlconn/handshake_test.go b/go/mysqlconn/handshake_test.go index d59f4d5139..cc42896456 100644 --- a/go/mysqlconn/handshake_test.go +++ b/go/mysqlconn/handshake_test.go @@ -20,7 +20,11 @@ import ( func TestClearTextClientAuth(t *testing.T) { th := &testHandler{} - authServer := &AuthServerNone{ClearText: true} + authServer := NewAuthServerStatic() + authServer.Method = MysqlClearPassword + authServer.Entries["user1"] = &AuthServerStaticEntry{ + Password: "password1", + } // Create the listener. l, err := NewListener("tcp", ":0", authServer, th) @@ -75,7 +79,10 @@ func TestClearTextClientAuth(t *testing.T) { func TestSSLConnection(t *testing.T) { th := &testHandler{} - authServer := &AuthServerNone{ClearText: false} + authServer := NewAuthServerStatic() + authServer.Entries["user1"] = &AuthServerStaticEntry{ + Password: "password1", + } // Create the listener, so we can get its host. l, err := NewListener("tcp", ":0", authServer, th) @@ -128,7 +135,7 @@ func TestSSLConnection(t *testing.T) { // Make sure clear text auth works over SSL. t.Run("ClearText", func(t *testing.T) { - authServer.ClearText = true + authServer.Method = MysqlClearPassword testSSLConnectionClearText(t, params) }) } diff --git a/go/mysqlconn/ldapauthserver/auth_server_ldap.go b/go/mysqlconn/ldapauthserver/auth_server_ldap.go index 2836ec0ddc..114fecbdee 100644 --- a/go/mysqlconn/ldapauthserver/auth_server_ldap.go +++ b/go/mysqlconn/ldapauthserver/auth_server_ldap.go @@ -19,12 +19,14 @@ import ( var ( ldapAuthConfigFile = flag.String("mysql_ldap_auth_config_file", "", "JSON File from which to read LDAP server config.") ldapAuthConfigString = flag.String("mysql_ldap_auth_config_string", "", "JSON representation of LDAP server config.") + ldapAuthMethod = flag.String("mysql_ldap_auth_method", mysqlconn.MysqlClearPassword, "client-side authentication method to use. Supported values: mysql_clear_password, dialog.") ) // AuthServerLdap implements AuthServer with an LDAP backend type AuthServerLdap struct { Client ServerConfig + Method string User string Password string GroupQuery string @@ -42,7 +44,14 @@ func Init() { log.Infof("Both mysql_ldap_auth_config_file and mysql_ldap_auth_config_string are non-empty, can only use one.") return } - ldapAuthServer := &AuthServerLdap{Client: &ClientImpl{}, ServerConfig: ServerConfig{}} + if *ldapAuthMethod != mysqlconn.MysqlClearPassword && *ldapAuthMethod != mysqlconn.MysqlDialog { + log.Fatalf("Invalid mysql_ldap_auth_method value: only support mysql_clear_password or dialog") + } + ldapAuthServer := &AuthServerLdap{ + Client: &ClientImpl{}, + ServerConfig: ServerConfig{}, + Method: *ldapAuthMethod, + } data := []byte(*ldapAuthConfigString) if *ldapAuthConfigFile != "" { @@ -58,32 +67,37 @@ func Init() { mysqlconn.RegisterAuthServerImpl("ldap", ldapAuthServer) } -// UseClearText is always true for AuthServerLdap -func (asl *AuthServerLdap) UseClearText() bool { - return true +// AuthMethod is part of the AuthServer interface. +func (asl *AuthServerLdap) AuthMethod(user string) (string, error) { + return asl.Method, nil } -// Salt is unimplemented for AuthServerLdap +// Salt will be unused in AuthServerLdap. func (asl *AuthServerLdap) Salt() ([]byte, error) { - panic("unimplemented") + return mysqlconn.NewSalt() } -// ValidateHash is unimplemented for AuthServerLdap +// ValidateHash is unimplemented for AuthServerLdap. func (asl *AuthServerLdap) ValidateHash(salt []byte, user string, authResponse []byte) (mysqlconn.Getter, error) { panic("unimplemented") } -// ValidateClearText connects to the LDAP server over TLS -// and attempts to bind as that user with the supplied password. -// It returns the supplied username. -func (asl *AuthServerLdap) ValidateClearText(username, password string) (mysqlconn.Getter, error) { - err := asl.Client.Connect("tcp", &asl.ServerConfig) +// Negotiate is part of the AuthServer interface. +func (asl *AuthServerLdap) Negotiate(c *mysqlconn.Conn, user string) (mysqlconn.Getter, error) { + // Finish the negotiation. + password, err := mysqlconn.AuthServerNegotiateClearOrDialog(c, asl.Method) if err != nil { return nil, err } + return asl.validate(user, password) +} + +func (asl *AuthServerLdap) validate(username, password string) (mysqlconn.Getter, error) { + if err := asl.Client.Connect("tcp", &asl.ServerConfig); err != nil { + return nil, err + } defer asl.Client.Close() - err = asl.Client.Bind(fmt.Sprintf(asl.UserDnPattern, username), password) - if err != nil { + if err := asl.Client.Bind(fmt.Sprintf(asl.UserDnPattern, username), password); err != nil { return nil, err } groups, err := asl.getGroups(username) diff --git a/go/mysqlconn/ldapauthserver/auth_server_ldap_test.go b/go/mysqlconn/ldapauthserver/auth_server_ldap_test.go index 9f2ebdf562..5c8dead26d 100644 --- a/go/mysqlconn/ldapauthserver/auth_server_ldap_test.go +++ b/go/mysqlconn/ldapauthserver/auth_server_ldap_test.go @@ -22,13 +22,18 @@ func (mlc *MockLdapClient) Search(searchRequest *ldap.SearchRequest) (*ldap.Sear } func TestValidateClearText(t *testing.T) { - asl := &AuthServerLdap{Client: &MockLdapClient{}, UserDnPattern: "%s", User: "testuser", Password: "testpass"} - _, err := asl.ValidateClearText("testuser", "testpass") + asl := &AuthServerLdap{ + Client: &MockLdapClient{}, + User: "testuser", + Password: "testpass", + UserDnPattern: "%s", + } + _, err := asl.validate("testuser", "testpass") if err != nil { t.Fatalf("AuthServerLdap failed to validate valid credentials. Got: %v", err) } - _, err = asl.ValidateClearText("invaliduser", "invalidpass") + _, err = asl.validate("invaliduser", "invalidpass") if err == nil { t.Fatalf("AuthServerLdap validated invalid credentials.") } diff --git a/go/mysqlconn/query_benchmark_test.go b/go/mysqlconn/query_benchmark_test.go index 0ebaf2c9bc..537c8db922 100644 --- a/go/mysqlconn/query_benchmark_test.go +++ b/go/mysqlconn/query_benchmark_test.go @@ -128,7 +128,7 @@ func benchmarkOldParallelReads(b *testing.B, params sqldb.ConnParams, parallelCo func BenchmarkParallelShortQueries(b *testing.B) { th := &testHandler{} - authServer := &AuthServerNone{ClearText: false} + authServer := &AuthServerNone{} l, err := NewListener("tcp", ":0", authServer, th) if err != nil { diff --git a/go/mysqlconn/server.go b/go/mysqlconn/server.go index 805a3510d8..bfe323876a 100644 --- a/go/mysqlconn/server.go +++ b/go/mysqlconn/server.go @@ -168,72 +168,53 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32) { } } - // See what method the client used. - renegotiateWithClearText := false - switch authMethod { - case mysqlNativePassword: - // This is what the server started with. Let's use it if we can. - if !l.authServer.UseClearText() { - userData, err := l.authServer.ValidateHash(salt, user, authResponse) - if err != nil { - c.writeErrorPacketFromError(err) - return - } - c.User = user - c.UserData = userData - // We're good. - break - } + // See what auth method the AuthServer wants to use for that user. + authServerMethod, err := l.authServer.AuthMethod(user) + if err != nil { + c.writeErrorPacketFromError(err) + return + } - // Our AuthServer cannot use mysql_native_password, it - // needs the real password. Let's request that. - renegotiateWithClearText = true - case mysqlClearPassword: - // Client sent us a clear text password. Let's use it if we can. - if !l.AllowClearTextWithoutTLS && c.Capabilities&CapabilityClientSSL == 0 { - c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Cannot use clear text authentication over non-SSL connections.") - return - } - userData, err := l.authServer.ValidateClearText(user, string(authResponse)) + // Compare with what the client sent back. + switch { + case authServerMethod == MysqlNativePassword && authMethod == MysqlNativePassword: + // Both server and client want to use MysqlNativePassword: + // the negotiation can be completed right away, using the + // ValidateHash() method. + userData, err := l.authServer.ValidateHash(salt, user, authResponse) if err != nil { c.writeErrorPacketFromError(err) return } c.User = user c.UserData = userData - break - default: - // Client decided to use something we don't understand. - // Let's try again with clear text password. - renegotiateWithClearText = true - } - // If we need to re-negotiate with clear text, do it. - if renegotiateWithClearText { - // Check error conditions. + case authServerMethod == MysqlNativePassword: + // The server really wants to use MysqlNativePassword, + // but the client returned a result for something else: + // not sure this can happen, so not supporting this now. + c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Client asked for auth %v, but server wants auth mysql_native_password", authMethod) + return + + default: + // The server wants to use something else, re-negotiate. + + // The negotiation happens in clear text. Let's check we can. if !l.AllowClearTextWithoutTLS && c.Capabilities&CapabilityClientSSL == 0 { c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Cannot use clear text authentication over non-SSL connections.") return } - if err := c.writeAuthSwitchRequest(mysqlClearPassword, nil); err != nil { + // Switch our auth method to what the server wants. + // Note: for now don't support the extra data. + if err := c.writeAuthSwitchRequest(authServerMethod, nil); err != nil { log.Errorf("Error write auth switch packet for client %v: %v", c.ConnectionID, err) return } - // The client is supposed to just send the data in a single packet. - // It is a zero-terminated string. - data, err := c.readEphemeralPacket() - if err != nil { - log.Warningf("Error reading auth switch response packet from client %v: %v", c.ConnectionID, err) - return - } - password, pos, ok := readNullString(data, 0) - if !ok || pos != len(data) { - c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Error parsing packet with password: %v", data) - return - } - userData, err := l.authServer.ValidateClearText(user, password) + // Then hand over the rest of the negotiation to the + // auth server. + userData, err := l.authServer.Negotiate(c, user) if err != nil { c.writeErrorPacketFromError(err) return @@ -242,7 +223,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32) { c.UserData = userData } - // Send an OK packet. + // Negotiation worked, send OK packet. if err := c.writeOKPacket(0, 0, c.StatusFlags, 0); err != nil { log.Errorf("Cannot write OK packet: %v", err) return @@ -332,7 +313,7 @@ func (c *Conn) writeHandshakeV10(serverVersion string, authServer AuthServer, en 1 + // length of auth plugin data 10 + // reserved (0) 13 + // auth-plugin-data - lenNullString(mysqlNativePassword) // auth-plugin-name + lenNullString(MysqlNativePassword) // auth-plugin-name data := c.startEphemeralPacket(length) pos := 0 @@ -346,17 +327,8 @@ func (c *Conn) writeHandshakeV10(serverVersion string, authServer AuthServer, en // Add connectionID in. pos = writeUint32(data, pos, c.ConnectionID) - // Generate the salt if needed, put 8 bytes in. - var salt []byte - var err error - if authServer.UseClearText() { - // salt will end up being unused, but we can't send - // just zero, as the client will still use it, and - // that may leak crypto information. - salt, err = newSalt() - } else { - salt, err = authServer.Salt() - } + // Generate the salt, put 8 bytes in. + salt, err := authServer.Salt() if err != nil { return nil, err } @@ -391,7 +363,7 @@ func (c *Conn) writeHandshakeV10(serverVersion string, authServer AuthServer, en pos++ // Copy authPluginName. We always start with mysql_native_password. - pos = writeNullString(data, pos, mysqlNativePassword) + pos = writeNullString(data, pos, MysqlNativePassword) // Sanity check. if pos != len(data) { @@ -504,7 +476,7 @@ func (l *Listener) parseClientHandshakePacket(c *Conn, firstTime bool, data []by } // authMethod (with default) - authMethod := mysqlNativePassword + authMethod := MysqlNativePassword if clientFlags&CapabilityClientPluginAuth != 0 { authMethod, pos, ok = readNullString(data, pos) if !ok { diff --git a/go/mysqlconn/server_test.go b/go/mysqlconn/server_test.go index 3b52aa9f47..455769bcf9 100644 --- a/go/mysqlconn/server_test.go +++ b/go/mysqlconn/server_test.go @@ -276,7 +276,7 @@ func TestClearTextServer(t *testing.T) { Password: "password1", UserData: "userData1", } - authServer.ClearText = true + authServer.Method = MysqlClearPassword l, err := NewListener("tcp", ":0", authServer, th) if err != nil { t.Fatalf("NewListener failed: %v", err)