Skip to content

Commit a14e4eb

Browse files
committed
Load passfile content on apply_pgpass
1 parent d097111 commit a14e4eb

2 files changed

Lines changed: 98 additions & 81 deletions

File tree

sqlx-postgres/src/options/mod.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
55

66
pub use ssl_mode::PgSslMode;
77

8+
use crate::options::pgpass::PGPassFile;
89
use crate::{connection::LogSettings, net::tls::CertificateInput};
910

1011
mod connect;
@@ -20,6 +21,7 @@ pub struct PgConnectOptions {
2021
pub(crate) socket: Option<PathBuf>,
2122
pub(crate) username: String,
2223
pub(crate) password: Option<String>,
24+
pub(crate) passfile: PGPassFile,
2325
pub(crate) database: Option<String>,
2426
pub(crate) ssl_mode: PgSslMode,
2527
pub(crate) ssl_root_cert: Option<CertificateInput>,
@@ -74,6 +76,7 @@ impl PgConnectOptions {
7476
socket: None,
7577
username,
7678
password: var("PGPASSWORD").ok(),
79+
passfile: PGPassFile::default(),
7780
database,
7881
ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from),
7982
ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from),
@@ -94,14 +97,7 @@ impl PgConnectOptions {
9497
}
9598

9699
pub(crate) fn apply_pgpass(mut self) -> Self {
97-
if self.password.is_none() {
98-
self.password = pgpass::load_password(
99-
&self.host,
100-
self.port,
101-
&self.username,
102-
self.database.as_deref(),
103-
);
104-
}
100+
self.passfile = PGPassFile::load().unwrap_or_default();
105101

106102
self
107103
}
@@ -531,7 +527,14 @@ impl PgConnectOptions {
531527
if self.password.is_some() {
532528
return self.password.as_deref().map(Cow::Borrowed);
533529
}
534-
None
530+
self.passfile
531+
.password_if_matching(
532+
&self.host,
533+
self.port,
534+
self.database.as_deref(),
535+
&self.username,
536+
)
537+
.map(Cow::Owned)
535538
}
536539

537540
/// Get the current database name.

sqlx-postgres/src/options/pgpass.rs

Lines changed: 86 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,100 @@
11
use std::borrow::Cow;
22
use std::env::var_os;
33
use std::fs::File;
4-
use std::io::{BufRead, BufReader};
4+
use std::io::{BufRead, Read};
55
use std::path::PathBuf;
66

