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} }