linuxutils_misc/
mcookie.rs1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::{
7 fs::File,
8 io::{self, Read},
9 path::PathBuf,
10 process::ExitCode,
11};
12
13#[derive(Parser)]
18#[command(name = "mcookie", version, about)]
19pub struct Args {
20 #[arg(short, long)]
23 file: Option<PathBuf>,
24
25 #[arg(short, long)]
27 max_size: Option<u64>,
28
29 #[arg(short, long)]
31 verbose: bool,
32}
33
34pub fn run(args: Args) -> ExitCode {
35 let result = if let Some(ref path) = args.file {
36 if path.as_os_str() == "-" {
37 let mut stdin = io::stdin().lock();
38 mcookie(Some(&mut stdin), args.max_size, args.verbose)
39 } else {
40 match File::open(path) {
41 Ok(mut f) => mcookie(Some(&mut f), args.max_size, args.verbose),
42 Err(e) => {
43 eprintln!("mcookie: {}: {e}", path.display());
44 return ExitCode::FAILURE;
45 }
46 }
47 }
48 } else {
49 mcookie(None, args.max_size, args.verbose)
50 };
51
52 match result {
53 Ok(cookie) => {
54 println!("{cookie}");
55 ExitCode::SUCCESS
56 }
57 Err(e) => {
58 eprintln!("mcookie: {e}");
59 ExitCode::FAILURE
60 }
61 }
62}
63
64pub fn mcookie(
69 extra_file: Option<&mut dyn Read>,
70 max_size: Option<u64>,
71 verbose: bool,
72) -> io::Result<String> {
73 let mut bytes = [0u8; 16];
74
75 getrandom(&mut bytes)?;
77 if verbose {
78 eprintln!("Got 16 bytes from getrandom/urandom");
79 }
80
81 if let Some(reader) = extra_file {
83 let mut extra = Vec::new();
84 match max_size {
85 Some(max) => {
86 reader.take(max).read_to_end(&mut extra)?;
87 }
88 None => {
89 reader.read_to_end(&mut extra)?;
90 }
91 }
92 if verbose {
93 eprintln!("Got {} bytes from file", extra.len());
94 }
95 for (i, &b) in extra.iter().enumerate() {
96 bytes[i % 16] ^= b;
97 }
98 }
99
100 Ok(bytes.iter().map(|b| format!("{b:02x}")).collect())
101}
102
103fn getrandom(buf: &mut [u8]) -> io::Result<()> {
104 let mut f = File::open("/dev/urandom")?;
105 f.read_exact(buf)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn generates_32_hex_chars() {
114 let cookie = mcookie(None, None, false).unwrap();
115 assert_eq!(cookie.len(), 32);
116 assert!(cookie.chars().all(|c| c.is_ascii_hexdigit()));
117 }
118
119 #[test]
120 fn two_cookies_differ() {
121 let a = mcookie(None, None, false).unwrap();
122 let b = mcookie(None, None, false).unwrap();
123 assert_ne!(a, b);
124 }
125
126 #[test]
127 fn extra_file_mixed_in() {
128 let mut extra = &b"extra randomness here"[..];
129 let cookie = mcookie(Some(&mut extra), None, false).unwrap();
130 assert_eq!(cookie.len(), 32);
131 assert!(cookie.chars().all(|c| c.is_ascii_hexdigit()));
132 }
133
134 #[test]
135 fn max_size_limits_read() {
136 let mut extra = &b"extra randomness here"[..];
137 let cookie = mcookie(Some(&mut extra), Some(4), false).unwrap();
138 assert_eq!(cookie.len(), 32);
139 assert!(cookie.chars().all(|c| c.is_ascii_hexdigit()));
140 }
141}