Skip to main content

machine_uid/
lib.rs

1// See README.md and LICENSE for more details.
2
3//! Get os native machine id without root permission.
4
5//! ## About machine id
6//!
7//! In Linux, machine id is a single newline-terminated, hexadecimal, 32-character, lowercase ID.
8//! When decoded from hexadecimal, this corresponds to a 16-byte/128-bit value.
9//! This ID may not be all zeros.
10//! This ID uniquely identifies the host. It should be considered "confidential",
11//! and must not be exposed in untrusted environments.
12//! And do note that the machine id can be re-generated by root.
13//!
14//! ## Usage
15//!
16//! ```Rust
17//! extern crate machine_uid;
18//!
19//! fn main() {
20//!     let id: String = machine_uid::get().unwrap();
21//!     println!("{}", id);
22//! }
23//! ```
24//!
25//! ## How it works
26//!
27//! It get machine id from following source:
28//!
29//! Linux or who use systemd:
30//!
31//! ```Bash
32//! cat /var/lib/dbus/machine-id # or /etc/machine-id
33//! ```
34//!
35//! BSD:
36//!
37//! ```Bash
38//! cat /etc/hostid # or kenv -q smbios.system.uuid
39//! ```
40//!
41//! OSX:
42//!
43//! ```C
44//! gethostuuid(3) // same value as `ioreg -rd1 -c IOPlatformExpertDevice`
45//! ```
46//!
47//! Windows:
48//!
49//! ```powershell
50//! (Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography).MachineGuid
51//! ```
52//!
53//! illumos:
54//!
55//! ```Bash
56//! gethostid(3C)
57//! ```
58//!
59//! ## Supported Platform
60//!
61//! I have tested in following platform:
62//!
63//! - Debian 8
64//! - OS X 10.6
65//! - FreeBSD 10.4
66//! - Fedora 28
67//! - Windows 10
68//! - OmniOS r151050
69//!
70
71use std::error::Error;
72use std::fs::File;
73use std::io::prelude::*;
74
75#[allow(dead_code)]
76fn read_file(file_path: &str) -> Result<String, Box<dyn Error>> {
77    let mut fd = File::open(file_path)?;
78    let mut content = String::new();
79    fd.read_to_string(&mut content)?;
80    Ok(content.trim().to_string())
81}
82
83#[cfg(target_os = "linux")]
84pub mod machine_id {
85    use super::read_file;
86    use std::error::Error;
87
88    // dbusPath is the default path for dbus machine id.
89    const DBUS_PATH: &str = "/var/lib/dbus/machine-id";
90    // or when not found (e.g. Fedora 20)
91    const DBUS_PATH_ETC: &str = "/etc/machine-id";
92
93    /// Return machine id
94    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
95        match read_file(DBUS_PATH) {
96            Ok(machine_id) => Ok(machine_id),
97            Err(_) => Ok(read_file(DBUS_PATH_ETC)?),
98        }
99    }
100}
101
102#[cfg(any(
103    target_os = "freebsd",
104    target_os = "dragonfly",
105    target_os = "openbsd",
106    target_os = "netbsd"
107))]
108pub mod machine_id {
109    use super::read_file;
110    use std::error::Error;
111    use std::process::Command;
112
113    const HOST_ID_PATH: &str = "/etc/hostid";
114
115    /// Return machine id
116    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
117        match read_file(HOST_ID_PATH) {
118            Ok(machine_id) => Ok(machine_id),
119            Err(_) => Ok(read_from_kenv()?),
120        }
121    }
122
123    fn read_from_kenv() -> Result<String, Box<dyn Error>> {
124        let output = Command::new("kenv")
125            .args(&["-q", "smbios.system.uuid"])
126            .output()?;
127        let content = String::from_utf8_lossy(&output.stdout);
128        Ok(content.trim().to_string())
129    }
130}
131
132#[cfg(target_os = "macos")]
133mod machine_id {
134    // Returns the same UUID exposed by `ioreg -rd1 -c IOPlatformExpertDevice`
135    // (i.e. `IOPlatformUUID`), but via the BSD `gethostuuid(3)` syscall.
136    //
137    // Both surfaces are fronted by the same kernel data, so the value is
138    // bit-for-bit identical (same hyphenation, same uppercase hex). Going
139    // through libc avoids a fork+exec of `ioreg` per call, which on macOS
140    // costs ~90ms (subprocess + dyld + IOKit init) versus ~10µs for the
141    // syscall.
142    use std::error::Error;
143    use std::io;
144    #[cfg(test)]
145    use std::process::Command;
146
147    /// Return machine id
148    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
149        let mut uuid: [u8; 16] = [0; 16];
150        // A zero `timespec` requests an indefinite wait. In practice the
151        // call returns immediately; the timeout only matters when the
152        // hostuuid has not yet been initialized very early in boot.
153        let wait = libc::timespec {
154            tv_sec: 0,
155            tv_nsec: 0,
156        };
157        let rc = unsafe { libc::gethostuuid(uuid.as_mut_ptr(), &wait) };
158        if rc != 0 {
159            return Err(Box::new(io::Error::last_os_error()));
160        }
161        Ok(format!(
162            "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
163            uuid[0],
164            uuid[1],
165            uuid[2],
166            uuid[3],
167            uuid[4],
168            uuid[5],
169            uuid[6],
170            uuid[7],
171            uuid[8],
172            uuid[9],
173            uuid[10],
174            uuid[11],
175            uuid[12],
176            uuid[13],
177            uuid[14],
178            uuid[15],
179        ))
180    }
181
182    /// Return machine id using the legacy `ioreg`-based implementation.
183    #[cfg(test)]
184    fn get_machine_id_legacy() -> Result<String, Box<dyn Error>> {
185        let output = Command::new("ioreg")
186            .args(["-rd1", "-c", "IOPlatformExpertDevice"])
187            .output()?;
188        let content = String::from_utf8_lossy(&output.stdout);
189        extract_id(&content)
190    }
191
192    #[cfg(test)]
193    fn extract_id(content: &str) -> Result<String, Box<dyn Error>> {
194        let lines = content.split('\n');
195        for line in lines {
196            if line.contains("IOPlatformUUID") {
197                let k: Vec<&str> = line.rsplitn(2, '=').collect();
198                let id = k[0].trim_matches(|c: char| c == '"' || c.is_whitespace());
199                return Ok(id.to_string());
200            }
201        }
202        Err(From::from(
203            "No matching IOPlatformUUID in `ioreg -rd1 -c IOPlatformExpertDevice` command.",
204        ))
205    }
206
207    #[test]
208    fn test_macos_get_matches_legacy_ioreg() {
209        let id = get_machine_id().unwrap();
210        let legacy_id = get_machine_id_legacy().unwrap();
211
212        assert_eq!(id, legacy_id);
213    }
214}
215
216#[cfg(target_os = "windows")]
217pub mod machine_id {
218    use std::error::Error;
219
220    use windows_registry::LOCAL_MACHINE;
221    use windows_sys::Win32::Foundation::GetLastError;
222    use windows_sys::Win32::System::Registry::{KEY_READ, KEY_WOW64_64KEY};
223    use windows_sys::Win32::System::Threading::{GetCurrentProcess, IsWow64Process};
224
225    fn machine_uid_is_wow64() -> Result<bool, Box<dyn Error>> {
226        unsafe {
227            let mut is_wow64: i32 = 0;
228            if IsWow64Process(GetCurrentProcess(), &mut is_wow64) == 0 {
229                return Err(From::from(format!("Failed to determine whether the specified process is running under WOW64 or an Intel64 of x64 processor: {}", GetLastError())));
230            }
231
232            Ok(is_wow64 == 1)
233        }
234    }
235
236    /// Return machine id
237    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
238        let flag = if machine_uid_is_wow64()? && cfg!(target_pointer_width = "32") {
239            KEY_READ | KEY_WOW64_64KEY
240        } else {
241            KEY_READ
242        };
243
244        let key = LOCAL_MACHINE
245            .options()
246            .access(flag)
247            .open("SOFTWARE\\Microsoft\\Cryptography")?;
248        let id = key.get_string("MachineGuid")?;
249
250        Ok(id.trim().to_string())
251    }
252}
253
254#[cfg(target_os = "illumos")]
255pub mod machine_id {
256    use std::error::Error;
257
258    /// Return machine id
259    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
260        Ok(format!("{:x}", unsafe { libc::gethostid() }))
261    }
262}
263
264pub use machine_id::get_machine_id as get;