nuts_tool/cli/
password.rs

1// MIT License
2//
3// Copyright (c) 2023,2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23use lazy_static::lazy_static;
24use rpassword::prompt_password;
25use std::fs::File;
26use std::io::{self, BufRead, BufReader};
27use std::os::fd::FromRawFd;
28
29use crate::cli::global::{PasswordSource, GLOBALS};
30
31fn ask_for_password() -> Result<Vec<u8>, String> {
32    lazy_static! {
33        static ref RESULT: Result<String, String> =
34            prompt_password("Enter a password: ").map_err(|err| err.to_string());
35    }
36
37    match RESULT.as_ref() {
38        Ok(s) => Ok(s.as_bytes().to_vec()),
39        Err(s) => Err(s.clone()),
40    }
41}
42
43fn ask_for_password_twice(prompt: &str) -> Result<Vec<u8>, String> {
44    let pass1 = prompt_password(format!("{}: ", prompt)).map_err(|err| err.to_string())?;
45    let pass2 = prompt_password(format!("{} (repeat): ", prompt)).map_err(|err| err.to_string())?;
46
47    if pass1 == pass2 {
48        Ok(pass1.as_bytes().to_vec())
49    } else {
50        Err("The passwords do not match".to_string())
51    }
52}
53
54fn password_from_file(file: File) -> io::Result<Vec<u8>> {
55    let mut reader = BufReader::new(file);
56    let mut buf = vec![];
57
58    reader.read_until(0x0a, &mut buf)?;
59
60    if let Some(n) = buf.last() {
61        if *n == 0x0a {
62            buf.pop();
63        }
64    }
65
66    Ok(buf)
67}
68
69fn password_from_source_or<F: FnOnce() -> Result<Vec<u8>, String>>(
70    source: &PasswordSource,
71    f: F,
72) -> Result<Vec<u8>, String> {
73    let file = match source {
74        PasswordSource::Fd(fd) => unsafe { Some(Ok(File::from_raw_fd(*fd))) },
75        PasswordSource::Path(path) => Some(File::open(path)),
76        PasswordSource::Console => None,
77    };
78
79    match file {
80        Some(Ok(f)) => password_from_file(f).map_err(|err| err.to_string()),
81        Some(Err(err)) => Err(err.to_string()),
82        None => f(),
83    }
84}
85
86pub fn password_from_source() -> Result<Vec<u8>, String> {
87    GLOBALS.with_borrow(|g| password_from_source_or(&g.password_source, ask_for_password))
88}
89
90pub fn password_from_source_twice(
91    source: &PasswordSource,
92    prompt: &str,
93) -> Result<Vec<u8>, String> {
94    password_from_source_or(source, || ask_for_password_twice(prompt))
95}