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 fail = unsafe {
112         // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
113         GetUserNameW(ptr::null_mut(), &mut size) == 0
114     };
115     debug_assert_eq!(fail, true);
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());
119     let orig_size = size;
120     let fail =
121         unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 };
122     if fail {
123         panic!(
124             "Failed to get username: {}, report at https://github.com/libcala/whoami/issues",
125             unsafe { GetLastError() }
126         );
127     }
128     debug_assert_eq!(orig_size, size);
129     unsafe {
130         name.set_len(size.try_into().unwrap());
131     }
132     let terminator = name.pop(); // Remove Trailing Null
133     debug_assert_eq!(terminator, Some(0u16));
134 
135     // Step 3. Convert to Rust String
136     OsString::from_wide(&name)
137 }
138 
139 #[inline(always)]
realname() -> String140 pub fn realname() -> String {
141     string_from_os(realname_os())
142 }
143 
144 #[inline(always)]
realname_os() -> OsString145 pub fn realname_os() -> OsString {
146     // Step 1. Retrieve the entire length of the username
147     let mut buf_size = 0;
148     let fail = unsafe {
149         GetUserNameExW(
150             ExtendedNameFormat::Display,
151             ptr::null_mut(),
152             &mut buf_size,
153         ) == 0
154     };
155     debug_assert_eq!(fail, true);
156     match unsafe { GetLastError() } {
157 		0x00EA /* more data */ => { /* Success, continue */ }
158 		0x054B /* no such domain */ => {
159 			// If domain controller over the network can't be contacted, return
160 			// "Unknown".
161 			return "Unknown".into()
162 		}
163 		0x0534 /* none mapped */ => {
164 			// Fallback to username
165 			return username_os();
166 		}
167 		u => {
168 			eprintln!("Unknown error code: {}, report at https://github.com/libcala/whoami/issues", u);
169 			unreachable!();
170 		}
171 	}
172 
173     // Step 2. Allocate memory to put the Windows (UTF-16) string.
174     let mut name: Vec<u16> = Vec::with_capacity(buf_size.try_into().unwrap());
175     let mut name_len = buf_size;
176     let fail = unsafe {
177         GetUserNameExW(
178             ExtendedNameFormat::Display,
179             name.as_mut_ptr().cast(),
180             &mut name_len,
181         ) == 0
182     };
183     if fail {
184         panic!(
185             "Failed to get username: {}, report at https://github.com/libcala/whoami/issues",
186             unsafe { GetLastError() }
187         );
188     }
189     debug_assert_eq!(buf_size, name_len + 1);
190     unsafe {
191         name.set_len(name_len.try_into().unwrap());
192     }
193 
194     // Step 3. Convert to Rust String
195     OsString::from_wide(&name)
196 }
197 
198 #[inline(always)]
devicename() -> String199 pub fn devicename() -> String {
200     string_from_os(devicename_os())
201 }
202 
203 #[inline(always)]
devicename_os() -> OsString204 pub fn devicename_os() -> OsString {
205     // Step 1. Retreive the entire length of the username
206     let mut size = 0;
207     let fail = unsafe {
208         // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
209         GetComputerNameExW(
210             ComputerNameFormat::DnsHostname,
211             ptr::null_mut(),
212             &mut size,
213         ) == 0
214     };
215     debug_assert_eq!(fail, true);
216 
217     // Step 2. Allocate memory to put the Windows (UTF-16) string.
218     let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap());
219     let fail = unsafe {
220         GetComputerNameExW(
221             ComputerNameFormat::DnsHostname,
222             name.as_mut_ptr().cast(),
223             &mut size,
224         ) == 0
225     };
226     if fail {
227         panic!(
228             "Failed to get computer name: {}, report at https://github.com/libcala/whoami/issues",
229             unsafe { GetLastError() }
230         );
231     }
232     unsafe {
233         name.set_len(size.try_into().unwrap());
234     }
235 
236     // Step 3. Convert to Rust String
237     OsString::from_wide(&name)
238 }
239 
hostname() -> String240 pub fn hostname() -> String {
241     string_from_os(hostname_os())
242 }
243 
hostname_os() -> OsString244 pub fn hostname_os() -> OsString {
245     // Step 1. Retreive the entire length of the username
246     let mut size = 0;
247     let fail = unsafe {
248         // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
249         GetComputerNameExW(
250             ComputerNameFormat::NetBIOS,
251             ptr::null_mut(),
252             &mut size,
253         ) == 0
254     };
255     debug_assert_eq!(fail, true);
256 
257     // Step 2. Allocate memory to put the Windows (UTF-16) string.
258     let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap());
259     let fail = unsafe {
260         GetComputerNameExW(
261             ComputerNameFormat::NetBIOS,
262             name.as_mut_ptr().cast(),
263             &mut size,
264         ) == 0
265     };
266     if fail {
267         panic!(
268             "Failed to get computer name: {}, report at https://github.com/libcala/whoami/issues",
269             unsafe { GetLastError() }
270         );
271     }
272     unsafe {
273         name.set_len(size.try_into().unwrap());
274     }
275 
276     // Step 3. Convert to Rust String
277     OsString::from_wide(&name)
278 }
279 
distro_os() -> Option<OsString>280 pub fn distro_os() -> Option<OsString> {
281     distro().map(|a| a.into())
282 }
283 
distro() -> Option<String>284 pub fn distro() -> Option<String> {
285     let mut version = std::mem::MaybeUninit::<OsVersionInfoEx>::zeroed();
286 
287     let version = unsafe {
288         (*version.as_mut_ptr()).os_version_info_size =
289             std::mem::size_of::<OsVersionInfoEx>() as u32;
290         RtlGetVersion(version.as_mut_ptr());
291         version.assume_init()
292     };
293 
294     let product = match version.product_type {
295         1 => "Workstation",
296         2 => "Domain Controller",
297         3 => "Server",
298         _ => "Unknown",
299     };
300 
301     let out = format!(
302         "Windows {}.{}.{} ({})",
303         version.major_version,
304         version.minor_version,
305         version.build_number,
306         product
307     );
308 
309     Some(out)
310 }
311 
312 #[inline(always)]
desktop_env() -> DesktopEnv313 pub const fn desktop_env() -> DesktopEnv {
314     DesktopEnv::Windows
315 }
316 
317 #[inline(always)]
platform() -> Platform318 pub const fn platform() -> Platform {
319     Platform::Windows
320 }
321 
322 struct LangIter {
323     array: Vec<String>,
324     index: usize,
325 }
326 
327 impl Iterator for LangIter {
328     type Item = String;
329 
next(&mut self) -> Option<Self::Item>330     fn next(&mut self) -> Option<Self::Item> {
331         if let Some(value) = self.array.get(self.index) {
332             self.index += 1;
333             Some(value.to_string())
334         } else {
335             None
336         }
337     }
338 }
339 
340 #[inline(always)]
lang() -> impl Iterator<Item = String>341 pub fn lang() -> impl Iterator<Item = String> {
342     let mut num_languages = 0;
343     let mut buffer_size = 0;
344     let mut buffer;
345 
346     unsafe {
347         assert_ne!(
348             GetUserPreferredUILanguages(
349                 0x08, /* MUI_LANGUAGE_NAME */
350                 &mut num_languages,
351                 std::ptr::null_mut(), // List of languages.
352                 &mut buffer_size,
353             ),
354             0
355         );
356 
357         buffer = Vec::with_capacity(buffer_size as usize);
358 
359         assert_ne!(
360             GetUserPreferredUILanguages(
361                 0x08, /* MUI_LANGUAGE_NAME */
362                 &mut num_languages,
363                 buffer.as_mut_ptr(), // List of languages.
364                 &mut buffer_size,
365             ),
366             0
367         );
368 
369         buffer.set_len(buffer_size as usize);
370     }
371 
372     // We know it ends in two null characters.
373     buffer.pop();
374     buffer.pop();
375 
376     //
377     let array = String::from_utf16_lossy(&buffer)
378         .split('\0')
379         .map(|x| x.to_string())
380         .collect();
381     let index = 0;
382 
383     LangIter { array, index }
384 }
385