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