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
166    use windows_registry::LOCAL_MACHINE;
167    use windows_sys::Win32::Foundation::GetLastError;
168    use windows_sys::Win32::System::Registry::{KEY_READ, KEY_WOW64_64KEY};
169    use windows_sys::Win32::System::Threading::{GetCurrentProcess, IsWow64Process};
170
171    fn machine_uid_is_wow64() -> Result<bool, Box<dyn Error>> {
172        unsafe {
173            let mut is_wow64: i32 = 0;
174            if IsWow64Process(GetCurrentProcess(), &mut is_wow64) == 0 {
175                return Err(From::from(format!("Failed to determine whether the specified process is running under WOW64 or an Intel64 of x64 processor: {}", GetLastError())));
176            }
177
178            Ok(is_wow64 == 1)
179        }
180    }
181
182    /// Return machine id
183    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
184        let flag = if machine_uid_is_wow64()? && cfg!(target_pointer_width = "32") {
185            KEY_READ | KEY_WOW64_64KEY
186        } else {
187            KEY_READ
188        };
189
190        let key = LOCAL_MACHINE
191            .options()
192            .access(flag)
193            .open("SOFTWARE\\Microsoft\\Cryptography")?;
194        let id = key.get_string("MachineGuid")?;
195
196        Ok(id.trim().to_string())
197    }
198}
199
200#[cfg(target_os = "illumos")]
201pub mod machine_id {
202    use std::error::Error;
203
204    /// Return machine id
205    pub fn get_machine_id() -> Result<String, Box<dyn Error>> {
206        Ok(format!("{:x}", unsafe { libc::gethostid() }))
207    }
208}
209
210pub use machine_id::get_machine_id as get;