1use 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
20static 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 buffer: Vec<u8>,
40 config: DatabaseConfig,
41 process_id: i32,
42 secret: i32,
43 status: TransactionStatus,
44 broken: bool,
46}
47
48impl Connection {
49 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 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 pub fn in_transaction(&self) -> bool {
94 self.status != TransactionStatus::Idle
95 }
96
97 pub fn is_encrypted(&self) -> bool {
99 self.stream.is_encrypted()
100 }
101
102 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 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 ("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 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 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 pub async fn query(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
301 if params.is_empty() {
302 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 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 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 async fn read_message(&mut self) -> Result<Backend> {
417 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 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 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
466pub 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
536fn affected_rows(tag: &str) -> u64 {
538 tag.split_whitespace().next_back().and_then(|n| n.parse().ok()).unwrap_or(0)
539}
540
541fn 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 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 assert!(!error.contains("***@") || !error.contains("hunter"));
613 }
614}