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

            
13
use std::{
14
    io::{IsTerminal, Read},
15
    path::Path,
16
};
17

            
18
use clap::Parser;
19
use service::Service;
20
use tokio::io::AsyncReadExt;
21

            
22
use crate::error::Error;
23

            
24
const BINARY_NAME: &str = env!("CARGO_BIN_NAME");
25

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

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

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

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

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

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

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

            
94
    tracing::info!("Starting {BINARY_NAME}");
95

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

            
120
    tracing::debug!("Starting loop");
121

            
122
    std::future::pending::<()>().await;
123

            
124
    Ok(())
125
}
126

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

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

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