1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use eframe::egui;
use std::process::{Command, Stdio};

#[derive(PartialEq)]
enum Privilege {
    Root,
    User,
    Suid,
}

/// Check current privilege:
///  - If Root/Suid privilege -> donothing
///  - If User privilege -> password request and re-run the application.
pub fn privilege_request() {
    if get_privilege() == Privilege::User {
        let options = eframe::NativeOptions {
            viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
            ..Default::default()
        };

        // Init for password and notification string.
        let mut pwd = "".to_owned();
        let mut notification = "".to_owned();

        // Run the UI to get password from user input
        let _ = eframe::run_simple_native("privilege Request", options, move |ctx, _frame| {
            egui::CentralPanel::default().show(ctx, |ui| {
                ui.vertical(|ui| {
                    ui.horizontal(|ui| {
                        let name_label = ui.label("admin/root password: ");
                        let text_edit = ui
                            .add(egui::TextEdit::singleline(&mut pwd).password(true))
                            .labelled_by(name_label.id);
                        // Check if Enter is pressed while the TextEdit has focus
                        if text_edit.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
                            if verify_password(&pwd) {
                                let _ = run_with_privilege(pwd.clone());
                            } else {
                                notification = "failed to verify the password".to_owned();
                            }
                        }
                    });
                    ui.label(&notification);
                });
            });
        });
    }
}

/// Use getuid() and geteuid() to know current privilege
fn get_privilege() -> Privilege {
    let uid = unsafe { libc::getuid() };
    let euid = unsafe { libc::geteuid() };

    match (uid, euid) {
        (0, 0) => Privilege::Root,
        (_, 0) => Privilege::Suid,
        (_, _) => Privilege::User,
    }
}

/// Run a dummy command with the password first to verify
fn verify_password(password: &str) -> bool {
    let mut command = Command::new("sudo");
    let mut child = command
        .arg("-k") // Invalidate cached credentials
        .arg("-S") // Read password from stdin
        .arg("true") // Dummy command to verify password
        .stdin(Stdio::piped())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
        .expect("Failed to execute sudo");

    if let Some(mut stdin) = child.stdin.take() {
        use std::io::Write;
        writeln!(stdin, "{}", password).expect("Failed to write to stdin");
    }

    let ecode = child.wait().expect("Failed to wait on child");

    ecode.success()
}

/// Re-run the application with sudo password
fn run_with_privilege(pwd: String) -> std::io::Result<Privilege> {
    let current = get_privilege();
    match current {
        Privilege::Root => {
            return Ok(current);
        }
        Privilege::Suid => {
            unsafe {
                libc::setuid(0);
            }
            return Ok(current);
        }
        Privilege::User => {
            println!("Escalating privileges");
        }
    }
    let mut args: Vec<_> = std::env::args().collect();
    if let Some(absolute_path) = std::env::current_exe()
        .ok()
        .and_then(|p| p.to_str().map(|p| p.to_string()))
    {
        args[0] = absolute_path;
    }

    let mut command: Command = Command::new("sudo");
    let mut child = command
        .arg("-S")
        .args(args)
        .stdin(Stdio::piped())
        .spawn()
        .expect("failed to execute child");

    if let Some(mut stdin) = child.stdin.take() {
        use std::io::Write;
        writeln!(stdin, "{}", pwd).expect("Failed to write to stdin");
        // Close stdin to signal no more input will be provided
    }
    std::process::exit(0);
}