139 lines
3.3 KiB
Go
139 lines
3.3 KiB
Go
package mysqlserver
|
|
|
|
import "crypto/rand"
|
|
|
|
func buildHandshake(connID uint32, scramble []byte) []byte {
|
|
if len(scramble) < 20 {
|
|
s := make([]byte, 20)
|
|
copy(s, scramble)
|
|
scramble = s
|
|
}
|
|
b := []byte{ProtocolVersion}
|
|
b = appendNUL(b, ServerVersion)
|
|
b = appendU32(b, connID)
|
|
b = append(b, scramble[:8]...)
|
|
b = append(b, 0x00)
|
|
b = appendU16(b, uint16(serverCapabilities))
|
|
b = append(b, charsetUTF8MB4)
|
|
b = appendU16(b, serverStatusAutocommit)
|
|
b = appendU16(b, uint16(serverCapabilities>>16))
|
|
b = append(b, 21) // auth plugin data len: 20 bytes + NUL
|
|
b = append(b, make([]byte, 10)...)
|
|
b = append(b, scramble[8:20]...)
|
|
b = append(b, 0x00)
|
|
b = appendNUL(b, AuthPluginNative)
|
|
return b
|
|
}
|
|
|
|
func randomScramble() ([]byte, error) {
|
|
s := make([]byte, 20)
|
|
if _, err := rand.Read(s); err != nil {
|
|
return nil, err
|
|
}
|
|
for i, c := range s {
|
|
if c == 0 {
|
|
s[i] = 1
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
type handshakeResponse struct {
|
|
Capabilities uint32
|
|
MaxPacket uint32
|
|
Charset byte
|
|
Username string
|
|
Auth []byte
|
|
Database string
|
|
Plugin string
|
|
SSL bool
|
|
}
|
|
|
|
func parseHandshakeResponse(payload []byte) (handshakeResponse, error) {
|
|
var hr handshakeResponse
|
|
if len(payload) < 32 {
|
|
return hr, errPacket("handshake response too short")
|
|
}
|
|
hr.Capabilities = uint32(payload[0]) | uint32(payload[1])<<8 | uint32(payload[2])<<16 | uint32(payload[3])<<24
|
|
hr.MaxPacket = uint32(payload[4]) | uint32(payload[5])<<8 | uint32(payload[6])<<16 | uint32(payload[7])<<24
|
|
hr.Charset = payload[8]
|
|
hr.SSL = hr.Capabilities&clientSSL != 0
|
|
if hr.SSL && len(payload) <= 32 {
|
|
return hr, nil
|
|
}
|
|
if hr.Capabilities&clientProtocol41 == 0 {
|
|
return hr, errPacket("CLIENT_PROTOCOL_41 required")
|
|
}
|
|
off := 32
|
|
user, off, err := readNUL(payload, off)
|
|
if err != nil {
|
|
return hr, err
|
|
}
|
|
hr.Username = user
|
|
|
|
switch {
|
|
case hr.Capabilities&clientPluginAuthLenencClientData != 0:
|
|
n, nOff, err := readLenencInt(payload, off)
|
|
if err != nil {
|
|
return hr, err
|
|
}
|
|
off = nOff
|
|
end := off + int(n)
|
|
if end > len(payload) {
|
|
return hr, errPacket("auth-response truncated")
|
|
}
|
|
hr.Auth = append([]byte(nil), payload[off:end]...)
|
|
off = end
|
|
case hr.Capabilities&clientSecureConnection != 0:
|
|
if off >= len(payload) {
|
|
return hr, errPacket("auth-response truncated")
|
|
}
|
|
n := int(payload[off])
|
|
off++
|
|
end := off + n
|
|
if end > len(payload) {
|
|
return hr, errPacket("auth-response truncated")
|
|
}
|
|
hr.Auth = append([]byte(nil), payload[off:end]...)
|
|
off = end
|
|
default:
|
|
s, nOff, err := readNUL(payload, off)
|
|
if err != nil {
|
|
return hr, err
|
|
}
|
|
hr.Auth = []byte(s)
|
|
off = nOff
|
|
}
|
|
|
|
if hr.Capabilities&clientConnectWithDB != 0 && off < len(payload) {
|
|
db, nOff, err := readNUL(payload, off)
|
|
if err != nil {
|
|
return hr, err
|
|
}
|
|
hr.Database = db
|
|
off = nOff
|
|
}
|
|
if hr.Capabilities&clientPluginAuth != 0 && off < len(payload) {
|
|
plugin, _, err := readNUL(payload, off)
|
|
if err != nil {
|
|
return hr, err
|
|
}
|
|
hr.Plugin = plugin
|
|
}
|
|
return hr, nil
|
|
}
|
|
|
|
func encodeAuthSwitch(plugin string, scramble []byte) []byte {
|
|
b := []byte{markerEOF} // 0xFE AuthSwitchRequest
|
|
b = appendNUL(b, plugin)
|
|
b = append(b, scramble...)
|
|
b = append(b, 0x00)
|
|
return b
|
|
}
|
|
|
|
type protoError struct{ s string }
|
|
|
|
func (e protoError) Error() string { return e.s }
|
|
|
|
func errPacket(s string) error { return protoError{s} }
|