Files
mysql-tunnel/internal/mysqlserver/handshake.go
T
2026-09-21 16:46:55 +08:00

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