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//! ```Bash
44//! ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID
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    // machineID returns the uuid returned by `ioreg -rd1 -c IOPlatformExpertDevice`.
135    use std::error::Error;
136    use std::process::Command;
137
138    /// Return machine id
139    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
140        let output = Command::new("ioreg")
141            .args(&["-rd1", "-c", "IOPlatformExpertDevice"])
142            .output()?;
143        let content = String::from_utf8_lossy(&output.stdout);
144        extract_id(&content)
145    }
146
147    fn extract_id(content: &str) -> Result<String, Box<dyn Error>> {
148        let lines = content.split('\n');
149        for line in lines {
150            if line.contains("IOPlatformUUID") {
151                let k: Vec<&str> = line.rsplitn(2, '=').collect();
152                let id = k[0].trim_matches(|c: char| c == '"' || c.is_whitespace());
153                return Ok(id.to_string());
154            }
155        }
156        Err(From::from(
157            "No matching IOPlatformUUID in `ioreg -rd1 -c IOPlatformExpertDevice` command.",
158        ))
159    }
160}
161
162#[cfg(target_os = "windows")]
163pub mod machine_id {
164    use std::error::Error;
165    use std::ffi::c_void;
166    use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY};
167    use winreg::RegKey;
168
169    type BOOL = i32;
170    type HANDLE = *mut c_void;
171    type PBOOL = *mut BOOL;
172
173    extern "system" {
174        fn GetModuleHandleA(lpModuleName: *const u8) -> HANDLE;
175        fn GetProcAddress(hModule: HANDLE, lpProcName: *const u8) -> *const c_void;
176        fn GetCurrentProcess() -> HANDLE;
177    }
178
179    const KERNEL32: *const u8 = b"kernel32.dll\0".as_ptr();
180    const ISWOW64PROCESS: *const u8 = b"IsWow64Process\0".as_ptr();
181
182    type LpfnIswow64process = unsafe extern "system" fn(HANDLE, PBOOL) -> BOOL;
183
184    fn machine_uid_is_wow64() -> bool {
185        unsafe {
186            let mut b_is_wow64: BOOL = 0;
187            let h_module = GetModuleHandleA(KERNEL32);
188            let fn_is_wow64_process: Option<LpfnIswow64process> =
189                std::mem::transmute(GetProcAddress(h_module, ISWOW64PROCESS));
190
191            if let Some(fn_is_wow64_process) = fn_is_wow64_process {
192                if fn_is_wow64_process(GetCurrentProcess(), &mut b_is_wow64) == 0 {
193                    // Handle error if needed
194                }
195            }
196            b_is_wow64 == 1
197        }
198    }
199
200    /// Return machine id
201    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
202        let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
203
204        let flag = if machine_uid_is_wow64() && cfg!(target_pointer_width = "32") {
205            KEY_READ | KEY_WOW64_64KEY
206        } else {
207            KEY_READ
208        };
209
210        let crypto = hklm.open_subkey_with_flags("SOFTWARE\\Microsoft\\Cryptography", flag)?;
211        let id: String = crypto.get_value("MachineGuid")?;
212
213        Ok(id.trim().to_string())
214    }
215}
216
217#[cfg(target_os = "illumos")]
218pub mod machine_id {
219    use std::error::Error;
220
221    /// Return machine id
222    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
223        Ok(format!("{:x}", unsafe { libc::gethostid() }))
224    }
225}
226
227pub use machine_id::get_machine_id as get;