Skip to main content

rustlavel_db/postgres/
connection.rs

1//! A single PostgreSQL connection.
2
3use super::auth::{self, Scram};
4use super::protocol::{
5    self, Authentication, Backend, Buffer, Field, ServerError, TransactionStatus,
6};
7use super::types;
8use crate::config::DatabaseConfig;
9use crate::random;
10use crate::row::{Columns, Row};
11use crate::value::Value;
12use crate::driver::{BoxFuture, Driver, DriverConnection, QueryResult};
13use rustlavel_core::events::Event;
14use rustlavel_core::{Error, Result};
15use std::sync::Arc;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::time::Instant;
18use tokio::net::TcpStream;
19
20/// Whether query parameters are included in the `db.query` event.
21///
22/// Bindings are what make a slow-query log useful, but they are also where a
23/// password or a token ends up when one is being written. On by default because
24/// the instrumentation bus only has subscribers in development; turned off for
25/// production by the application at boot.
26static LOG_BINDINGS: AtomicBool = AtomicBool::new(true);
27
28pub fn set_log_bindings(enabled: bool) {
29    LOG_BINDINGS.store(enabled, Ordering::Relaxed);
30}
31
32pub fn log_bindings() -> bool {
33    LOG_BINDINGS.load(Ordering::Relaxed)
34}
35
36pub struct Connection {
37    stream: crate::tls::DbStream,
38    /// Bytes read from the socket but not yet consumed as a message.
39    buffer: Vec<u8>,
40    config: DatabaseConfig,
41    process_id: i32,
42    secret: i32,
43    status: TransactionStatus,
44    /// Set when the connection is known to be unusable, so the pool discards it.
45    broken: bool,
46}
47
48impl Connection {
49    /// Open a connection and complete the startup handshake.
50    pub async fn connect(config: &DatabaseConfig) -> Result<Connection> {
51        let address = format!("{}:{}", config.host, config.port);
52
53        let stream = tokio::time::timeout(config.connect_timeout, TcpStream::connect(&address))
54            .await
55            .map_err(|_| {
56                Error::msg(format!(
57                    "timed out connecting to {address}. Is PostgreSQL running and reachable?"
58                ))
59            })?
60            .map_err(|e| {
61                Error::msg(format!(
62                    "cannot connect to {}: {e}",
63                    config.redacted_url()
64                ))
65            })?;
66
67        let _ = stream.set_nodelay(true);
68
69        let mut connection = Connection {
70            stream: crate::tls::DbStream::Plain(stream),
71            buffer: Vec::with_capacity(8 * 1024),
72            config: config.clone(),
73            process_id: 0,
74            secret: 0,
75            status: TransactionStatus::Idle,
76            broken: false,
77        };
78
79        // Before the startup packet, because the startup packet carries the
80        // user name and the database, and after it the password follows. All
81        // of that has to be inside the tunnel, not in front of it.
82        connection.negotiate_tls().await?;
83        connection.startup().await?;
84        Ok(connection)
85    }
86
87    pub fn is_broken(&self) -> bool {
88        self.broken
89    }
90
91    /// True while a transaction is open, so the pool never hands back a
92    /// connection that would leak one.
93    pub fn in_transaction(&self) -> bool {
94        self.status != TransactionStatus::Idle
95    }
96
97    /// Whether this connection is encrypted.
98    pub fn is_encrypted(&self) -> bool {
99        self.stream.is_encrypted()
100    }
101
102    /// Ask PostgreSQL to encrypt the connection, per the SSLRequest exchange in
103    /// the protocol's message formats.
104    ///
105    /// It is eight bytes — a length and a magic number where a normal packet
106    /// would carry its version — and the reply is a single byte outside the
107    /// usual message framing: `S` for yes, `N` for no. That is the whole
108    /// negotiation, and it happens before anything identifying has been sent.
109    async fn negotiate_tls(&mut self) -> Result<()> {
110        let mode = self.config.tls_mode;
111        if !mode.wants_tls() {
112            return Ok(());
113        }
114
115        let mut request = Vec::with_capacity(8);
116        request.extend_from_slice(&8i32.to_be_bytes());
117        request.extend_from_slice(&protocol::SSL_REQUEST_CODE.to_be_bytes());
118        if let Err(e) = self.stream.write_all(&request).await {
119            self.broken = true;
120            return Err(Error::Io(e));
121        }
122        if let Err(e) = self.stream.flush().await {
123            self.broken = true;
124            return Err(Error::Io(e));
125        }
126
127        let mut answer = [0u8; 1];
128        match self.stream.read(&mut answer).await {
129            Ok(1) => {}
130            Ok(_) => {
131                self.broken = true;
132                return Err(Error::msg(
133                    "the server closed the connection when asked about TLS. A PostgreSQL older                      than 8.0 does not understand SSLRequest; set sslmode=disable if that is                      what this is.",
134                ));
135            }
136            Err(e) => {
137                self.broken = true;
138                return Err(Error::Io(e));
139            }
140        }
141
142        match answer[0] {
143            b'S' => {
144                let plain = self.stream.take_plain()?;
145                let encrypted =
146                    crate::tls::upgrade(plain, &self.config.host, &self.config).await?;
147                self.stream = crate::tls::DbStream::Tls(Box::new(encrypted));
148                Ok(())
149            }
150            // The server is willing to talk, but not privately. Under `prefer`
151            // that is accepted; under anything stronger it is the whole point.
152            b'N' if !mode.demands_tls() => Ok(()),
153            b'N' => {
154                self.broken = true;
155                Err(Error::msg(format!(
156                    "sslmode is `{mode}` but this PostgreSQL refused to encrypt the connection.                      Either the server was built without SSL support or `ssl` is off in                      postgresql.conf. Turn it on, or set sslmode=prefer to accept a connection                      in clear text."
157                )))
158            }
159            b'E' => {
160                self.broken = true;
161                Err(Error::msg(
162                    "the server reported an error in response to SSLRequest. This usually means                      it is not actually PostgreSQL.",
163                ))
164            }
165            other => {
166                self.broken = true;
167                Err(Error::msg(format!(
168                    "the server answered SSLRequest with {other:?}, which is not `S` or `N`.                      Whatever is on this port, it is not speaking the PostgreSQL protocol."
169                )))
170            }
171        }
172    }
173
174    async fn startup(&mut self) -> Result<()> {
175        let mut buffer = Buffer::new();
176        buffer.startup(&[
177            ("user", self.config.user.as_str()),
178            ("database", self.config.database.as_str()),
179            ("application_name", self.config.application_name.as_str()),
180            // Timestamps and dates come back in a predictable shape.
181            ("DateStyle", "ISO, MDY"),
182            ("client_encoding", "UTF8"),
183        ]);
184        self.write(buffer).await?;
185
186        let mut scram: Option<Scram> = None;
187
188        loop {
189            match self.read_message().await? {
190                Backend::Authentication(Authentication::Ok) => continue,
191                Backend::Authentication(Authentication::CleartextPassword) => {
192                    // Not on a socket anybody can read. With `sslmode=prefer`,
193                    // an attacker positioned to watch the connection is also
194                    // positioned to answer "no TLS here" to the SSLRequest and
195                    // then ask for this — and the password would arrive
196                    // verbatim. The MySQL driver in this crate already refuses
197                    // the equivalent (`mysql_clear_password`); one crate should
198                    // not hold two policies on one threat.
199                    //
200                    // Encrypted, this is ordinary: it is how PostgreSQL is
201                    // configured to authenticate against LDAP and PAM, and the
202                    // password is inside TLS.
203                    if !self.stream.is_encrypted() {
204                        return Err(Error::msg(format!(
205                            "{} asked for the password in the clear on an unencrypted \
206                             connection, and this driver will not send it. A server that asks \
207                             for this can read the password, and so can anyone on the path — \
208                             including someone who answered the SSLRequest with \"no\" to get \
209                             here. Connect with sslmode=require or stronger, or change the \
210                             server's pg_hba.conf to scram-sha-256.",
211                            self.config.host
212                        )));
213                    }
214                    let mut buffer = Buffer::new();
215                    buffer.password(&self.config.password);
216                    self.write(buffer).await?;
217                }
218                Backend::Authentication(Authentication::Md5Password { salt }) => {
219                    let digest =
220                        auth::md5_password(&self.config.user, &self.config.password, &salt);
221                    let mut buffer = Buffer::new();
222                    buffer.password(&digest);
223                    self.write(buffer).await?;
224                }
225                Backend::Authentication(Authentication::Sasl { mechanisms }) => {
226                    if !mechanisms.iter().any(|m| m == Scram::MECHANISM) {
227                        return Err(Error::msg(format!(
228                            "the server offers only {mechanisms:?}; this driver implements {}",
229                            Scram::MECHANISM
230                        )));
231                    }
232                    let exchange = Scram::new(&self.config.password, random::nonce(24));
233                    let mut buffer = Buffer::new();
234                    buffer.sasl_initial(Scram::MECHANISM, &exchange.client_first());
235                    self.write(buffer).await?;
236                    scram = Some(exchange);
237                }
238                Backend::Authentication(Authentication::SaslContinue { data }) => {
239                    let exchange = scram
240                        .as_mut()
241                        .ok_or_else(|| Error::Protocol("SASL continue before SASL start".into()))?;
242                    let response = exchange.client_final(&data)?;
243                    let mut buffer = Buffer::new();
244                    buffer.sasl_response(&response);
245                    self.write(buffer).await?;
246                }
247                Backend::Authentication(Authentication::SaslFinal { data }) => {
248                    scram
249                        .as_ref()
250                        .ok_or_else(|| Error::Protocol("SASL final before SASL start".into()))?
251                        .verify(&data)?;
252                }
253                Backend::Authentication(Authentication::Unsupported(code)) => {
254                    return Err(Error::msg(format!(
255                        "the server requested authentication method {code}, which this driver does not implement"
256                    )));
257                }
258                Backend::BackendKeyData { process_id, secret } => {
259                    self.process_id = process_id;
260                    self.secret = secret;
261                }
262                Backend::ParameterStatus { .. } | Backend::Notice(_) => continue,
263                Backend::ReadyForQuery(status) => {
264                    self.status = status;
265                    return Ok(());
266                }
267                Backend::Error(error) => {
268                    self.broken = true;
269                    return Err(authentication_error(error, &self.config));
270                }
271                other => {
272                    return Err(Error::Protocol(format!(
273                        "unexpected message during startup: {other:?}"
274                    )));
275                }
276            }
277        }
278    }
279
280    /// Run a statement with no parameters through the simple query protocol.
281    ///
282    /// Used for DDL and for statements that must run as one unit, such as
283    /// `begin`/`commit`.
284    pub async fn simple_query(&mut self, sql: &str) -> Result<QueryResult> {
285        let started = Instant::now();
286        let mut buffer = Buffer::new();
287        buffer.query(sql);
288        self.write(buffer).await?;
289
290        let result = self.collect(sql).await;
291        self.record(sql, &[], started, &result);
292        result
293    }
294
295    /// Run a parameterised statement through the extended query protocol.
296    ///
297    /// Parameters never enter the SQL text, so a value cannot change the shape
298    /// of the statement — this is what makes SQL injection structurally
299    /// impossible rather than a matter of remembering to escape.
300    pub async fn query(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
301        if params.is_empty() {
302            // Still uses the extended protocol, so a single statement per call
303            // is enforced either way.
304            return self.extended(sql, params).await;
305        }
306        self.extended(sql, params).await
307    }
308
309    async fn extended(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
310        let started = Instant::now();
311        let encoded: Vec<Option<String>> = params.iter().map(Value::to_sql_text).collect();
312
313        let mut buffer = Buffer::new();
314        buffer.parse("", sql);
315        buffer.bind("", "", &encoded);
316        buffer.describe_portal("");
317        buffer.execute("", 0);
318        buffer.sync();
319        self.write(buffer).await?;
320
321        let result = self.collect(sql).await;
322        self.record(sql, params, started, &result);
323        result
324    }
325
326    /// Read messages until `ReadyForQuery`, gathering rows on the way.
327    ///
328    /// The loop always runs to `ReadyForQuery` even after an error, otherwise
329    /// the next query would read this one's leftovers.
330    async fn collect(&mut self, sql: &str) -> Result<QueryResult> {
331        let mut columns: Columns = Arc::new(Vec::new());
332        let mut fields: Vec<Field> = Vec::new();
333        let mut result = QueryResult::default();
334        let mut failure: Option<ServerError> = None;
335
336        loop {
337            match self.read_message().await? {
338                Backend::RowDescription(described) => {
339                    columns = Arc::new(described.iter().map(|f| f.name.clone()).collect());
340                    fields = described;
341                }
342                Backend::DataRow(raw) => {
343                    let values = raw
344                        .iter()
345                        .enumerate()
346                        .map(|(index, bytes)| {
347                            let oid = fields.get(index).map_or(types::TEXT, |f| f.type_oid);
348                            types::decode(oid, bytes.as_deref())
349                        })
350                        .collect();
351                    result.rows.push(Row::new(Arc::clone(&columns), values));
352                }
353                Backend::CommandComplete(tag) => result.affected = affected_rows(&tag),
354                Backend::Error(error) => failure = Some(error),
355                Backend::ReadyForQuery(status) => {
356                    self.status = status;
357                    break;
358                }
359                Backend::EmptyQueryResponse
360                | Backend::ParseComplete
361                | Backend::BindComplete
362                | Backend::CloseComplete
363                | Backend::NoData
364                | Backend::PortalSuspended
365                | Backend::Notice(_)
366                | Backend::ParameterStatus { .. }
367                | Backend::NotificationResponse { .. }
368                | Backend::BackendKeyData { .. }
369                | Backend::Other(_)
370                | Backend::Authentication(_) => {}
371            }
372        }
373
374        match failure {
375            Some(error) => Err(error.into_error(Some(sql))),
376            None => Ok(result),
377        }
378    }
379
380    /// Publish the query on the event bus for Telescope and slow-query logging.
381    fn record(&self, sql: &str, params: &[Value], started: Instant, result: &Result<QueryResult>) {
382        let elapsed = started.elapsed();
383
384        if rustlavel_core::events::has_subscribers() {
385            let bindings = if log_bindings() {
386                params.iter().map(Value::to_display).collect::<Vec<_>>().join(", ")
387            } else {
388                format!("{} value(s) hidden", params.len())
389            };
390            Event::new("db.query")
391                .with("sql", sql)
392                .with("bindings", bindings)
393                .with("rows", result.as_ref().map(|r| r.rows.len()).unwrap_or(0))
394                .with("ok", result.is_ok())
395                .took(elapsed)
396                .dispatch();
397        }
398
399        rustlavel_core::debug!("db: {sql} ({:.1}ms)", elapsed.as_secs_f64() * 1000.0);
400    }
401
402    async fn write(&mut self, buffer: Buffer) -> Result<()> {
403        let bytes = buffer.into_bytes();
404        if let Err(e) = self.stream.write_all(&bytes).await {
405            self.broken = true;
406            return Err(Error::Io(e));
407        }
408        if let Err(e) = self.stream.flush().await {
409            self.broken = true;
410            return Err(Error::Io(e));
411        }
412        Ok(())
413    }
414
415    /// Read exactly one backend message.
416    async fn read_message(&mut self) -> Result<Backend> {
417        // A message is a type byte plus a length that includes itself.
418        self.fill_to(5).await?;
419        let tag = self.buffer[0];
420        let length = i32::from_be_bytes(self.buffer[1..5].try_into().expect("4 bytes")) as usize;
421
422        if length < 4 {
423            self.broken = true;
424            return Err(Error::Protocol("message length is impossibly small".into()));
425        }
426
427        let total = length + 1;
428        self.fill_to(total).await?;
429        let body = self.buffer[5..total].to_vec();
430        self.buffer.drain(..total);
431
432        Backend::parse(tag, &body)
433    }
434
435    /// Read from the socket until the buffer holds at least `wanted` bytes.
436    async fn fill_to(&mut self, wanted: usize) -> Result<()> {
437        while self.buffer.len() < wanted {
438            let mut chunk = [0u8; 8192];
439            let read = match self.stream.read(&mut chunk).await {
440                Ok(read) => read,
441                Err(e) => {
442                    self.broken = true;
443                    return Err(Error::Io(e));
444                }
445            };
446            if read == 0 {
447                self.broken = true;
448                return Err(Error::Protocol(
449                    "the database closed the connection unexpectedly".into(),
450                ));
451            }
452            self.buffer.extend_from_slice(&chunk[..read]);
453        }
454        Ok(())
455    }
456
457    /// Ask the server to close the session politely.
458    pub async fn close(mut self) {
459        let mut buffer = Buffer::new();
460        buffer.terminate();
461        let _ = self.write(buffer).await;
462        let _ = self.stream.shutdown().await;
463    }
464}
465
466/// Opens PostgreSQL connections.
467///
468/// The reference implementation of [`Driver`]: everything above the driver line
469/// is written once, and this is what that line looks like from below.
470pub struct PostgresDriver {
471    config: DatabaseConfig,
472    dialect: Arc<dyn crate::dialect::Dialect>,
473}
474
475impl PostgresDriver {
476    pub fn new(config: DatabaseConfig) -> Self {
477        PostgresDriver { config, dialect: Arc::new(crate::dialect::Postgres) }
478    }
479
480    pub fn config(&self) -> &DatabaseConfig {
481        &self.config
482    }
483}
484
485impl Driver for PostgresDriver {
486    fn generation(&self) -> u64 {
487        self.config.generation()
488    }
489
490    fn dialect(&self) -> Arc<dyn crate::dialect::Dialect> {
491        Arc::clone(&self.dialect)
492    }
493
494    fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
495        Box::pin(async move {
496            let connection = Connection::connect(&self.config.resolved()).await?;
497            Ok(Box::new(connection) as Box<dyn DriverConnection>)
498        })
499    }
500
501    fn describe(&self) -> String {
502        self.config.redacted_url()
503    }
504
505    fn max_connections(&self) -> usize {
506        self.config.max_connections
507    }
508}
509
510impl DriverConnection for Connection {
511    fn query<'a>(
512        &'a mut self,
513        sql: &'a str,
514        params: &'a [Value],
515    ) -> BoxFuture<'a, Result<QueryResult>> {
516        Box::pin(Connection::query(self, sql, params))
517    }
518
519    fn simple_query<'a>(&'a mut self, sql: &'a str) -> BoxFuture<'a, Result<QueryResult>> {
520        Box::pin(Connection::simple_query(self, sql))
521    }
522
523    fn is_broken(&self) -> bool {
524        Connection::is_broken(self)
525    }
526
527    fn in_transaction(&self) -> bool {
528        Connection::in_transaction(self)
529    }
530
531    fn close(self: Box<Self>) -> BoxFuture<'static, ()> {
532        Box::pin(async move { Connection::close(*self).await })
533    }
534}
535
536/// `INSERT 0 3`, `UPDATE 2`, `SELECT 5` → the trailing count.
537fn affected_rows(tag: &str) -> u64 {
538    tag.split_whitespace().next_back().and_then(|n| n.parse().ok()).unwrap_or(0)
539}
540
541/// Turn an authentication failure into something the developer can act on.
542fn authentication_error(error: ServerError, config: &DatabaseConfig) -> Error {
543    let base = error.clone().into_error(None);
544
545    let advice = match error.code.as_str() {
546        "28P01" => Some(format!(
547            "The password for `{}` was rejected. Check DATABASE_URL in your .env.",
548            config.user
549        )),
550        "3D000" => Some(format!(
551            "Database `{}` does not exist. Create it, or point DATABASE_URL at an existing one.",
552            config.database
553        )),
554        "28000" => Some(
555            "The server rejected this role or host. Check pg_hba.conf allows this connection."
556                .to_string(),
557        ),
558        _ => None,
559    };
560
561    match advice {
562        Some(advice) => Error::msg(format!("{base}\n  {advice}")),
563        None => base,
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570
571    #[test]
572    fn reads_the_row_count_from_a_command_tag() {
573        assert_eq!(affected_rows("INSERT 0 3"), 3);
574        assert_eq!(affected_rows("UPDATE 2"), 2);
575        assert_eq!(affected_rows("DELETE 0"), 0);
576        assert_eq!(affected_rows("CREATE TABLE"), 0);
577    }
578
579    #[test]
580    fn bindings_can_be_kept_out_of_the_event_stream() {
581        // Restored immediately: the flag is process-wide.
582        assert!(log_bindings());
583        set_log_bindings(false);
584        assert!(!log_bindings());
585        set_log_bindings(true);
586    }
587
588    #[test]
589    fn a_wrong_password_explains_where_to_look() {
590        let error = ServerError {
591            code: "28P01".into(),
592            message: "password authentication failed".into(),
593            ..ServerError::default()
594        };
595        let config = DatabaseConfig { user: "ada".into(), ..DatabaseConfig::default() };
596
597        let rendered = authentication_error(error, &config).to_string();
598        assert!(rendered.contains("DATABASE_URL"));
599        assert!(rendered.contains("`ada`"));
600    }
601
602    #[tokio::test]
603    async fn connecting_to_a_closed_port_names_the_server() {
604        let config = DatabaseConfig { port: 1, ..DatabaseConfig::default() };
605        let error = match Connection::connect(&config).await {
606            Err(error) => error.to_string(),
607            Ok(_) => panic!("nothing should be listening on port 1"),
608        };
609
610        assert!(error.contains("127.0.0.1:1"));
611        // The password must never appear, even in a connection error.
612        assert!(!error.contains("***@") || !error.contains("hunter"));
613    }
614}