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}