1 // WhoAmI
2 // Copyright © 2017-2021 Jeron Aldaron Lau.
3 //
4 // Licensed under any of:
5 //  - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
6 //  - MIT License (https://mit-license.org/)
7 //  - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt)
8 // At your choosing (See accompanying files LICENSE_APACHE_2_0.txt,
9 // LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt).
10 
11 use crate::{DesktopEnv, Platform};
12 
13 use std::convert::TryInto;
14 use std::ffi::OsString;
15 use std::os::raw::{c_char, c_int, c_uchar, c_ulong, c_ushort};
16 use std::os::windows::ffi::OsStringExt;
17 use std::ptr;
18 
19 #[repr(C)]
20 struct OsVersionInfoEx {
21     os_version_info_size: c_ulong,
22     major_version: c_ulong,
23     minor_version: c_ulong,
24     build_number: c_ulong,
25     platform_id: c_ulong,
26     sz_csd_version: [u16; 128],
27     service_pack_major: c_ushort,
28     service_pack_minor: c_ushort,
29     suite_mask: c_ushort,
30     product_type: c_uchar,
31     reserved: c_uchar,
32 }
33 
34 #[allow(unused)]
35 #[repr(C)]
36 enum ExtendedNameFormat {
37     Unknown,          // Nothing
38     FullyQualifiedDN, // Nothing
39     SamCompatible,    // Hostname Followed By Username
40     Display,          // Full Name
41     UniqueId,         // Nothing
42     Canonical,        // Nothing
43     UserPrincipal,    // Nothing
44     CanonicalEx,      // Nothing
45     ServicePrincipal, // Nothing
46     DnsDomain,        // Nothing
47     GivenName,        // Nothing
48     Surname,          // Nothing
49 }
50 
51 #[allow(unused)]
52 #[repr(C)]
53 enum ComputerNameFormat {
54     NetBIOS,                   // Same as GetComputerNameW
55     DnsHostname,               // Fancy Name
56     DnsDomain,                 // Nothing
57     DnsFullyQualified,         // Fancy Name with, for example, .com
58     PhysicalNetBIOS,           // Same as GetComputerNameW
59     PhysicalDnsHostname,       // Same as GetComputerNameW
60     PhysicalDnsDomain,         // Nothing
61     PhysicalDnsFullyQualified, // Fancy Name with, for example, .com
62     Max,
63 }
64 
65 #[link(name = "secur32")]
66 extern "system" {
GetLastError() -> c_ulong67     fn GetLastError() -> c_ulong;
GetUserNameExW( a: ExtendedNameFormat, b: *mut c_char, c: *mut c_ulong, ) -> c_uchar68     fn GetUserNameExW(
69         a: ExtendedNameFormat,
70         b: *mut c_char,
71         c: *mut c_ulong,
72     ) -> c_uchar;
GetUserNameW(a: *mut c_char, b: *mut c_ulong) -> c_int73     fn GetUserNameW(a: *mut c_char, b: *mut c_ulong) -> c_int;
GetComputerNameExW( a: ComputerNameFormat, b: *mut c_char, c: *mut c_ulong, ) -> c_int74     fn GetComputerNameExW(
75         a: ComputerNameFormat,
76         b: *mut c_char,
77         c: *mut c_ulong,
78     ) -> c_int;
79 }
80 
81 #[link(name = "ntdll")]
82 extern "system" {
RtlGetVersion(a: *mut OsVersionInfoEx) -> u3283     fn RtlGetVersion(a: *mut OsVersionInfoEx) -> u32;
84 }
85 
86 #[link(name = "kernel32")]
87 extern "system" {
GetUserPreferredUILanguages( dw_flags: c_ulong, pul_num_languages: *mut c_ulong, pwsz_languages_buffer: *mut u16, pcch_languages_buffer: *mut c_ulong, ) -> c_int88     fn GetUserPreferredUILanguages(
89         dw_flags: c_ulong,
90         pul_num_languages: *mut c_ulong,
91         pwsz_languages_buffer: *mut u16,
92         pcch_languages_buffer: *mut c_ulong,
93     ) -> c_int;
94 }
95 
96 // Convert an OsString into a String
string_from_os(string: OsString) -> String97 fn string_from_os(string: OsString) -> String {
98     match string.into_string() {
99         Ok(string) => string,
100         Err(string) => string.to_string_lossy().to_string(),
101     }
102 }
103 
username() -> String104 pub fn username() -> String {
105     string_from_os(username_os())
106 }
107 
username_os() -> OsString108 pub fn username_os() -> OsString {
109     // Step 1. Retreive the entire length of the username
110     let mut size = 0;
111     let success = unsafe {
112         // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
113         GetUserNameW(ptr::null_mut(), &mut size) == 0
114     };
115     assert!(success);
116 
117     // Step 2. Allocate memory to put the Windows (UTF-16) string.
118     let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap_or(usize::MAX));
119     size = name.capacity().try_into().unwrap_or(c_ulong::MAX);
120     let orig_size = size;
121     let fail =
122         unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 };
123     if fail {
124         return "unknown".to_string().into();
125     }
126     debug_assert_eq!(orig_size, size);
127     unsafe {
128         name.set_len(size.try_into().unwrap_or(usize::MAX));
129     }
130     let terminator = name.pop(); // Remove Trailing Null
131     debug_assert_eq!(terminator, Some(0u16));
132 
133     // Step 3. Convert to Rust String
134     OsString::from_wide(&name)
135 }
136 
137 #[inline(always)]
realname() -> String138 pub fn realname() -> String {
139     string_from_os(realname_os())
140 }
141 
142 #[inline(always)]
realname_os() -> OsString143 pub fn realname_os() -> OsString {
144     // Step 1. Retrieve the entire length of the username
145     let mut buf_size = 0;
146     let success = unsafe {
147         GetUserNameExW(
148             ExtendedNameFormat::Display,
149             ptr::null_mut(),
150             &mut buf_size,
151         ) == 0
152     };
153     assert!(success);
154     match unsafe { GetLastError() } {
155 		0x00EA /* more data */ => { /* Success, continue */ }
156 		_ /* network error or none mapped */ => {
157 			// Fallback to username
158 			return username_os();
159 		}
160 	}
161 
162     // Step 2. Allocate memory to put the Windows (UTF-16) string.
163     let mut name: Vec<u16> = Vec::with_capacity(buf_size.try_into().unwrap_or(usize::MAX));
164     let mut name_len = name.capacity().try_into().unwrap_or(c_ulong::MAX);
165     let fail = unsafe {
166         GetUserNameExW(
167             ExtendedNameFormat::Display,
168             name.as_mut_ptr().cast(),
169             &mut name_len,
170         ) == 0
171     };
172     if fail {
173         return "Unknown".to_string().into();
174     }
175     debug_assert_eq!(buf_size, name_len + 1);
176     unsafe {
177         name.set_len(name_len.try_into().unwrap_or(usize::MAX));
178     }
179 
180     // Step 3. Convert to Rust String
181     OsString::from_wide(&name)
182 }
183 
184 #[inline(always)]
devicename() -> String185 pub fn devicename() -> String {
186     string_from_os(devicename_os())
187 }
188 
189 #[inline(always)]
devicename_os() -> OsString190 pub fn devicename_os() -> OsString {
191     // Step 1. Retreive the entire length of the device name
192     let mut size = 0;
193     let success = unsafe {
194         // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
195         GetComputerNameExW(
196             ComputerNameFormat::DnsHostname,
197             ptr::null_mut(),
198             &mut size,
199         ) == 0
200     };
201     assert!(success);
202 
203     // Step 2. Allocate memory to put the Windows (UTF-16) string.
204     let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap_or(usize::MAX));
205     size = name.capacity().try_into().unwrap_or(c_ulong::MAX);
206     let fail = unsafe {
207         GetComputerNameExW(
208             ComputerNameFormat::DnsHostname,
209             name.as_mut_ptr().cast(),
210             &mut size,
211         ) == 0
212     };
213     if fail {
214         return "Unknown".to_string().into();
215     }
216     unsafe {
217         name.set_len(size.try_into().unwrap_or(usize::MAX));
218     }
219 
220     // Step 3. Convert to Rust String
221     OsString::from_wide(&name)
222 }
223 
hostname() -> String224 pub fn hostname() -> String {
225     string_from_os(hostname_os())
226 }
227 
hostname_os() -> OsString228 pub fn hostname_os() -> OsString {
229     // Step 1. Retreive the entire length of the username
230     let mut size = 0;
231     let fail = unsafe {
232         // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
233         GetComputerNameExW(
234             ComputerNameFormat::NetBIOS,
235             ptr::null_mut(),
236             &mut size,
237         ) == 0
238     };
239     debug_assert!(fail);
240 
241     // Step 2. Allocate memory to put the Windows (UTF-16) string.
242     let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap_or(usize::MAX));
243     size = name.capacity().try_into().unwrap_or(c_ulong::MAX);
244     let fail = unsafe {
245         GetComputerNameExW(
246             ComputerNameFormat::NetBIOS,
247             name.as_mut_ptr().cast(),
248             &mut size,
249         ) == 0
250     };
251     if fail {
252         return "localhost".to_string().into();
253     }
254     unsafe {
255         name.set_len(size.try_into().unwrap_or(usize::MAX));
256     }
257 
258     // Step 3. Convert to Rust String
259     OsString::from_wide(&name)
260 }
261 
distro_os() -> Option<OsString>262 pub fn distro_os() -> Option<OsString> {
263     distro().map(|a| a.into())
264 }
265 
distro() -> Option<String>266 pub fn distro() -> Option<String> {
267     let mut version = std::mem::MaybeUninit::<OsVersionInfoEx>::zeroed();
268 
269     let version = unsafe {
270         (*version.as_mut_ptr()).os_version_info_size =
271             std::mem::size_of::<OsVersionInfoEx>() as u32;
272         RtlGetVersion(version.as_mut_ptr());
273         version.assume_init()
274     };
275 
276     let product = match version.product_type {
277         1 => "Workstation",
278         2 => "Domain Controller",
279         3 => "Server",
280         _ => "Unknown",
281     };
282 
283     let out = format!(
284         "Windows {}.{}.{} ({})",
285         version.major_version,
286         version.minor_version,
287         version.build_number,
288         product
289     );
290 
291     Some(out)
292 }
293 
294 #[inline(always)]
desktop_env() -> DesktopEnv295 pub const fn desktop_env() -> DesktopEnv {
296     DesktopEnv::Windows
297 }
298 
299 #[inline(always)]
platform() -> Platform300 pub const fn platform() -> Platform {
301     Platform::Windows
302 }
303 
304 struct LangIter {
305     array: Vec<String>,
306     index: usize,
307 }
308 
309 impl Iterator for LangIter {
310     type Item = String;
311 
next(&mut self) -> Option<Self::Item>312     fn next(&mut self) -> Option<Self::Item> {
313         if let Some(value) = self.array.get(self.index) {
314             self.index += 1;
315             Some(value.to_string())
316         } else {
317             None
318         }
319     }
320 }
321 
322 #[inline(always)]
lang() -> impl Iterator<Item = String>323 pub fn lang() -> impl Iterator<Item = String> {
324     let mut num_languages = 0;
325     let mut buffer_size = 0;
326     let mut buffer;
327 
328     unsafe {
329         assert_ne!(
330             GetUserPreferredUILanguages(
331                 0x08, /* MUI_LANGUAGE_NAME */
332                 &mut num_languages,
333                 std::ptr::null_mut(), // List of languages.
334                 &mut buffer_size,
335             ),
336             0
337         );
338 
339         buffer = Vec::with_capacity(buffer_size as usize);
340 
341         assert_ne!(
342             GetUserPreferredUILanguages(
343                 0x08, /* MUI_LANGUAGE_NAME */
344                 &mut num_languages,
345                 buffer.as_mut_ptr(), // List of languages.
346                 &mut buffer_size,
347             ),
348             0
349         );
350 
351         buffer.set_len(buffer_size as usize);
352     }
353 
354     // We know it ends in two null characters.
355     buffer.pop();
356     buffer.pop();
357 
358     //
359     let array = String::from_utf16_lossy(&buffer)
360         .split('\0')
361         .map(|x| x.to_string())
362         .collect();
363     let index = 0;
364 
365     LangIter { array, index }
366 }
367