genpass3/
lib.rs

1// MIT License - Copyright (c) 2022 Nicolás Castellán
2// SPDX License identifier: MIT
3// THE SOFTWARE IS PROVIDED "AS IS"
4// Read the included LICENSE file for more information
5
6//! # genpass3
7//!
8//! `genpass3` is a library and binary crate that allows Linux users to generate very long & secure
9//! passwords at incredible speeds using `/dev/urandom`.
10
11use std::{
12    error::Error,
13    fs::File,
14    io::{BufReader, Read},
15};
16
17/// The config for the generation of passwords. Taken as an argument by the [`run`](run) function.
18///
19/// You can create an instance of this struct using [`Config::new`](Config::new) or
20/// [`Config::build`](Config::build).
21pub struct Config {
22    length: usize,
23}
24
25impl Config {
26    /// Creates a [`Config`](Config) type using the command line arguments of the binary, passed in
27    /// as arguments. **Assumes** the first iteration of `args` is the program name, so it's
28    /// ignored.
29    ///
30    /// Parameter:
31    /// - `args` - An iterator, meant to iterate over the binary's arguments and flags.
32    ///
33    /// # Errors
34    /// This function can result in an error if the length parameter could not be parsed into a
35    /// [`usize`](usize).
36    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, Box<dyn Error>> {
37        // Ignore the first argument
38        args.next();
39
40        let length = match args.next() {
41            Some(length) => length.parse()?,
42            None => 16,
43        };
44
45        Ok(Config { length })
46    }
47
48    /// Creates a [`Config`](Config) type. Recommended over [`Config::build`](Config::build) for
49    /// most use cases.
50    ///
51    /// Parameter:
52    /// - `length` - A [`usize`](usize) to act as the length of the password.
53    pub fn new(length: usize) -> Config {
54        Config { length }
55    }
56
57    /// Prints the configuration options to stderr.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// # use genpass3::*;
63    /// Config::print_config();
64    /// ```
65    pub fn print_config() {
66        eprint!(
67            "\
68Usage:
69    \x1B[01m{} <LENGTH>\x1B[00m\n
70The LENGTH is an optional parameter specifying the desired length of the password.\n
71Version: {}, {} License
72",
73            env!("CARGO_PKG_NAME"),
74            env!("CARGO_PKG_VERSION"),
75            env!("CARGO_PKG_LICENSE")
76        );
77    }
78}
79
80/// Runs the configured password generation
81///
82/// Parameter:
83/// - `config` - A [`Config`](Config) that contains the configuration for the function.
84///
85/// # Errors
86///
87/// This function can return errors if `/dev/urandom` does not exist.
88///
89/// # Example
90///
91/// ```
92/// # use genpass3::*;
93/// # let args = vec![
94/// #     String::from("program_name"),
95/// #     String::from("20"),
96/// # ];
97/// # let args = args.into_iter();
98/// let config = match Config::build(args) {
99///     Ok(config) => config,
100///     Err(error) => panic!("Failed to build `Config`: {}", error),
101/// };
102///
103/// genpass3::run(&config);
104/// ```
105pub fn run(config: &Config) -> Result<String, Box<dyn Error>> {
106    let mut password = vec![0u8; config.length];
107
108    {
109        // This is a special file available in Linux as a way to get random data.
110        // It's a device, it generates characters as we request it.
111        let dev_chars = File::open("/dev/urandom")?;
112        let mut reader = BufReader::with_capacity(config.length, dev_chars);
113
114        // Read all the data at once.
115        reader.read_exact(&mut password)?;
116    }
117
118    {
119        // We're bounding our characters within 32 and 127 in ASCII.
120        let min_ascii = 32;
121        let diff_ascii = 95;
122
123        // We need to iterate over a range of numbers because if we were to iterate over the
124        // elements of the Vec we would have mutable reference problems.
125        #[allow(clippy::needless_range_loop)]
126        for i in 0..config.length {
127            password[i] = password[i] % diff_ascii + min_ascii;
128        }
129    }
130
131    // Effectively transforms `Vec<u8>` into `Vec<char>` into `String`.
132    let password: String = password.into_iter().map(|c| c as char).collect();
133
134    Ok(password)
135}