summaryrefslogtreecommitdiffhomepage
path: root/mullvad-daemon/src/macos_launch_daemon.rs
blob: 3e10d94ce77410208cf49be92b0ecb83e4534d36 (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
//! Provides functions to handle or query the status of the Mullvad launch
//! daemon/system service on macOS.
//!
//! If the service exists but needs to be approved by the user, this status
//! must be checked so that the user can be directed to approve the launch
//! daemon in the system settings.

use objc::{class, msg_send, sel, sel_impl};
use std::ffi::CStr;

type Id = *mut objc::runtime::Object;

// Framework that contains `SMAppService`.
#[link(name = "ServiceManagement", kind = "framework")]
extern "C" {}

/// Returned by `[NSProcessInfo operatingSystemVersion]`. Contains the current
#[repr(C)]
#[derive(Debug)]
struct NSOperatingSystemVersion {
    major_version: libc::c_longlong,
    minor_version: libc::c_longlong,
    patch_version: libc::c_longlong,
}

/// Implement Objective-C type encoding for the struct. Allows the `objc` crate
/// to perform function signature matching before performing calls into the Objective-C
/// runtime. This is needed to be able to enable the `verify_message` feature on `objc`.
unsafe impl objc::Encode for NSOperatingSystemVersion {
    fn encode() -> objc::Encoding {
        unsafe { objc::Encoding::from_str("{?=qqq}") }
    }
}

/// Authorization status of the Mullvad daemon.
#[repr(i32)]
pub enum LaunchDaemonStatus {
    Ok = 0,
    NotFound = 1,
    NotAuthorized = 2,
    Unknown = 3,
}

/// Return whether the daemon is running, not found, or is not authorized.
/// NOTE: On macos < 13, this function always returns `LaunchDaemonStatus::Ok`.
pub fn get_status() -> LaunchDaemonStatus {
    // `SMAppService` does not exist if the major version is less than 13.
    if get_os_version().major_version < 13 {
        return LaunchDaemonStatus::Ok;
    }
    get_status_for_url(&daemon_plist_url())
}

fn get_status_for_url(url: &Object) -> LaunchDaemonStatus {
    let status: libc::c_long =
        unsafe { msg_send![class!(SMAppService), statusForLegacyURL: url.0] };

    match status {
        // SMAppServiceStatusNotRegistered | SMAppServiceStatusNotFound
        0 | 3 => LaunchDaemonStatus::NotFound,
        // SMAppServiceStatusEnabled
        1 => LaunchDaemonStatus::Ok,
        // SMAppServiceStatusRequiresApproval
        2 => LaunchDaemonStatus::NotAuthorized,
        // Unknown status
        _ => LaunchDaemonStatus::Unknown,
    }
}

fn get_os_version() -> NSOperatingSystemVersion {
    // the object is lazily instantiated, so we don't release it
    let proc_info: Id = unsafe { msg_send![class!(NSProcessInfo), processInfo] };
    unsafe { msg_send![proc_info, operatingSystemVersion] }
}

/// Returns an `NSURL` instance for `DAEMON_PLIST_PATH`.
fn daemon_plist_url() -> Object {
    /// Path to the plist that defines the Mullvad launch daemon.
    /// It must be kept in sync with the path defined in
    /// `dist-assets/pkg-scripts/postinstall`.
    const DAEMON_PLIST_PATH: &CStr = unsafe {
        CStr::from_bytes_with_nul_unchecked(b"/Library/LaunchDaemons/net.mullvad.daemon.plist\0")
    };

    let nsstr_inst: Id = unsafe { msg_send![class!(NSString), alloc] };
    let nsstr_inst: Id =
        unsafe { msg_send![nsstr_inst, initWithUTF8String: DAEMON_PLIST_PATH.as_ptr()] };

    let nsurl_inst: Id = unsafe { msg_send![class!(NSURL), alloc] };
    let nsurl_inst: Id = unsafe { msg_send![nsurl_inst, initWithString: nsstr_inst] };

    let _: () = unsafe { msg_send![nsstr_inst, release] };

    assert!(!nsurl_inst.is_null());

    Object(nsurl_inst)
}

/// Calls `[self.0 release]` when the wrapped instance is dropped.
struct Object(Id);

impl Drop for Object {
    fn drop(&mut self) {
        let _: () = unsafe { msg_send![self.0, release] };
    }
}