vitess.io/vitess@v0.16.2/go/mysql/auth_server.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mysql
    18  
    19  import (
    20  	"crypto/rand"
    21  	"crypto/rsa"
    22  	"crypto/sha1"
    23  	"crypto/sha256"
    24  	"crypto/subtle"
    25  	"encoding/hex"
    26  	"net"
    27  	"sync"
    28  
    29  	"vitess.io/vitess/go/vt/log"
    30  	"vitess.io/vitess/go/vt/proto/vtrpc"
    31  	"vitess.io/vitess/go/vt/vterrors"
    32  )
    33  
    34  // AuthServer is the interface that servers must implement to validate
    35  // users and passwords. It needs to be able to return a list of AuthMethod
    36  // interfaces which implement the supported auth methods for the server.
    37  type AuthServer interface {
    38  	// AuthMethods returns a list of auth methods that are part of this
    39  	// interface. Building an authentication server usually means
    40  	// creating AuthMethod instances with the known helpers for the
    41  	// currently supported AuthMethod implementations.
    42  	//
    43  	// When a client connects, the server checks the list of auth methods
    44  	// available. If an auth method for the requested auth mechanism by the
    45  	// client is available, it will be used immediately.
    46  	//
    47  	// If there is no overlap between the provided auth methods and
    48  	// the one the client requests, the server will send back an
    49  	// auth switch request using the first provided AuthMethod in this list.
    50  	AuthMethods() []AuthMethod
    51  
    52  	// DefaultAuthMethodDescription returns the auth method that the auth server
    53  	// sends during the initial server handshake. This needs to be either
    54  	// `mysql_native_password` or `caching_sha2_password` as those are the only
    55  	// supported auth methods during the initial handshake.
    56  	//
    57  	// It's not needed to also support those methods in the AuthMethods(),
    58  	// in fact, if you want to only support for example clear text passwords,
    59  	// you must still return `mysql_native_password` or `caching_sha2_password`
    60  	// here and the auth switch protocol will be used to switch to clear text.
    61  	DefaultAuthMethodDescription() AuthMethodDescription
    62  }
    63  
    64  // AuthMethod interface for concrete auth method implementations.
    65  // When building an auth server, you usually don't implement these yourself
    66  // but the helper methods to build AuthMethod instances should be used.
    67  type AuthMethod interface {
    68  	// Name returns the auth method description for this implementation.
    69  	// This is the name that is sent as the auth plugin name during the
    70  	// Mysql authentication protocol handshake.
    71  	Name() AuthMethodDescription
    72  
    73  	// HandleUser verifies if the current auth method can authenticate
    74  	// the given user with the current auth method. This can be useful
    75  	// for example if you only have a plain text of hashed password
    76  	// for specific users and not all users and auth method support
    77  	// depends on what you have.
    78  	HandleUser(conn *Conn, user string) bool
    79  
    80  	// AllowClearTextWithoutTLS identifies if an auth method is allowed
    81  	// on a plain text connection. This check is only enforced
    82  	// if the listener has AllowClearTextWithoutTLS() disabled.
    83  	AllowClearTextWithoutTLS() bool
    84  
    85  	// AuthPluginData generates the information for the auth plugin.
    86  	// This is included in for example the auth switch request. This
    87  	// is auth plugin specific and opaque to the Mysql handshake
    88  	// protocol.
    89  	AuthPluginData() ([]byte, error)
    90  
    91  	// HandleAuthPluginData handles the returned auth plugin data from
    92  	// the client. The original data the server sent is also included
    93  	// which can include things like the salt for `mysql_native_password`.
    94  	//
    95  	// The remote address is provided for plugins that also want to
    96  	// do additional checks like IP based restrictions.
    97  	HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error)
    98  }
    99  
   100  // UserValidator is an interface that allows checking if a specific
   101  // user will work for an auth method. This interface is called by
   102  // all the default helpers that create AuthMethod instances for
   103  // the various supported Mysql authentication methods.
   104  type UserValidator interface {
   105  	HandleUser(user string) bool
   106  }
   107  
   108  // CacheState is a state that is returned by the UserEntryWithCacheHash
   109  // method from the CachingStorage interface. This state is needed to indicate
   110  // whether the authentication is accepted, rejected by the cache itself
   111  // or if the cache can't fullfill the request. In that case it indicates
   112  // that with AuthNeedMoreData.
   113  type CacheState int
   114  
   115  const (
   116  	// AuthRejected is used when the cache knows the request can be rejected.
   117  	AuthRejected CacheState = iota
   118  	// AuthAccepted is used when the cache knows the request can be accepted.
   119  	AuthAccepted
   120  	// AuthNeedMoreData is used when the cache doesn't know the answer and more data is needed.
   121  	AuthNeedMoreData
   122  )
   123  
   124  // HashStorage describes an object that is suitable to retrieve user information
   125  // based on the hashed authentication response for mysql_native_password.
   126  //
   127  // In general, an implementation of this would use an internally stored password
   128  // that is hashed twice with SHA1.
   129  //
   130  // The VerifyHashedMysqlNativePassword helper method can be used to verify
   131  // such a hash based on the salt and auth response provided here after retrieving
   132  // the hashed password from the storage.
   133  type HashStorage interface {
   134  	UserEntryWithHash(conn *Conn, salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error)
   135  }
   136  
   137  // PlainTextStorage describes an object that is suitable to retrieve user information
   138  // based on the plain text password of a user. This can be obtained through various
   139  // Mysql authentication methods, such as `mysql_clear_passwrd`, `dialog` or
   140  // `caching_sha2_password` in the full authentication handshake case of the latter.
   141  //
   142  // This mechanism also would allow for picking your own password storage in the backend,
   143  // such as BCrypt, SCrypt, PBKDF2 or Argon2 once the plain text is obtained.
   144  //
   145  // When comparing plain text passwords directly, please ensure to use `subtle.ConstantTimeCompare`
   146  // to prevent timing based attacks on the password.
   147  type PlainTextStorage interface {
   148  	UserEntryWithPassword(conn *Conn, user string, password string, remoteAddr net.Addr) (Getter, error)
   149  }
   150  
   151  // CachingStorage describes an object that is suitable to retrieve user information
   152  // based on a hashed value of the password. This applies to the `caching_sha2_password`
   153  // authentication method.
   154  //
   155  // The cache would hash the password internally as `SHA256(SHA256(password))`.
   156  //
   157  // The VerifyHashedCachingSha2Password helper method can be used to verify
   158  // such a hash based on the salt and auth response provided here after retrieving
   159  // the hashed password from the cache.
   160  type CachingStorage interface {
   161  	UserEntryWithCacheHash(conn *Conn, salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, CacheState, error)
   162  }
   163  
   164  // NewMysqlNativeAuthMethod will create a new AuthMethod that implements the
   165  // `mysql_native_password` handshake. The caller will need to provide a storage
   166  // object and validator that will be called during the handshake phase.
   167  func NewMysqlNativeAuthMethod(layer HashStorage, validator UserValidator) AuthMethod {
   168  	authMethod := mysqlNativePasswordAuthMethod{
   169  		storage:   layer,
   170  		validator: validator,
   171  	}
   172  	return &authMethod
   173  }
   174  
   175  // NewMysqlClearAuthMethod will create a new AuthMethod that implements the
   176  // `mysql_clear_password` handshake. The caller will need to provide a storage
   177  // object for plain text passwords and validator that will be called during the
   178  // handshake phase.
   179  func NewMysqlClearAuthMethod(layer PlainTextStorage, validator UserValidator) AuthMethod {
   180  	authMethod := mysqlClearAuthMethod{
   181  		storage:   layer,
   182  		validator: validator,
   183  	}
   184  	return &authMethod
   185  }
   186  
   187  // Constants for the dialog plugin.
   188  const (
   189  	// Default message if no custom message
   190  	// is configured. This is used when the message
   191  	// is the empty string.
   192  	mysqlDialogDefaultMessage = "Enter password: "
   193  
   194  	// Dialog plugin is similar to clear text, but can respond to multiple
   195  	// prompts in a row. This is not yet implemented.
   196  	// Follow questions should be prepended with a `cmd` byte:
   197  	// 0x02 - ordinary question
   198  	// 0x03 - last question
   199  	// 0x04 - password question
   200  	// 0x05 - last password
   201  	mysqlDialogAskPassword = 0x04
   202  )
   203  
   204  // NewMysqlDialogAuthMethod will create a new AuthMethod that implements the
   205  // `dialog` handshake. The caller will need to provide a storage object for plain
   206  // text passwords and validator that will be called during the handshake phase.
   207  // The message given will be sent as part of the dialog. If the empty string is
   208  // provided, the default message of "Enter password: " will be used.
   209  func NewMysqlDialogAuthMethod(layer PlainTextStorage, validator UserValidator, msg string) AuthMethod {
   210  	if msg == "" {
   211  		msg = mysqlDialogDefaultMessage
   212  	}
   213  
   214  	authMethod := mysqlDialogAuthMethod{
   215  		storage:   layer,
   216  		validator: validator,
   217  		msg:       msg,
   218  	}
   219  	return &authMethod
   220  }
   221  
   222  // NewSha2CachingAuthMethod will create a new AuthMethod that implements the
   223  // `caching_sha2_password` handshake. The caller will need to provide a cache
   224  // object for the fast auth path and a plain text storage object that will
   225  // be called if the return of the first layer indicates the full auth dance is
   226  // needed.
   227  //
   228  // Right now we only support caching_sha2_password over TLS or a Unix socket.
   229  //
   230  // If TLS is not enabled, the client needs to encrypt it with the public
   231  // key of the server. In that case, Vitess is already configured with
   232  // a certificate anyway, so we recommend to use TLS if you want to use
   233  // caching_sha2_password in that case instead of allowing the plain
   234  // text fallback path here.
   235  //
   236  // This might change in the future if there's a good argument and implementation
   237  // for allowing the plain text path here as well.
   238  func NewSha2CachingAuthMethod(layer1 CachingStorage, layer2 PlainTextStorage, validator UserValidator) AuthMethod {
   239  	authMethod := mysqlCachingSha2AuthMethod{
   240  		cache:     layer1,
   241  		storage:   layer2,
   242  		validator: validator,
   243  	}
   244  	return &authMethod
   245  }
   246  
   247  // ScrambleMysqlNativePassword computes the hash of the password using 4.1+ method.
   248  //
   249  // This can be used for example inside a `mysql_native_password` plugin implementation
   250  // if the backend storage implements storage of plain text passwords.
   251  func ScrambleMysqlNativePassword(salt, password []byte) []byte {
   252  	if len(password) == 0 {
   253  		return nil
   254  	}
   255  
   256  	// stage1Hash = SHA1(password)
   257  	crypt := sha1.New()
   258  	crypt.Write(password)
   259  	stage1 := crypt.Sum(nil)
   260  
   261  	// scrambleHash = SHA1(salt + SHA1(stage1Hash))
   262  	// inner Hash
   263  	crypt.Reset()
   264  	crypt.Write(stage1)
   265  	hash := crypt.Sum(nil)
   266  	// outer Hash
   267  	crypt.Reset()
   268  	crypt.Write(salt)
   269  	crypt.Write(hash)
   270  	scramble := crypt.Sum(nil)
   271  
   272  	// token = scrambleHash XOR stage1Hash
   273  	for i := range scramble {
   274  		scramble[i] ^= stage1[i]
   275  	}
   276  	return scramble
   277  }
   278  
   279  // DecodeMysqlNativePasswordHex decodes the standard format used by MySQL
   280  // for 4.1 style password hashes. It drops the optionally leading * before
   281  // decoding the rest as a hex encoded string.
   282  func DecodeMysqlNativePasswordHex(hexEncodedPassword string) ([]byte, error) {
   283  	if hexEncodedPassword[0] == '*' {
   284  		hexEncodedPassword = hexEncodedPassword[1:]
   285  	}
   286  	return hex.DecodeString(hexEncodedPassword)
   287  }
   288  
   289  // VerifyHashedMysqlNativePassword verifies a client reply against a stored hash.
   290  //
   291  // This can be used for example inside a `mysql_native_password` plugin implementation
   292  // if the backend storage where the stored password is a SHA1(SHA1(password)).
   293  //
   294  // All values here are non encoded byte slices, so if you store for example the double
   295  // SHA1 of the password as hex encoded characters, you need to decode that first.
   296  // See DecodeMysqlNativePasswordHex for a decoding helper for the standard encoding
   297  // format of this hash used by MySQL.
   298  func VerifyHashedMysqlNativePassword(reply, salt, hashedNativePassword []byte) bool {
   299  	if len(reply) == 0 || len(hashedNativePassword) == 0 {
   300  		return false
   301  	}
   302  
   303  	// scramble = SHA1(salt+hash)
   304  	crypt := sha1.New()
   305  	crypt.Write(salt)
   306  	crypt.Write(hashedNativePassword)
   307  	scramble := crypt.Sum(nil)
   308  
   309  	for i := range scramble {
   310  		scramble[i] ^= reply[i]
   311  	}
   312  	hashStage1 := scramble
   313  
   314  	crypt.Reset()
   315  	crypt.Write(hashStage1)
   316  	candidateHash2 := crypt.Sum(nil)
   317  
   318  	return subtle.ConstantTimeCompare(candidateHash2, hashedNativePassword) == 1
   319  }
   320  
   321  // VerifyHashedCachingSha2Password verifies a client reply against a stored hash.
   322  //
   323  // This can be used for example inside a `caching_sha2_password` plugin implementation
   324  // if the cache storage uses password keys with SHA256(SHA256(password)).
   325  //
   326  // All values here are non encoded byte slices, so if you store for example the double
   327  // SHA256 of the password as hex encoded characters, you need to decode that first.
   328  func VerifyHashedCachingSha2Password(reply, salt, hashedCachingSha2Password []byte) bool {
   329  	if len(reply) == 0 || len(hashedCachingSha2Password) == 0 {
   330  		return false
   331  	}
   332  
   333  	crypt := sha256.New()
   334  	crypt.Write(hashedCachingSha2Password)
   335  	crypt.Write(salt)
   336  	scramble := crypt.Sum(nil)
   337  
   338  	// token = scramble XOR stage1Hash
   339  	for i := range scramble {
   340  		scramble[i] ^= reply[i]
   341  	}
   342  	hashStage1 := scramble
   343  
   344  	crypt.Reset()
   345  	crypt.Write(hashStage1)
   346  	candidateHash2 := crypt.Sum(nil)
   347  
   348  	return subtle.ConstantTimeCompare(candidateHash2, hashedCachingSha2Password) == 1
   349  }
   350  
   351  // ScrambleCachingSha2Password computes the hash of the password using SHA256 as required by
   352  // caching_sha2_password plugin for "fast" authentication
   353  func ScrambleCachingSha2Password(salt []byte, password []byte) []byte {
   354  	if len(password) == 0 {
   355  		return nil
   356  	}
   357  
   358  	// stage1Hash = SHA256(password)
   359  	crypt := sha256.New()
   360  	crypt.Write(password)
   361  	stage1 := crypt.Sum(nil)
   362  
   363  	// scrambleHash = SHA256(SHA256(stage1Hash) + salt)
   364  	crypt.Reset()
   365  	crypt.Write(stage1)
   366  	innerHash := crypt.Sum(nil)
   367  
   368  	crypt.Reset()
   369  	crypt.Write(innerHash)
   370  	crypt.Write(salt)
   371  	scramble := crypt.Sum(nil)
   372  
   373  	// token = stage1Hash XOR scrambleHash
   374  	for i := range stage1 {
   375  		stage1[i] ^= scramble[i]
   376  	}
   377  
   378  	return stage1
   379  }
   380  
   381  // EncryptPasswordWithPublicKey obfuscates the password and encrypts it with server's public key as required by
   382  // caching_sha2_password plugin for "full" authentication
   383  func EncryptPasswordWithPublicKey(salt []byte, password []byte, pub *rsa.PublicKey) ([]byte, error) {
   384  	if len(password) == 0 {
   385  		return nil, nil
   386  	}
   387  
   388  	buffer := make([]byte, len(password)+1)
   389  	copy(buffer, password)
   390  	for i := range buffer {
   391  		buffer[i] ^= salt[i%len(salt)]
   392  	}
   393  
   394  	sha1Hash := sha1.New()
   395  	enc, err := rsa.EncryptOAEP(sha1Hash, rand.Reader, pub, buffer, nil)
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  
   400  	return enc, nil
   401  }
   402  
   403  type mysqlNativePasswordAuthMethod struct {
   404  	storage   HashStorage
   405  	validator UserValidator
   406  }
   407  
   408  func (n *mysqlNativePasswordAuthMethod) Name() AuthMethodDescription {
   409  	return MysqlNativePassword
   410  }
   411  
   412  func (n *mysqlNativePasswordAuthMethod) HandleUser(conn *Conn, user string) bool {
   413  	return n.validator.HandleUser(user)
   414  }
   415  
   416  func (n *mysqlNativePasswordAuthMethod) AuthPluginData() ([]byte, error) {
   417  	salt, err := newSalt()
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  	return append(salt, 0), nil
   422  }
   423  
   424  func (n *mysqlNativePasswordAuthMethod) AllowClearTextWithoutTLS() bool {
   425  	return true
   426  }
   427  
   428  func (n *mysqlNativePasswordAuthMethod) HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) {
   429  	if serverAuthPluginData[len(serverAuthPluginData)-1] != 0x00 {
   430  		return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
   431  	}
   432  
   433  	salt := serverAuthPluginData[:len(serverAuthPluginData)-1]
   434  	return n.storage.UserEntryWithHash(conn, salt, user, clientAuthPluginData, remoteAddr)
   435  }
   436  
   437  type mysqlClearAuthMethod struct {
   438  	storage   PlainTextStorage
   439  	validator UserValidator
   440  }
   441  
   442  func (n *mysqlClearAuthMethod) Name() AuthMethodDescription {
   443  	return MysqlClearPassword
   444  }
   445  
   446  func (n *mysqlClearAuthMethod) HandleUser(conn *Conn, user string) bool {
   447  	return n.validator.HandleUser(user)
   448  }
   449  
   450  func (n *mysqlClearAuthMethod) AuthPluginData() ([]byte, error) {
   451  	return nil, nil
   452  }
   453  
   454  func (n *mysqlClearAuthMethod) AllowClearTextWithoutTLS() bool {
   455  	return false
   456  }
   457  
   458  func (n *mysqlClearAuthMethod) HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) {
   459  	return n.storage.UserEntryWithPassword(conn, user, string(clientAuthPluginData[:len(clientAuthPluginData)-1]), remoteAddr)
   460  }
   461  
   462  type mysqlDialogAuthMethod struct {
   463  	storage   PlainTextStorage
   464  	validator UserValidator
   465  	msg       string
   466  }
   467  
   468  func (n *mysqlDialogAuthMethod) Name() AuthMethodDescription {
   469  	return MysqlDialog
   470  }
   471  
   472  func (n *mysqlDialogAuthMethod) HandleUser(conn *Conn, user string) bool {
   473  	return n.validator.HandleUser(user)
   474  }
   475  
   476  func (n *mysqlDialogAuthMethod) AuthPluginData() ([]byte, error) {
   477  	result := make([]byte, len(n.msg)+2)
   478  	result[0] = mysqlDialogAskPassword
   479  	writeNullString(result, 1, n.msg)
   480  	return result, nil
   481  }
   482  
   483  func (n *mysqlDialogAuthMethod) HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) {
   484  	return n.storage.UserEntryWithPassword(conn, user, string(clientAuthPluginData[:len(clientAuthPluginData)-1]), remoteAddr)
   485  }
   486  
   487  func (n *mysqlDialogAuthMethod) AllowClearTextWithoutTLS() bool {
   488  	return false
   489  }
   490  
   491  type mysqlCachingSha2AuthMethod struct {
   492  	cache     CachingStorage
   493  	storage   PlainTextStorage
   494  	validator UserValidator
   495  }
   496  
   497  func (n *mysqlCachingSha2AuthMethod) Name() AuthMethodDescription {
   498  	return CachingSha2Password
   499  }
   500  
   501  func (n *mysqlCachingSha2AuthMethod) HandleUser(conn *Conn, user string) bool {
   502  	if !conn.TLSEnabled() && !conn.IsUnixSocket() {
   503  		return false
   504  	}
   505  	return n.validator.HandleUser(user)
   506  }
   507  
   508  func (n *mysqlCachingSha2AuthMethod) AuthPluginData() ([]byte, error) {
   509  	salt, err := newSalt()
   510  	if err != nil {
   511  		return nil, err
   512  	}
   513  	return append(salt, 0), nil
   514  }
   515  
   516  func (n *mysqlCachingSha2AuthMethod) AllowClearTextWithoutTLS() bool {
   517  	return true
   518  }
   519  
   520  func (n *mysqlCachingSha2AuthMethod) HandleAuthPluginData(c *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) {
   521  	if serverAuthPluginData[len(serverAuthPluginData)-1] != 0x00 {
   522  		return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
   523  	}
   524  
   525  	salt := serverAuthPluginData[:len(serverAuthPluginData)-1]
   526  	result, cacheState, err := n.cache.UserEntryWithCacheHash(c, salt, user, clientAuthPluginData, remoteAddr)
   527  
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  
   532  	switch cacheState {
   533  	case AuthRejected:
   534  		return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
   535  	case AuthAccepted:
   536  		// We need to write a more data packet to indicate the
   537  		// handshake completed properly. This  will be followed
   538  		// by a regular OK packet which the caller of this method will send.
   539  		data, pos := c.startEphemeralPacketWithHeader(2)
   540  		pos = writeByte(data, pos, AuthMoreDataPacket)
   541  		_ = writeByte(data, pos, CachingSha2FastAuth)
   542  		err = c.writeEphemeralPacket()
   543  		if err != nil {
   544  			return nil, err
   545  		}
   546  		return result, nil
   547  	case AuthNeedMoreData:
   548  		if !c.TLSEnabled() && !c.IsUnixSocket() {
   549  			return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
   550  		}
   551  
   552  		data, pos := c.startEphemeralPacketWithHeader(2)
   553  		pos = writeByte(data, pos, AuthMoreDataPacket)
   554  		writeByte(data, pos, CachingSha2FullAuth)
   555  		c.writeEphemeralPacket()
   556  
   557  		password, err := readPacketPasswordString(c)
   558  		if err != nil {
   559  			return nil, err
   560  		}
   561  
   562  		return n.storage.UserEntryWithPassword(c, user, password, remoteAddr)
   563  	default:
   564  		// Somehow someone returned an unknown state, let's error with access denied.
   565  		return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
   566  	}
   567  }
   568  
   569  // authServers is a registry of AuthServer implementations.
   570  var authServers = make(map[string]AuthServer)
   571  
   572  // mu is used to lock access to authServers
   573  var mu sync.Mutex
   574  
   575  // RegisterAuthServer registers an implementations of AuthServer.
   576  func RegisterAuthServer(name string, authServer AuthServer) {
   577  	mu.Lock()
   578  	defer mu.Unlock()
   579  	if _, ok := authServers[name]; ok {
   580  		log.Fatalf("AuthServer named %v already exists", name)
   581  	}
   582  	authServers[name] = authServer
   583  }
   584  
   585  // GetAuthServer returns an AuthServer by name, or log.Exitf.
   586  func GetAuthServer(name string) AuthServer {
   587  	mu.Lock()
   588  	defer mu.Unlock()
   589  	authServer, ok := authServers[name]
   590  	if !ok {
   591  		log.Exitf("no AuthServer name %v registered", name)
   592  	}
   593  	return authServer
   594  }
   595  
   596  func newSalt() ([]byte, error) {
   597  	salt := make([]byte, 20)
   598  	if _, err := rand.Read(salt); err != nil {
   599  		return nil, err
   600  	}
   601  
   602  	// Salt must be a legal UTF8 string.
   603  	for i := 0; i < len(salt); i++ {
   604  		salt[i] &= 0x7f
   605  		if salt[i] == '\x00' || salt[i] == '$' {
   606  			salt[i]++
   607  		}
   608  	}
   609  
   610  	return salt, nil
   611  }
   612  
   613  func negotiateAuthMethod(conn *Conn, as AuthServer, user string, requestedAuth AuthMethodDescription) (AuthMethod, error) {
   614  	for _, m := range as.AuthMethods() {
   615  		if m.Name() == requestedAuth && m.HandleUser(conn, user) {
   616  			return m, nil
   617  		}
   618  	}
   619  	return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "unknown auth method requested: %s", string(requestedAuth))
   620  }
   621  
   622  func readPacketPasswordString(c *Conn) (string, error) {
   623  	// Read a packet, the password is the payload, as a
   624  	// zero terminated string.
   625  	data, err := c.ReadPacket()
   626  	if err != nil {
   627  		return "", err
   628  	}
   629  	if len(data) == 0 || data[len(data)-1] != 0 {
   630  		return "", vterrors.Errorf(vtrpc.Code_INTERNAL, "received invalid response packet, datalen=%v", len(data))
   631  	}
   632  	return string(data[:len(data)-1]), nil
   633  }