1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// MIT License - Copyright (c) 2022 Nicolás Castellán
// SPDX License identifier: MIT
// THE SOFTWARE IS PROVIDED "AS IS"
// Read the included LICENSE file for more information

//! # genpass3
//!
//! `genpass3` is a library and binary crate that allows Linux users to generate very long & secure
//! passwords at incredible speeds using `/dev/urandom`.

use std::{
    error::Error,
    fs::File,
    io::{BufReader, Read},
};

/// The config for the generation of passwords. Taken as an argument by the [`run`](run) function.
///
/// You can create an instance of this struct using [`Config::new`](Config::new) or
/// [`Config::build`](Config::build).
pub struct Config {
    length: usize,
}

impl Config {
    /// Creates a [`Config`](Config) type using the command line arguments of the binary, passed in
    /// as arguments. **Assumes** the first iteration of `args` is the program name, so it's
    /// ignored.
    ///
    /// Parameter:
    /// - `args` - An iterator, meant to iterate over the binary's arguments and flags.
    ///
    /// # Errors
    /// This function can result in an error if the length parameter could not be parsed into a
    /// [`usize`](usize).
    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, Box<dyn Error>> {
        // Ignore the first argument
        args.next();

        let length = match args.next() {
            Some(length) => length.parse()?,
            None => 16,
        };

        Ok(Config { length })
    }

    /// Creates a [`Config`](Config) type. Recommended over [`Config::build`](Config::build) for
    /// most use cases.
    ///
    /// Parameter:
    /// - `length` - A [`usize`](usize) to act as the length of the password.
    pub fn new(length: usize) -> Config {
        Config { length }
    }

    /// Prints the configuration options to stderr.
    ///
    /// # Example
    ///
    /// ```
    /// # use genpass3::*;
    /// Config::print_config();
    /// ```
    pub fn print_config() {
        eprint!(
            "\
Usage:
    \x1B[01m{} <LENGTH>\x1B[00m\n
The LENGTH is an optional parameter specifying the desired length of the password.\n
Version: {}, {} License
",
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_VERSION"),
            env!("CARGO_PKG_LICENSE")
        );
    }
}

/// Runs the configured password generation
///
/// Parameter:
/// - `config` - A [`Config`](Config) that contains the configuration for the function.
///
/// # Errors
///
/// This function can return errors if `/dev/urandom` does not exist.
///
/// # Example
///
/// ```
/// # use genpass3::*;
/// # let args = vec![
/// #     String::from("program_name"),
/// #     String::from("20"),
/// # ];
/// # let args = args.into_iter();
/// let config = match Config::build(args) {
///     Ok(config) => config,
///     Err(error) => panic!("Failed to build `Config`: {}", error),
/// };
///
/// genpass3::run(&config);
/// ```
pub fn run(config: &Config) -> Result<String, Box<dyn Error>> {
    let mut password = vec![0u8; config.length];

    {
        // This is a special file available in Linux as a way to get random data.
        // It's a device, it generates characters as we request it.
        let dev_chars = File::open("/dev/urandom")?;
        let mut reader = BufReader::with_capacity(config.length, dev_chars);

        // Read all the data at once.
        reader.read_exact(&mut password)?;
    }

    {
        // We're bounding our characters within 32 and 127 in ASCII.
        let min_ascii = 32;
        let diff_ascii = 95;

        // We need to iterate over a range of numbers because if we were to iterate over the
        // elements of the Vec we would have mutable reference problems.
        #[allow(clippy::needless_range_loop)]
        for i in 0..config.length {
            password[i] = password[i] % diff_ascii + min_ascii;
        }
    }

    // Effectively transforms `Vec<u8>` into `Vec<char>` into `String`.
    let password: String = password.into_iter().map(|c| c as char).collect();

    Ok(password)
}