cdbc-pg 0.1.22

Rust Coroutine Database Driver Connectivity
Documentation
use bytes::Bytes;

use cdbc::error::Error;
use crate::connection::stream::PgStream;
use crate::message::SslRequest;
use crate::{PgConnectOptions, PgSslMode};

pub(super) fn maybe_upgrade(
    stream: &mut PgStream,
    options: &PgConnectOptions,
) -> Result<(), Error> {
    // https://www.postgresql.org/docs/12/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS
    match options.ssl_mode {
        // FIXME: Implement ALLOW
        PgSslMode::Allow | PgSslMode::Disable => {}

        PgSslMode::Prefer => {
            // try upgrade, but its okay if we fail
            upgrade(stream, options)?;
        }

        PgSslMode::Require | PgSslMode::VerifyFull | PgSslMode::VerifyCa => {
            if !upgrade(stream, options)? {
                // upgrade failed, die
                return Err(Error::Tls("server does not support TLS".into()));
            }
        }
    }

    Ok(())
}

fn upgrade(stream: &mut PgStream, options: &PgConnectOptions) -> Result<bool, Error> {
    // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.11

    // To initiate an SSL-encrypted connection, the frontend initially sends an
    // SSLRequest message rather than a StartupMessage

    stream.send(SslRequest)?;

    // The server then responds with a single byte containing S or N, indicating that
    // it is willing or unwilling to perform SSL, respectively.

    match stream.read::<Bytes>(1)?[0] {
        b'S' => {
            // The server is ready and willing to accept an SSL connection
        }

        b'N' => {
            // The server is _unwilling_ to perform SSL
            return Ok(false);
        }

        other => {
            return Err(err_protocol!(
                "unexpected response from SSLRequest: 0x{:02x}",
                other
            ));
        }
    }

    let accept_invalid_certs = !matches!(
        options.ssl_mode,
        PgSslMode::VerifyCa | PgSslMode::VerifyFull
    );
    let accept_invalid_hostnames = !matches!(options.ssl_mode, PgSslMode::VerifyFull);

    if !cfg!(feature = "native-tls")  {
        return cdbc::Result::Err(Error::from("must enable native-tls!"));
    }
    #[cfg(feature = "native-tls")]
    stream.upgrade(
            &options.host,
            accept_invalid_certs,
            accept_invalid_hostnames,
            options.ssl_root_cert.as_ref(),
        )
        ?;

    Ok(true)
}