82 lines
2.8 KiB
Go
82 lines
2.8 KiB
Go
// Package pgwire contains the minimal PostgreSQL wire framing PawSQL needs before proxying.
|
|
package pgwire
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
sslRequestLength uint32 = 8
|
|
sslRequestCode uint32 = 80877103
|
|
)
|
|
|
|
var (
|
|
ErrNotSSLRequest = errors.New("expected PostgreSQL SSLRequest")
|
|
ErrNotNegotiation = errors.New("not a PostgreSQL SSL or GSSENC negotiation request")
|
|
)
|
|
|
|
// ReadSSLRequest validates the eight-byte PostgreSQL SSL negotiation request.
|
|
func ReadSSLRequest(reader io.Reader) error {
|
|
var request [8]byte
|
|
if _, err := io.ReadFull(reader, request[:]); err != nil {
|
|
return fmt.Errorf("read PostgreSQL SSLRequest: %w", err)
|
|
}
|
|
if length := binary.BigEndian.Uint32(request[0:4]); length != sslRequestLength {
|
|
return fmt.Errorf("%w: invalid length %d", ErrNotSSLRequest, length)
|
|
}
|
|
if code := binary.BigEndian.Uint32(request[4:8]); code != sslRequestCode {
|
|
return fmt.Errorf("%w: invalid request code %d", ErrNotSSLRequest, code)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NegotiatePlainPostgreSQL handles a plaintext server's first protocol exchange
|
|
// and returns the connection positioned at the StartupMessage. When the client
|
|
// opens with an SSLRequest or GSSENCRequest it answers 'N', the standard
|
|
// PostgreSQL decline, so the client retries without encryption.
|
|
func NegotiatePlainPostgreSQL(connection net.Conn) (net.Conn, error) {
|
|
var preamble [8]byte
|
|
if err := connection.SetReadDeadline(time.Now().Add(15 * time.Second)); err != nil {
|
|
return connection, fmt.Errorf("set negotiation deadline: %w", err)
|
|
}
|
|
if _, err := io.ReadFull(connection, preamble[:]); err != nil {
|
|
return connection, fmt.Errorf("read PostgreSQL negotiation preamble: %w", err)
|
|
}
|
|
if err := connection.SetReadDeadline(time.Time{}); err != nil {
|
|
return connection, fmt.Errorf("clear negotiation deadline: %w", err)
|
|
}
|
|
length := binary.BigEndian.Uint32(preamble[0:4])
|
|
code := binary.BigEndian.Uint32(preamble[4:8])
|
|
if length == sslRequestLength && (code == sslRequestCode || code == gssEncryptionCode) {
|
|
if err := declineSSL(connection); err != nil {
|
|
return connection, err
|
|
}
|
|
return connection, nil
|
|
}
|
|
if length < 8 || length > maxStartupMessageSize {
|
|
return connection, fmt.Errorf("%w: invalid length %d", ErrInvalidStartupMessage, length)
|
|
}
|
|
return Replay(connection, preamble[:]), nil
|
|
}
|
|
|
|
func declineSSL(connection net.Conn) error {
|
|
if _, err := connection.Write([]byte{'N'}); err != nil {
|
|
return fmt.Errorf("write PostgreSQL SSL decline: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SSLRequest returns the exact client preamble used to request TLS from PostgreSQL.
|
|
// It exists to make protocol-level tests and clients unambiguous.
|
|
func SSLRequest() [8]byte {
|
|
var request [8]byte
|
|
binary.BigEndian.PutUint32(request[0:4], sslRequestLength)
|
|
binary.BigEndian.PutUint32(request[4:8], sslRequestCode)
|
|
return request
|
|
}
|