1
mod capability;
2
mod collection;
3
mod error;
4
#[allow(unused)]
5
mod gnome;
6
mod i18n;
7
mod item;
8
mod pam_listener;
9
mod prompt;
10
mod service;
11
mod session;
12
#[cfg(test)]
13
mod tests;
14

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

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

            
24
use crate::error::Error;
25

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

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

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

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

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

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

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

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

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

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

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

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

            
126
    Ok(())
127
}
128

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

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

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