diff --git a/src/lists.rs b/src/lists.rs index ac51dea..6cb308b 100644 --- a/src/lists.rs +++ b/src/lists.rs @@ -183,7 +183,7 @@ pub fn selected_listings(release: &Release) -> Vec { } if let Some(ref entry_arch) = entry.arch { - if arch != entry_arch { + if !entry_arch.contains(arch) { continue; } } diff --git a/src/release.rs b/src/release.rs index fe79dbb..6f02afc 100644 --- a/src/release.rs +++ b/src/release.rs @@ -38,6 +38,7 @@ pub struct RequestedRelease { pub codename: String, pub arches: Vec, + pub untrusted: bool, } #[derive(Debug, Clone)] @@ -139,6 +140,7 @@ impl RequestedReleases { mirror: Url::parse(&entry.url)?, codename: entry.suite_codename.to_string(), arches: arches.to_vec(), + untrusted: entry.untrusted, }) { hash_map::Entry::Vacant(vacancy) => { vacancy.insert(vec![entry.clone()]); @@ -173,7 +175,7 @@ impl RequestedReleases { &dest, )], ) { - Ok(_) => gpg.verify_clearsigned(&dest, &verified), + Ok(_) => gpg.read_clearsigned(&dest, &verified, !release.untrusted), Err(_) => { let mut detatched_signature = dest.as_os_str().to_os_string(); detatched_signature.push(".gpg"); @@ -183,14 +185,18 @@ impl RequestedReleases { &[Download::from_to(release.dists()?.join("Release")?, &dest)], )?; - fetch( - client, - &[Download::from_to( - release.dists()?.join("Release.gpg")?, - &detatched_signature, - )], - )?; - gpg.verify_detached(&dest, detatched_signature, verified) + if !release.untrusted { + fetch( + client, + &[Download::from_to( + release.dists()?.join("Release.gpg")?, + &detatched_signature, + )], + )?; + gpg.verify_detached(&dest, detatched_signature, verified) + } else { + Ok(()) + } } } .with_context(|| anyhow!("verifying {:?} at {:?}", release, dest))?; diff --git a/src/signing.rs b/src/signing.rs index c1a7f3c..a1e2f8f 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -17,10 +17,11 @@ impl<'k> GpgClient<'k> { GpgClient { keyring } } - pub fn verify_clearsigned, Q: AsRef>( + pub fn read_clearsigned, Q: AsRef>( &self, file: P, dest: Q, + verify: bool, ) -> Result<(), Error> { let from = fs::File::open(file).with_context(|| anyhow!("opening input file"))?; let to = PersistableTempFile::new_in( @@ -30,7 +31,12 @@ impl<'k> GpgClient<'k> { ) .with_context(|| anyhow!("creating temporary file"))?; - gpgrv::verify_message(io::BufReader::new(from), &to, &self.keyring)?; + let reader = io::BufReader::new(from); + if verify { + gpgrv::verify_message(reader, &to, &self.keyring)?; + } else { + gpgrv::read_doc(reader, &to)?; + } to.persist_by_rename(dest) .map_err(|e| e.error) diff --git a/src/sources_list.rs b/src/sources_list.rs index 91de552..200ba71 100644 --- a/src/sources_list.rs +++ b/src/sources_list.rs @@ -14,7 +14,30 @@ pub struct Entry { pub url: String, pub suite_codename: String, pub components: Vec, - pub arch: Option, + pub arch: Option>, + pub untrusted: bool, +} + +#[derive(Default)] +struct ParsedOpts { + arch: Option>, + untrusted: Option, +} + +fn parse_opts(opt_parts: Vec<&str>) -> Result { + let mut ret = ParsedOpts::default(); + for opt in opt_parts { + let parts: Vec<_> = opt.split("=").collect(); + match parts.len() { + 2 => match parts[0] { + "arch" => ret.arch = Some(parts[1].split(',').map(|s| s.to_owned()).collect()), + "untrusted" => ret.untrusted = Some(parts[1] == "yes"), + other => bail!("unknown option: {}", other), + }, + _ => bail!("multiple = in option"), + } + } + Ok(ret) } fn read_single_line(line: &str) -> Result, Error> { @@ -33,14 +56,27 @@ fn read_single_line(line: &str) -> Result, Error> { let src = parts .next() .ok_or_else(|| anyhow!("deb{{,s,-src}} section required"))?; - let arch = match parts.peek() { - Some(&val) if val.starts_with("[") => { + + let mut opt_parts = vec![]; + if let Some(&(mut val)) = parts.peek() { + if val.starts_with("[") { + val = val.strip_prefix("[").expect("opening ["); parts.next(); - Some(val) + loop { + if val.ends_with("]") { + opt_parts.push(val.strip_suffix("]").expect("closing ]")); + break; + } + opt_parts.push(val); + match parts.next() { + Some(next) => { + val = next; + } + None => bail!("unexpected end of line reading options"), + } + } } - Some(_) => None, - None => bail!("unexpected end of line looking for arch or url"), - }; + } let url = parts .next() @@ -60,6 +96,7 @@ fn read_single_line(line: &str) -> Result, Error> { let mut ret = Vec::with_capacity(srcs.len()); + let parsed_opts = parse_opts(opt_parts)?; for src in srcs { ret.push(Entry { src: *src, @@ -70,7 +107,8 @@ fn read_single_line(line: &str) -> Result, Error> { }, suite_codename: suite.to_string(), components: components.iter().map(|x| x.to_string()).collect(), - arch: arch.map(|arch| arch.to_string()), + arch: parsed_opts.arch.clone(), + untrusted: parsed_opts.untrusted.unwrap_or(false), }); } @@ -113,6 +151,7 @@ mod tests { url: "http://foo/".to_string(), suite_codename: "bar".to_string(), components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, }, Entry { src: true, @@ -120,12 +159,113 @@ mod tests { url: "http://foo/".to_string(), suite_codename: "bar".to_string(), components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, }, ], read(io::Cursor::new( r" deb http://foo bar baz quux deb-src http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn arch() { + assert_eq!( + vec![Entry { + src: false, + arch: Some(vec!["amd64".to_string()]), + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, + },], + read(io::Cursor::new( + r" +deb [arch=amd64] http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn untrusted() { + assert_eq!( + vec![Entry { + src: false, + arch: None, + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: true, + },], + read(io::Cursor::new( + r" +deb [untrusted=yes] http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn untrusted_not_yes() { + assert_eq!( + vec![Entry { + src: false, + arch: None, + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, + },], + read(io::Cursor::new( + r" +deb [untrusted=yeppers] http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn multiple_opt() { + assert_eq!( + vec![Entry { + src: false, + arch: Some(vec!["amd64".to_string()]), + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: true, + },], + read(io::Cursor::new( + r" +deb [untrusted=yes arch=amd64] http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn multiple_arches() { + assert_eq!( + vec![Entry { + src: false, + arch: Some(vec!["amd64".to_string(), "i386".to_string()]), + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, + },], + read(io::Cursor::new( + r" +deb [arch=amd64,i386] http://foo bar baz quux ", )) .unwrap()