7-
/// try to load a password from the various pgpass file locations
8-
pub fn load_password(
9-
host: &str,
10-
port: u16,
11-
username: &str,
12-
database: Option<&str>,
13-
) -> Option<String> {
14-
let custom_file = var_os("PGPASSFILE");
15-
if let Some(file) = custom_file {
16-
if let Some(password) =
17-
load_password_from_file(PathBuf::from(file), host, port, username, database)
18-
{
19-
return Some(password);
7+
/// PostgreSQL passfile content.
8+
#[derive(Clone, Debug, Default)]
9+
pub struct PGPassFile(String);
10+
11+
impl PGPassFile {
12+
/// Loads the first valid passfile discovered.
13+
///
14+
/// Loading is attempted in the following order:
15+
/// 1. Path given via the `PGPASSFILE` environment variable.
16+
/// 2. Default path (`~/.pgpass` on Linux and
17+
/// `%APPDATA%/postgres/pgpass.conf` on Windows)
18+
///
19+
/// If loading of any file fails, the function proceeds to the next.
20+
/// Returns `None` in case no file can be loaded.
21+
pub fn load() -> Option<Self> {
22+
let custom_file = var_os("PGPASSFILE");
23+
if let Some(file) = custom_file {
24+
if let Some(password) = Self::load_from_file(PathBuf::from(file)) {
25+
return Some(password);
26+
}
2027
}
21-
}
2228

23-
#[cfg(not(target_os = "windows"))]
24-
let default_file = home::home_dir().map(|path| path.join(".pgpass"));
25-
#[cfg(target_os = "windows")]
26-
let default_file = {
27-
use etcetera::BaseStrategy;
28-
29-
etcetera::base_strategy::Windows::new()
30-
.ok()
31-
.map(|basedirs| basedirs.data_dir().join("postgres").join("pgpass.conf"))
32-
};
33-
load_password_from_file(default_file?, host, port, username, database)
34-
}
29+
#[cfg(not(target_os = "windows"))]
30+
let default_file = home::home_dir().map(|path| path.join(".pgpass"));
31+
#[cfg(target_os = "windows")]
32+
let default_file = {
33+
use etcetera::BaseStrategy;
34+
35+
etcetera::base_strategy::Windows::new()
36+
.ok()
37+
.map(|basedirs| basedirs.data_dir().join("postgres").join("pgpass.conf"))
38+
};
39+
Self::load_from_file(default_file?)
40+
}
3541

36-
/// try to extract a password from a pgpass file
37-
fn load_password_from_file(
38-
path: PathBuf,
39-
host: &str,
40-
port: u16,
41-
username: &str,
42-
database: Option<&str>,
43-
) -> Option<String> {
44-
let file = File::open(&path)
45-
.map_err(|e| {
46-
match e.kind() {
47-
std::io::ErrorKind::NotFound => {
48-
tracing::debug!(
49-
path = %path.display(),
50-
"`.pgpass` file not found",
51-
);
52-
}
53-
_ => {
54-
tracing::warn!(
55-
path = %path.display(),
56-
"Failed to open `.pgpass` file: {e:?}",
57-
);
58-
}
59-
};
60-
})
61-
.ok()?;
62-
63-
#[cfg(target_os = "linux")]
64-
{
65-
use std::os::unix::fs::PermissionsExt;
66-
67-
// check file permissions on linux
68-
69-
let metadata = file.metadata().ok()?;
70-
let permissions = metadata.permissions();
71-
let mode = permissions.mode();
72-
if mode & 0o77 != 0 {
73-
tracing::warn!(
74-
path = %path.display(),
75-
permissions = format!("{mode:o}"),
76-
"Ignoring path. Permissions are not strict enough",
77-
);
78-
return None;
42+
/// Returns the PostgreSQL passfile loaded from the given path.
43+
fn load_from_file(path: PathBuf) -> Option<Self> {
44+
let mut file = File::open(&path)
45+
.map_err(|e| {
46+
match e.kind() {
47+
std::io::ErrorKind::NotFound => {
48+
tracing::debug!(
49+
path = %path.display(),
50+
"`.pgpass` file not found",
51+
);
52+
}
53+
_ => {
54+
tracing::warn!(
55+
path = %path.display(),
56+
"Failed to open `.pgpass` file: {e:?}",
57+
);
58+
}
59+
};
60+
})
61+
.ok()?;
62+
63+
#[cfg(target_os = "linux")]
64+
{
65+
use std::os::unix::fs::PermissionsExt;
66+
67+
// check file permissions on linux
68+
69+
let metadata = file.metadata().ok()?;
70+
let permissions = metadata.permissions();
71+
let mode = permissions.mode();
72+
if mode & 0o77 != 0 {
73+
tracing::warn!(
74+
path = %path.display(),
75+
permissions = format!("{mode:o}"),
76+
"Ignoring path. Permissions are not strict enough",
77+
);
78+
return None;
79+
}
7980
}
81+
82+
let mut passfile = Self::default();
83+
file.read_to_string(&mut passfile.0).ok()?;
84+
85+
Some(passfile)
8086
}
8187

82-
let reader = BufReader::new(file);
83-
load_password_from_reader(reader, host, port, username, database)
88+
/// Returns the password matched by the given parameters.
89+
pub fn password_if_matching(
90+
&self,
91+
hostname: &str,
92+
port: u16,
93+
database: Option<&str>,
94+
username: &str,
95+
) -> Option<String> {
96+
load_password_from_reader(self.0.as_bytes(), hostname, port, username, database)
97+
}
8498
}
8599

86100
fn load_password_from_reader(

0 commit comments

Comments
 (0)