Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pgrx-pg-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ mod cstr;
mod include;
mod node;
mod port;

pub mod libpq;
pub mod submodules;

#[cfg(feature = "cshim")]
Expand Down
110 changes: 110 additions & 0 deletions pgrx-pg-sys/src/libpq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/** Manually-constructed bindings to libpq

Because pgrx is for extensions which run in the Postgres server, it rarely needs access to libpq.
However, some server-side extensions need to interact with the reality that clients exist.
Unfortunately, doing that means acknowledging that clients need authentication and authorization,
areas of concern that are far beyond what pgrx wants to involve itself with or be responsible for.

We define some types and signatures here to allow a minimal amount of usage of items from libpq,
while largely rejecting the notion that we should involve ourselves in security-laden concerns.

*/

pub mod be {

unsafe extern "C" {
pub static mut MyProcPort: *mut Port;
}

/// #define SCRAM_MAX_KEY_LEN PG_SHA256_DIGEST_LENGTH
/// #define PG_SHA256_DIGEST_LENGTH 32
const SCRAM_MAX_KEY_LEN: usize = 32;

#[repr(C)]
pub struct Port {
pub sock: crate::pgsocket,
pub noblock: bool,
pub proto: crate::ProtocolVersion,
pub laddr: crate::SockAddr,
pub raddr: crate::SockAddr,
pub remote_host: *mut core::ffi::c_char,
pub remote_hostname: *mut core::ffi::c_char,
pub remote_hostname_resolv: core::ffi::c_int,
pub remote_hostname_errcode: core::ffi::c_int,
pub remote_port: *mut core::ffi::c_char,
#[cfg(any(feature = "pg13", feature = "pg14", feature = "pg15", feature = "pg16"))]
pub canAcceptConnections: core::ffi::c_uint,
#[cfg(feature = "pg18")]
pub local_host: [core::ffi::c_char; 64],
pub database_name: *mut core::ffi::c_char,
pub user_name: *mut core::ffi::c_char,
pub cmdline_options: *mut core::ffi::c_char,
pub guc_options: *mut crate::List,
pub application_name: *mut core::ffi::c_char,

// The remainder is for completeness, so Rust sees Port's layout as correctly as possible.
// Ideally we would use `extern type` so the remainder of this was seen as of unknown size.
// An alternative is to simply treat them as private fields, so we do.

// This should be `*mut crate::HbaLine` if we ever bind that
hba: *mut core::ffi::c_void,

#[cfg(any(feature = "pg14", feature = "pg15"))]
authn_id: *const core::ffi::c_char,

default_keepalives_idle: core::ffi::c_int,
default_keepalives_interval: core::ffi::c_int,
default_keepalives_count: core::ffi::c_int,
default_tcp_user_timeout: core::ffi::c_int,
keepalives_idle: core::ffi::c_int,
keepalives_interval: core::ffi::c_int,
keepalives_count: core::ffi::c_int,
tcp_user_timeout: core::ffi::c_int,

#[cfg(feature = "pg18")]
scram_ClientKey: [u8; SCRAM_MAX_KEY_LEN],
#[cfg(feature = "pg18")]
scram_ServerKey: [u8; SCRAM_MAX_KEY_LEN],
#[cfg(feature = "pg18")]
has_scram_keys: bool,

// as if ENABLE_GSS == false && ENABLE_SSPI == false
gss: *mut core::ffi::c_void,

ssl_in_use: bool,
peer_cn: *mut core::ffi::c_char,
#[cfg(any(
feature = "pg14",
feature = "pg15",
feature = "pg16",
feature = "pg17",
feature = "pg18"
))]
peer_dn: *mut core::ffi::c_char,
peer_cert_valid: bool,

#[cfg(any(feature = "pg17", feature = "pg18"))]
alpn_used: bool,
#[cfg(feature = "pg18")]
last_read_was_eof: bool,

// NOTE: 5 fields remain on PG17, but two are `#ifdef USE_OPENSSL` in PG17, so treat all
// as conditioned on PG18, even if that is not strictly accurate for PG17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 So we're too small in pg17. I'm not sure what to do about that or how to better explain it to the reader. 👍🏻

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently inclined to write it off as "if someone used size_of on a random Postgres struct it was probably wrong anyways" because of Postgres gleefully (ab)using the "pointer-to-header" pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm tracking correctly... can we add some _padding bytes in the correct spots?

Or would it be better to just define this struct once for every Postgres version where it's different? It is a bit difficult to reason about as currently written whereas defining it N times would be easier to see what's going on at the expense of duplicating portions of the struct. I think that's probably a fair compromise, yeah?

Copy link
Member Author

@workingjubilee workingjubilee Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for Postgres 17 we can get as close as making sure there's some padding to at least match the minimum size, but it will be inexact. I'll add some with a comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Yeah, I'm going to split this along the Postgres 17 "divider".


// as if USE_OPENSSL == false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can implement #[cfg(accessible(crate::USE_OPENSSL))] here through bindgen::callbacks::ParseCallbacks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, that can fix the size on pg17, I guess

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how, at least not right away, @usamoi. I'm not seeing an obvious header to include that doesn't also try to pull in OpenSSL.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how, at least not right away, @usamoi. I'm not seeing an obvious header to include that doesn't also try to pull in OpenSSL.

It's pg_config.h, which is already included.

Screenshot_20251028_140609

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh. then why doesn't our code define USE_OPENSSL as a constant?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh. then why doesn't our code define USE_OPENSSL as a constant?

It's undefined if PostgreSQL is not compiled with OpenSSL. If we set the environment variable PGRX_PG_CONFIG_PATH to the pg_config of a PostgreSQL build compiled with OpenSSL, then pgrx_pg_sys::USE_OPENSSL will be present.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh right, duh.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in ParseCallbacks::int_macro, right? I see, I see.

#[cfg(feature = "pg18")]
ssl: *mut core::ffi::c_void,
#[cfg(feature = "pg18")]
peer: *mut core::ffi::c_void,

#[cfg(any(feature = "pg17", feature = "pg18"))]
#[cfg_attr(feature = "pg17", deprecated(since = "0.17.0", note = "may be unsound to access on Postgres 17 depending on build `#define`s")]
raw_buf: *mut core::ffi::c_char,
#[cfg(any(feature = "pg17", feature = "pg18"))]
#[cfg_attr(feature = "pg17", deprecated(since = "0.17.0", note = "may be unsound to access on Postgres 17 depending on build `#define`s")]
raw_buf_consumed: isize,
#[cfg(any(feature = "pg17", feature = "pg18"))]
#[cfg_attr(feature = "pg17", deprecated(since = "0.17.0", note = "may be unsound to access on Postgres 17 depending on build `#define`s")]
raw_buf_remaining: isize,
}
}
Loading