1
mod capability;
2
mod collection;
3
mod error;
4
#[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))]
5
mod gnome;
6
mod item;
7
mod pam_listener;
8
#[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))]
9
mod plasma;
10
mod prompt;
11
mod service;
12
mod session;
13
#[cfg(test)]
14
mod tests;
15

            
16
use std::{
17
    io::{IsTerminal, Read},
18
    path::Path,
19
};
20

            
21
use clap::Parser;
22
use service::Service;
23
use tokio::io::AsyncReadExt;
24

            
25
use crate::error::Error;
26

            
27
const BINARY_NAME: &str = env!("CARGO_BIN_NAME");
28

            
29
#[derive(Parser)]
30
#[command(version, about, long_about = None)]
31
struct Args {
32
    #[arg(
33
        short = 'l',
34
        long,
35
        default_value_t = false,
36
        help = "Read a password from stdin, and use it to unlock the login keyring."
37
    )]
38
    login: bool,
39
    #[arg(short, long, help = "Replace a running instance.")]
40
    replace: bool,
41
    #[arg(
42
        short = 'v',
43
        long = "verbose",
44
        help = "Print debug information during command processing."
45
    )]
46
    is_verbose: bool,
47
}
48

            
49
/// Whether the daemon should exit if the password provided for unlocking the
50
/// session keyring is incorrect.
51
enum ShouldErrorOut {
52
    Yes,
53
    No,
54
}
55

            
56
async fn inner_main(args: Args) -> Result<(), Error> {
57
    capability::drop_unnecessary_capabilities()?;
58

            
59
    let secret_info = if args.login {
60
        let mut stdin = std::io::stdin().lock();
61
        if stdin.is_terminal() {
62
            let password = rpassword::prompt_password("Enter the login password: ")?;
63
            if password.is_empty() {
64
                tracing::error!("Login password can't be empty.");
65
                return Err(Error::EmptyPassword);
66
            }
67

            
68
            Some((oo7::Secret::text(password), ShouldErrorOut::Yes))
69
        } else {
70
            let mut buff = vec![];
71
            stdin.read_to_end(&mut buff)?;
72

            
73
            Some((oo7::Secret::from(buff), ShouldErrorOut::No))
74
        }
75
    } else if let Ok(credential_dir) = std::env::var("CREDENTIALS_DIRECTORY") {
76
        // We try to unlock the login keyring with a system credential.
77
        let mut contents = Vec::new();
78
        let cred_path = Path::new(&credential_dir).join("oo7.keyring-encryption-password");
79

            
80
        match tokio::fs::File::open(&cred_path).await {
81
            Ok(mut cred_file) => {
82
                tracing::info!("Unlocking session keyring with user's systemd credentials");
83
                cred_file.read_to_end(&mut contents).await?;
84
                let secret = oo7::Secret::from(contents);
85
                Some((secret, ShouldErrorOut::No))
86
            }
87
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
88
            Err(err) => {
89
                tracing::error!("Failed to open system credential {err:?}");
90
                Err(err)?
91
            }
92
        }
93
    } else {
94
        None
95
    };
96

            
97
    tracing::info!("Starting {BINARY_NAME}");
98

            
99
    if let Some((secret, should_error_out)) = secret_info {
100
        let res = Service::run(Some(secret), args.replace).await;
101
        match res {
102
            Ok(()) => (),
103
            // Wrong password provided via system credentials
104
            Err(Error::File(oo7::file::Error::IncorrectSecret))
105
                if matches!(should_error_out, ShouldErrorOut::No) =>
106
            {
107
                tracing::warn!(
108
                    "Failed to unlock session keyring: credential contains wrong password"
109
                )
110
            }
111
            Err(Error::Zbus(zbus::Error::NameTaken)) if !args.replace => {
112
                tracing::error!(
113
                    "There is an instance already running. Run with --replace to replace it."
114
                );
115
                Err(Error::Zbus(zbus::Error::NameTaken))?
116
            }
117
            Err(err) => Err(err)?,
118
        }
119
    } else {
120
        Service::run(None, args.replace).await?;
121
    }
122

            
123
    tracing::debug!("Starting loop");
124

            
125
    std::future::pending::<()>().await;
126

            
127
    Ok(())
128
}
129

            
130
#[tokio::main]
131
async fn main() -> Result<(), Error> {
132
    let args = Args::parse();
133

            
134
    if args.is_verbose {
135
        tracing_subscriber::fmt()
136
            .with_max_level(tracing_subscriber::filter::LevelFilter::DEBUG)
137
            .init();
138
        tracing::debug!("Running in verbose mode");
139
    } else {
140
        tracing_subscriber::fmt::init();
141
    }
142

            
143
    inner_main(args).await.inspect_err(|err| {
144
        tracing::error!("{err:#}");
145
    })
146
}