1use crate::args::Args;
2use crate::errors::*;
3use crate::keyring::Keyring;
4use crate::signed::Signed;
5use bstr::BString;
6use bytes::Bytes;
7use sequoia_openpgp::armor;
8use serde::{Deserialize, Serialize};
9use std::borrow::Cow;
10use std::io::prelude::*;
11use std::net::SocketAddr;
12use std::path::{Path, PathBuf};
13use tokio::fs;
14
15#[derive(Debug, PartialEq, Default)]
16pub struct Config {
17 pub data: ConfigData,
18 pub config_path: Option<PathBuf>,
19 pub data_path: Option<PathBuf>,
20}
21
22impl Config {
23 pub async fn load_with_args(args: &Args) -> Result<Self> {
24 let (config_path, data) = if let Some(path) = &args.config {
25 if path.to_str() == Some("#") {
26 debug!("Config loading has been explicitly disabled, using default config");
27 (None, ConfigData::default())
28 } else {
29 let data = ConfigData::load_config_from(path)
30 .await
31 .with_context(|| anyhow!("Failed to load configuration from {:?}", path))?;
32 (Some(path.to_owned()), data)
33 }
34 } else if let Some((path, buf)) = Self::find_config().await {
35 debug!("Using configuration from {:?}", path);
36 let data = ConfigData::load_config_from_str(&buf)?;
37 (Some(path), data)
38 } else {
39 (None, ConfigData::default())
40 };
41
42 Ok(Config {
43 data,
44 config_path,
45 data_path: args.data_path.clone(),
46 })
47 }
48
49 async fn find_config() -> Option<(PathBuf, String)> {
50 for path in [
51 Self::default_config_path(),
52 Ok("/etc/apt-swarm.conf".into()),
53 ]
54 .into_iter()
55 .flatten()
56 {
57 match fs::read_to_string(&path).await {
58 Ok(buf) => return Some((path, buf)),
59 Err(err) => {
60 debug!("Attempt to read config from {path:?} failed: {err:#}");
61 }
62 }
63 }
64
65 None
66 }
67
68 pub fn apt_swarm_path(&self) -> Result<Cow<'_, PathBuf>> {
69 let path = if let Some(path) = &self.data_path {
70 Cow::Borrowed(path)
71 } else {
72 let data_dir = dirs::data_dir().context("Failed to detect data directory")?;
73 let path = data_dir.join("apt-swarm");
74 Cow::Owned(path)
75 };
76
77 Ok(path)
78 }
79
80 pub fn database_path(&self) -> Result<PathBuf> {
81 let data_dir = self.apt_swarm_path()?;
82 let path = data_dir.join("storage");
83 Ok(path)
84 }
85
86 pub fn database_migrate_path(&self) -> Result<PathBuf> {
87 let data_dir = self.apt_swarm_path()?;
88 let path = data_dir.join("storage~");
89 Ok(path)
90 }
91
92 pub fn database_delete_path(&self) -> Result<PathBuf> {
93 let data_dir = self.apt_swarm_path()?;
94 let path = data_dir.join("storage=");
95 Ok(path)
96 }
97
98 pub fn db_socket_path(&self) -> Result<PathBuf> {
99 let data_dir = self.apt_swarm_path()?;
100 let path = data_dir.join("db.sock");
101 Ok(path)
102 }
103
104 fn default_config_path() -> Result<PathBuf> {
105 let config_dir = dirs::config_dir().context("Failed to detect config directory")?;
106 let path = config_dir.join("apt-swarm.conf");
107 Ok(path)
108 }
109
110 pub fn peerdb_path(&self) -> Result<PathBuf> {
111 let data_dir = self.apt_swarm_path()?;
112 let path = data_dir.join("peerdb.json");
113 Ok(path)
114 }
115
116 pub fn peerdb_new_path(&self) -> Result<PathBuf> {
117 let data_dir = self.apt_swarm_path()?;
118 let path = data_dir.join("peerdb.json-");
119 Ok(path)
120 }
121}
122
123#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
124pub struct ConfigData {
125 #[serde(default)]
126 pub system: System,
127 #[serde(rename = "repository", default)]
128 pub repositories: Vec<Repository>,
129}
130
131impl ConfigData {
132 pub fn load_config_from_str(buf: &str) -> Result<Self> {
133 let config = toml::from_str(buf)?;
134 Ok(config)
135 }
136
137 pub async fn load_config_from(path: &Path) -> Result<Self> {
138 let buf = fs::read_to_string(&path).await?;
139 Self::load_config_from_str(&buf)
140 }
141}
142
143#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
144pub struct System {
145 #[serde(default)]
146 pub announce: Vec<SocketAddr>,
147}
148
149#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
150pub struct Repository {
151 #[serde(default)]
152 pub urls: Vec<UrlSource>,
153 pub keyring: String,
154}
155
156impl Repository {
157 pub fn contains_fingerprint(&self, fingerprints: &[sequoia_openpgp::Fingerprint]) -> bool {
158 let Ok(keyring) = Keyring::new(self.keyring.as_bytes()) else {
159 return false;
160 };
161 fingerprints.iter().any(|fp| keyring.get(fp).is_some())
162 }
163}
164
165#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
166#[serde(untagged)]
167pub enum UrlSource {
168 Url(String),
169 Detached { content: String, sig: String },
170}
171
172impl UrlSource {
173 pub async fn fetch(&self, client: &reqwest::Client) -> Result<Vec<Signed>> {
174 match self {
175 UrlSource::Url(url) => {
176 let body = Self::fetch_data(client, url).await?;
177
178 let signed = Signed::find_all_in_text(&body)
179 .context("Failed to parse signed message from http response")?;
180
181 if signed.is_empty() {
182 bail!("Failed to find any signed data in http response");
183 }
184
185 Ok(signed)
186 }
187 UrlSource::Detached { content, sig } => {
188 let content = Self::fetch_data(client, content).await?;
189 if !content.ends_with(b"\n") {
190 bail!("Detached signatures are currently only supported if the signed data ends with a newline");
191 }
192 let sig = Self::fetch_data(client, sig).await?;
193
194 let mut reader = armor::Reader::from_bytes(
195 &sig,
196 armor::ReaderMode::Tolerant(Some(armor::Kind::Signature)),
197 );
198
199 let mut signature = Vec::new();
200 reader.read_to_end(&mut signature)?;
201
202 Ok(vec![Signed {
203 content: BString::new(content.into()),
204 signature,
205 }])
206 }
207 }
208 }
209
210 async fn fetch_data(client: &reqwest::Client, url: &str) -> Result<Bytes> {
211 info!("Fetching url {:?}...", url);
212 let r = client
213 .get(url)
214 .send()
215 .await
216 .context("Failed to send request")?
217 .error_for_status()
218 .context("Received http error")?;
219 let body = r
220 .bytes()
221 .await
222 .context("Failed to download http response")?;
223 Ok(body)
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
231
232 const KEYRING: &str = "-----BEGIN PGP PUBLIC KEY BLOCK-----
233
234mQINBFjlSicBEACgho//0EzxuvuCn01LwFqGAgwPKcSSl4L+AWws5/YbsZZvmTBk
235ggIiVOCIMh+d3cmGu5W3ydaeUbWbFGNsxO44EB5YBZcuLa5EzRKbNPVaOXKXmhp+
236w0mEbkoKbF+3mz3lifwBnzcBpukyJDgcJSq8cXfq5JsDPR1KAL6ph/kwKeiDNg+8
237oFgqfboukK56yPTYc9iM8hkTFdx9L6JCJaZGaDMfihoQm2caKAmqc+TlpgtKbBL0
238t5hrzDpCPpJvCddu1NRysTcqfACSSocvoqY0dlbNPMN8j04LH8hcKGFipuLdI8qx
239BFqlMIQJCVJhr05E8rEsI4nYEyG44YoPopTFLuQa+wewZsQkLwcfYeCecU1KxlpE
240OI3xRtALJjA/C/AzUXVXsWn7Xpcble8i3CKkm5LgX5zvR6OxTbmBUmpNgKQiyxD6
241TrP3uADm+0P6e8sJQtA7DlxZLA6HuSi+SQ2WNcuyLL3Q/lJE0qBRWVJ08nI9vvxR
242vAs20LKxq+D1NDhZ2jfG2+5agY661fkx66CZNFdz5OgxJih1UXlwiHpn6qhP7Rub
243OJ54CFb+EwyzDVVKj3EyIZ1FeN/0I8a0WZV6+Y/p08DsDLcKgqcDtK01ydWYP0tA
244o1S2Z7Jsgya50W7ZuP/VkobDqhOmE0HDPggX3zEpXrZKuMnRAcz6Bgi6lwARAQAB
245tDFPcGVuIFdoaXNwZXIgU3lzdGVtcyA8c3VwcG9ydEB3aGlzcGVyc3lzdGVtcy5v
246cmc+iQI3BBMBCgAhBQJY5UonAhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJ
247ENmAoXRX9vsGU00P/RBPPc5qx1EljTW3nnTtgugORrJhYl1CxNvrohVovAF4oP1b
248UIGT5/3FoDsxJHSEIvorPFSaG2+3CBhMB1k950Ig2c2n+PTnNk6D0YIUbbEI0KTX
249nLbCskdpy/+ICiaLfJZMe11wcQpkoNbG587JdQwnGegbQoo580CTSsYMdnvGzC8A
250l1F7r37RVZToJMGgfMKK3oz8xIDXqOe5oiiKcV36tZ5V/PCDAu0hXYBRchtqHlHP
251cKWeRTb1aDkbQ7SPlJ2bSvUjFdB6KahlSGJl3nIU5zAH2LA/tUQY16Z1QaJmfkEb
252RY61B/LPv1TaA1SIUW32ej0NmeF09Ze4Cggdkacxv6E+CaBVbz5rLh6m91acBibm
253pJdGWdZyQU90wYFRbSsqdDNB+0DvJy6AUg4e5f79JYDWT/Szdr0TLKmdPXOxa1Mb
254i34UebYI7WF7q22e7AphpO/JbHcD+N6yYtN6FkUAmJskGkkgYzsM/G8OEbBRS7A+
255eg3+NdQRFhKa7D7nIuufXDOTMUUkUqNYLC+qvZVPJrWnK9ZsGKsP0EUZTfEGkmEN
256UzmASxyMMe6JHmm5Alk4evJeQ31U5jy7ntZSWEV1pSGmSEJLRNJtycciFJpsEp/p
257LkL0iFb30R9bHBp6cg7gjXbqZ9ZpEsxtZMBuqS70ZZyQdu2yGDQCBk7eLKCjuQIN
258BFjlSicBEACsxCLVUE7UuxsEjNblTpSEysoTD6ojc2nWP/eCiII5g6SwA/tQKiQI
259ZcGZsTZB9kTbCw4T3hVEmzPl6u2G6sY9Kh1NHKMR3jXvMC+FHODhOGyAOPERjHCJ
260g20XF2/Gg462iW8e3lS7CQBzbplUCW/oMajj2Qkc61NLtxxzsssXjCKExub2HxCQ
261AYtenuDtLU73G75BoghWJ19dIkodnEI0/fzccsgiP5xeVgmkWJPo9xKJtrBS5gcS
262s7yaGY9YYo71RFzkpJpeAeLrJJqt+2KqH1u0EJUbs8YVGXKlnYeSNisg4OaRsldW
263JmDDCD5WUdFq2LNdVisfwirgjmwYpLrzVMbmzPvdmxQ1NYzJsX4ARSL/wuKCvEub
264gh1AR5oV7mUEA9I3KRH0TIDOnH4nGG3kqArzrV2E1WtnNzFII0IN9/48xY7Vkxs7
265Oil+E+wCpzUv/tF4ALx5TAXoPd66ddEOxzDrtBpEzsouszt7uUyncyT3X6ip5l9f
266mI4uxbsjwkLVfd1WpD1uvp869oyx6wtHluswr1VY/cbnHO8J6J35JVMhYQdMOaTZ
267rX6npe/YOHJ4a7YzLMfdrxyzK1wq5xu/9LgclMTdIhAKvnaXBg41jsid5n0GdIeW
268ek8WAVNyvuvoTwm3GG6+/pkTwu0J79lAMD1mhJsuSca6SFNgYnd+PQARAQABiQIf
269BBgBCgAJBQJY5UonAhsMAAoJENmAoXRX9vsGvRgQAJ4tWnK2TncCpu5nTCxYMXjW
270LuvwORq8EBWczHS6SjLdwmSVKGKSYtl2n6nCkloVY6tONMoiCWmtcq7SJMJoyZw3
271XIf82Z39tzn/conjQcP0aIOFzww1XG7YiaTAhsDZ62kchukI52jUYm2w8cTZMEZB
272oIwIWBpmLlyaDhjIM5neY5RuL7IbIpS/fdk2lwfAwcNq6z/ri2E5RWl3AEINdLUO
273gAiVMagNJaJ+ap7kMcwOLoI2GD84mmbtDWemdUZ3HnqLHv0mb1djsWL6LwjCuOgK
274l2GDrWCh18mE+9mVB1Lo7jzYXNSHXQP6FlDE6FhGO1nNBs2IJzDvmewpnO+a/0pw
275dCerATHWtrCKwMOHrbGLSiTKEjnNt/74gKjXxdFKQkpaEfMFCeiAOFP93tKjRRhP
2765wf1JHBZ1r1+pgfZlS5F20XnM2+f/K1dWmgh+4Grx8pEHGQGLP+A22O7iWjg9pS+
277LD3yikgyGGyQxgcN3sJBQ4yxakOUDZiljm3uNyklUMCiMjTvT/F02PalQMapvA5w
2787Gwg5mSI8NDs3RtiG1rKl9Ytpdq7uHaStlHwGXBVfvayDDKnlpmndee2GBiU/hc2
279ZsYHzEWKXME/ru6EZofUFxeVdev5+9ztYJBBZCGMug5Xp3Gxh/9JUWi6F1+9qAyz
280N+O606NOXLwcmq5KZL0g
281=zyVo
282-----END PGP PUBLIC KEY BLOCK-----
283";
284
285 #[test]
286 fn test_contains_fingerprint() {
287 let repo = Repository {
288 urls: vec![],
289 keyring: KEYRING.to_string(),
290 };
291
292 let fingerprints = vec!["DBA36B5181D0C816F630E889D980A17457F6FB06"
294 .parse::<sequoia_openpgp::Fingerprint>()
295 .unwrap()];
296 assert!(repo.contains_fingerprint(&fingerprints));
297
298 let fingerprints = vec![
300 "4D64FEC119C2029067D6E791F8D2585B8783D481"
301 .parse::<sequoia_openpgp::Fingerprint>()
302 .unwrap(),
303 "05AB90340C0C5E797F44A8C8254CF3B5AEC0A8F0"
304 .parse::<sequoia_openpgp::Fingerprint>()
305 .unwrap(),
306 ];
307 assert!(!repo.contains_fingerprint(&fingerprints));
308
309 let fingerprints = vec![
311 "4D64FEC119C2029067D6E791F8D2585B8783D481"
312 .parse::<sequoia_openpgp::Fingerprint>()
313 .unwrap(),
314 "DBA36B5181D0C816F630E889D980A17457F6FB06"
315 .parse::<sequoia_openpgp::Fingerprint>()
316 .unwrap(),
317 "05AB90340C0C5E797F44A8C8254CF3B5AEC0A8F0"
318 .parse::<sequoia_openpgp::Fingerprint>()
319 .unwrap(),
320 ];
321 assert!(repo.contains_fingerprint(&fingerprints));
322
323 assert!(!repo.contains_fingerprint(&[]));
325 }
326
327 #[test]
328 fn test_parse_empty_config() {
329 let buf = "";
330 let config = ConfigData::load_config_from_str(buf).unwrap();
331 assert_eq!(config, ConfigData::default());
332 }
333
334 #[test]
335 fn test_parse_empty_system_config() {
336 let buf = "[system]";
337 let config = ConfigData::load_config_from_str(buf).unwrap();
338 assert_eq!(config, ConfigData::default());
339 }
340
341 #[test]
342 fn test_parse_system_config_announce() {
343 let buf = r#"
344 [system]
345 announce = ["[2001:db8::1]:1337", "192.168.0.1:16169"]
346 "#;
347 let config = ConfigData::load_config_from_str(buf).unwrap();
348 assert_eq!(
349 config,
350 ConfigData {
351 system: System {
352 announce: vec![
353 SocketAddr::new(
354 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
355 1337
356 ),
357 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), 16169),
358 ],
359 },
360 ..Default::default()
361 }
362 );
363 }
364}