summaryrefslogtreecommitdiffhomepage
path: root/test/test-runner/src/app.rs
blob: 009638c315159232784df6a2a4bf76fe86d0520d (plain)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use chrono::{DateTime, Utc};
use std::path::{Path, PathBuf};

use test_rpc::{AppTrace, Error};

/// Get the installed app version string
pub async fn version() -> Result<String, Error> {
    // The `mullvad` binary is seemingly not in PATH on Windows after upgrading the app..
    // So, as a workaround we use the absolute path instead.
    const MULLVAD_CLI_BIN: &str = if cfg!(target_os = "windows") {
        r"C:\Program Files\Mullvad VPN\resources\mullvad.exe"
    } else {
        "mullvad"
    };
    let version = tokio::process::Command::new(MULLVAD_CLI_BIN)
        .arg("--version")
        .output()
        .await
        .map_err(|e| Error::ServiceNotFound(e.to_string()))?;
    let version = String::from_utf8(version.stdout).map_err(|err| Error::Other(err.to_string()))?;
    // HACK: The output from `mullvad --version` includes the `mullvad-cli` binary name followed by
    // the version string. Simply remove the leading noise and get at the version string.
    let Some(version) = version.split_whitespace().nth(1) else {
        return Err(Error::Other(
            "Could not parse version number from `mullvad-cli --version`".to_string(),
        ));
    };
    let version = version.to_string();
    Ok(version)
}

#[cfg(target_os = "windows")]
pub fn find_traces() -> Result<Vec<AppTrace>, Error> {
    // TODO: Check GUI data
    // TODO: Check temp data
    // TODO: Check devices and drivers

    let settings_dir = mullvad_paths::get_default_settings_dir().map_err(|error| {
        log::error!("Failed to obtain system app data: {error}");
        Error::Syscall
    })?;

    let caches = find_cache_traces()?;
    let traces = vec![
        Path::new(r"C:\Program Files\Mullvad VPN"),
        // NOTE: This only works as of `499c06decda37dc639e5f` in the Mullvad app.
        // Older builds have no way of silently fully uninstalling the app.
        Path::new(r"C:\ProgramData\Mullvad VPN"),
        // NOTE: Works as of `4116ebc` (Mullvad app).
        &settings_dir,
        &caches,
    ];

    Ok(existing_paths(&traces))
}

#[cfg(target_os = "linux")]
pub fn find_traces() -> Result<Vec<AppTrace>, Error> {
    // TODO: Check GUI data
    // TODO: Check temp data

    let caches = find_cache_traces()?;
    let traces = vec![
        Path::new(r"/etc/mullvad-vpn/"),
        Path::new(r"/var/log/mullvad-vpn/"),
        &caches,
        Path::new(r"/opt/Mullvad VPN/"),
        // management interface socket
        Path::new(r"/var/run/mullvad-vpn"),
        // service unit config files
        Path::new(r"/usr/lib/systemd/system/mullvad-daemon.service"),
        Path::new(r"/usr/lib/systemd/system/mullvad-early-boot-blocking.service"),
        Path::new(r"/usr/bin/mullvad"),
        Path::new(r"/usr/bin/mullvad-daemon"),
        Path::new(r"/usr/bin/mullvad-exclude"),
        Path::new(r"/usr/bin/mullvad-problem-report"),
        Path::new(r"/usr/share/bash-completion/completions/mullvad"),
        Path::new(r"/usr/local/share/zsh/site-functions/_mullvad"),
        Path::new(r"/usr/share/fish/vendor_completions.d/mullvad.fish"),
    ];

    Ok(existing_paths(&traces))
}

pub fn find_cache_traces() -> Result<PathBuf, Error> {
    mullvad_paths::get_cache_dir().map_err(|error| Error::FileSystem(error.to_string()))
}

#[cfg(target_os = "macos")]
pub fn find_traces() -> Result<Vec<AppTrace>, Error> {
    // TODO: Check GUI data
    // TODO: Check temp data

    let caches = find_cache_traces()?;
    let traces = vec![
        Path::new(r"/Applications/Mullvad VPN.app/"),
        Path::new(r"/var/log/mullvad-vpn/"),
        &caches,
        // management interface socket
        Path::new(r"/var/run/mullvad-vpn"),
        // launch daemon
        Path::new(r"/Library/LaunchDaemons/net.mullvad.daemon.plist"),
        Path::new(r"/usr/local/bin/mullvad"),
        Path::new(r"/usr/local/bin/mullvad-problem-report"),
        // completions
        Path::new(r"/usr/local/share/zsh/site-functions/_mullvad"),
        Path::new(r"/opt/homebrew/share/fish/vendor_completions.d/mullvad.fish"),
        Path::new(r"/usr/local/share/fish/vendor_completions.d/mullvad.fish"),
    ];

    Ok(existing_paths(&traces))
}

/// Find all present app traces on the test runner.
fn existing_paths(paths: &[&Path]) -> Vec<AppTrace> {
    paths
        .iter()
        .filter(|&path| path.try_exists().is_ok_and(|exists| exists))
        .map(|path| AppTrace::Path(path.to_path_buf()))
        .collect()
}

pub async fn make_device_json_old() -> Result<(), Error> {
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    const DEVICE_JSON_PATH: &str = "/etc/mullvad-vpn/device.json";
    #[cfg(target_os = "windows")]
    const DEVICE_JSON_PATH: &str =
        "C:\\Windows\\system32\\config\\systemprofile\\AppData\\Local\\Mullvad VPN\\device.json";
    let device_json = tokio::fs::read_to_string(DEVICE_JSON_PATH)
        .await
        .map_err(|e| Error::FileSystem(e.to_string()))?;

    let mut device_state: serde_json::Value =
        serde_json::from_str(&device_json).map_err(|e| Error::FileSerialization(e.to_string()))?;
    let created_ref: &mut serde_json::Value = device_state
        .get_mut("logged_in")
        .unwrap()
        .get_mut("device")
        .unwrap()
        .get_mut("wg_data")
        .unwrap()
        .get_mut("created")
        .unwrap();
    let created: DateTime<Utc> = serde_json::from_value(created_ref.clone()).unwrap();
    let created = created
        .checked_sub_signed(chrono::Duration::days(365))
        .unwrap();

    *created_ref = serde_json::to_value(created).unwrap();

    let device_json = serde_json::to_string(&device_state)
        .map_err(|e| Error::FileSerialization(e.to_string()))?;
    tokio::fs::write(DEVICE_JSON_PATH, device_json.as_bytes())
        .await
        .map_err(|e| Error::FileSystem(e.to_string()))?;

    Ok(())
}