passgen_cli/
lib.rs

1use clap::{Args, Parser, ValueEnum};
2use passgen::random::Random;
3use std::{path::PathBuf, str::FromStr};
4
5#[derive(Clone)]
6pub struct WordlistSpec {
7    pub name: String,
8    pub path: PathBuf,
9}
10
11#[derive(thiserror::Error, Debug)]
12pub enum WordlistSpecParseError {
13    #[error("missing colon")]
14    MissingColon,
15}
16
17impl FromStr for WordlistSpec {
18    type Err = WordlistSpecParseError;
19
20    fn from_str(input: &str) -> Result<Self, Self::Err> {
21        let (name, path) = input
22            .split_once(':')
23            .ok_or(WordlistSpecParseError::MissingColon)?;
24        Ok(Self {
25            name: name.into(),
26            path: path.into(),
27        })
28    }
29}
30
31#[derive(ValueEnum, Clone)]
32pub enum Format {
33    Text,
34    Json,
35    Csv,
36}
37
38/// Generate random sequences from a regex-like pattern.
39#[derive(Parser)]
40#[command(author, version)]
41pub struct Options {
42    /// Use the given named preset instead of specifying a pattern.
43    #[clap(short, long)]
44    pub preset: Option<String>,
45
46    /// Amount of sequences to generate.
47    #[clap(short, long, default_value = "1")]
48    pub amount: usize,
49
50    /// Source of randomness to use.
51    ///
52    /// Possible sources are:
53    ///
54    /// `system`: use the secure system random number generator (default)
55    ///
56    /// `null`: use a dummy randomness source (insecure)
57    ///
58    /// `xoshiro:<seed>`: use the xoshiro randomness source with the given seed (insecure)
59    #[clap(short, long)]
60    pub random: Option<Random>,
61
62    #[clap(flatten)]
63    pub master: MasterOptions,
64
65    /// Output format to use when emitting generated sequences.
66    ///
67    /// This defaults to a text-based format, but you can also specify 'json' or 'csv'.
68    #[clap(short, long, default_value = "text")]
69    pub format: Format,
70
71    /// Amount of threads to use, enables multi-threaded generation.
72    #[clap(short = 'j', long)]
73    pub threads: Option<usize>,
74
75    /// Enable debug log output.
76    #[clap(short, long)]
77    pub verbose: Option<usize>,
78
79    /// Load wordlist for generating wordlist or markov-chain based words.
80    ///
81    /// The format of this is `<name>:<path>`. For example, to load a wordlist with the name
82    /// `english` that is located at `/usr/share/dict/words`, specify `-w
83    /// english:/usr/share/dict/words`.
84    #[clap(short, long)]
85    pub wordlist: Vec<WordlistSpec>,
86
87    #[clap(required_unless_present("preset"))]
88    pub pattern: Option<String>,
89
90    /// Load configuration file.
91    pub config: Vec<PathBuf>,
92}
93
94/// Options for master-pass mode.
95#[derive(Args)]
96#[group()]
97pub struct MasterOptions {
98    /// Set the master passphrase to use for generating passwords.
99    ///
100    /// In master passphrase mode, Passgen uses the master passphrase you supply here to generate
101    /// deterministic outputs. With this functionality, you can use Passgen as a password manager,
102    /// only having to remember a single master passphrase and using it to generate unique
103    /// passphrases per domain and login.
104    ///
105    /// This also enables the master-passphrase randomness source. You cannot combine this with
106    /// any other randomness source.
107    #[clap(short, long, conflicts_with("random"))]
108    pub master: Option<String>,
109
110    /// Set the domain for the master passphrase mode.
111    ///
112    /// Requires that you specify a master passphrase.
113    #[clap(short, long, requires("master"))]
114    pub domain: Option<String>,
115
116    /// Set the token for the master passphrase.
117    ///
118    /// This allows you to generate multiple, unique passphrases for a single domain, for example
119    /// if you have multiple accounts.
120    #[clap(short, long, requires("master"))]
121    pub token: Option<String>,
122}
123
124impl Options {
125    pub fn random(&self) -> Random {
126        self.master
127            .random()
128            .or(self.random.clone())
129            .unwrap_or_default()
130    }
131}
132
133impl MasterOptions {
134    pub fn random(&self) -> Option<Random> {
135        self.master
136            .as_deref()
137            .map(|master| Random::from_master_pass(master, self.domain.as_deref().unwrap_or("")))
138    }
139}