cypat/util/
user.rs

1/*  
2*   SPDX-License-Identifier: GPL-3.0-only
3*   A cyberpatriots scoring engine library
4*   Copyright (C) 2023 Teresa Maria Rivera
5*/
6#![allow(unused_imports)]
7
8use std::{
9	fs::File, 
10	io::{BufRead, BufReader, Read}, 
11	mem::{ManuallyDrop, MaybeUninit}, 
12	process::{Command, Stdio}, 
13	ptr::{null, null_mut}, 
14	str::FromStr, 
15	string::String, 
16	vec::Vec,
17	ffi::CString,
18};
19
20use super::errno;
21
22#[cfg(target_os = "linux")]
23use libc::{gid_t, uid_t, sysconf, getpwnam_r, getgrnam_r, getpwuid_r, getgrgid_r, getgrouplist, strlen};
24
25#[cfg(target_os = "windows")]
26use winapi::{
27	um::{
28		wincred::NERR_BASE,
29		lmaccess::{
30			NetUserGetLocalGroups, 
31			NetGroupGetInfo, 
32			LPLOCALGROUP_USERS_INFO_0, 
33			LOCALGROUP_USERS_INFO_0, 
34			LPUSER_INFO_0,
35			LPGROUP_INFO_0
36		}, 
37		lmapibuf::NetApiBufferFree
38	}, 
39	ctypes::c_void
40};
41
42/// An entry to the /etc/group file.
43#[cfg(target_os = "linux")]
44#[derive(Clone)]
45pub struct GroupEntry {
46	pub groupname: String,
47	pub gid: gid_t,
48	pub list: Vec<String>,
49}
50
51/// An entry to the /etc/passwd file.
52#[cfg(target_os = "linux")]
53#[derive(Clone)]
54pub struct PasswdEntry {
55	pub username: String,
56    pub password_in_shadow: bool,
57    pub uid: uid_t,
58	pub gid: gid_t,
59    pub gecos: String,
60    pub home_dir: String,
61    pub shell: String,
62}
63
64#[cfg(target_os = "linux")]
65impl PasswdEntry {
66	/// Parse a passwd entry from a string
67	pub fn parse_entry<T: ToString>(entry: &T) -> PasswdEntry {
68		let entry_str = entry.to_string();
69		let tokenized_entry: Vec<_> = entry_str.split(':').collect();
70
71		PasswdEntry {
72			username: tokenized_entry[0].to_string(),
73			password_in_shadow: tokenized_entry[1] == "x",
74			uid: tokenized_entry[2].parse::<uid_t>().unwrap(),
75			gid: tokenized_entry[3].parse::<gid_t>().unwrap(),
76			gecos: tokenized_entry[4].to_string(),
77			home_dir: tokenized_entry[5].to_string(),
78			shell: tokenized_entry[6].to_string(),
79		}
80	}
81	
82	/// Get the entry from the password database
83	pub fn get_entry_from_passwd<T: ToString>(name: &T) -> Result<PasswdEntry, i32> {
84		let username = match CString::new(name.to_string()) {
85			Ok(s) => s,
86			_ => return Err(-1),
87		};
88
89		unsafe {
90			let mut pass = MaybeUninit::zeroed().assume_init();
91        	let mut pass_ptr = MaybeUninit::zeroed().assume_init();
92        	let mut buf = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
93        	let mut res ;
94			let tmp;
95        	res = getpwnam_r(username.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
96
97        	while res != 0 && errno() == libc::ERANGE {
98            	let mut nb = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
99            	buf.append(&mut nb);
100            	res = getpwnam_r(username.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
101        	}
102
103			if res != 0 {
104				return Err(errno());
105			}
106
107			tmp = pass_ptr.as_mut().unwrap();
108
109			Ok(PasswdEntry {
110				username: String::from_raw_parts(tmp.pw_name as *mut u8, libc::strlen(tmp.pw_name), libc::strlen(tmp.pw_name)),
111				uid: tmp.pw_uid,
112				gid: tmp.pw_gid,
113				password_in_shadow: *tmp.pw_passwd == 'x' as i8,
114				gecos: String::from_raw_parts(tmp.pw_gecos as *mut u8, libc::strlen(tmp.pw_gecos), libc::strlen(tmp.pw_gecos)),
115				home_dir: String::from_raw_parts(tmp.pw_dir as *mut u8, libc::strlen(tmp.pw_dir), libc::strlen(tmp.pw_dir)),
116				shell: String::from_raw_parts(tmp.pw_shell as *mut u8, libc::strlen(tmp.pw_shell), libc::strlen(tmp.pw_shell)),
117			}.clone())
118		}
119	}
120
121	/// Get the entry from the password database by uid
122	pub fn get_entry_from_passwd_by_uid(uid: uid_t) -> Result<PasswdEntry, i32> {
123		unsafe {
124			let mut pass = MaybeUninit::zeroed().assume_init();
125        	let mut pass_ptr = MaybeUninit::zeroed().assume_init();
126        	let mut buf = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
127        	let mut res;
128			let tmp;
129        	res = getpwuid_r(uid, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
130
131        	while res != 0 && errno() == libc::ERANGE {
132            	let mut nb = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
133            	buf.append(&mut nb);
134            	res = getpwuid_r(uid, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
135        	}
136
137			if res != 0 {
138				return Err(errno());
139			}
140
141			tmp = pass_ptr.as_mut().unwrap();
142
143			Ok(PasswdEntry {
144				username: String::from_raw_parts(tmp.pw_name as *mut u8, libc::strlen(tmp.pw_name), libc::strlen(tmp.pw_name)),
145				uid: tmp.pw_uid,
146				gid: tmp.pw_gid,
147				password_in_shadow: *tmp.pw_passwd == 'x' as i8,
148				gecos: String::from_raw_parts(tmp.pw_gecos as *mut u8, libc::strlen(tmp.pw_gecos), libc::strlen(tmp.pw_gecos)),
149				home_dir: String::from_raw_parts(tmp.pw_dir as *mut u8, libc::strlen(tmp.pw_dir), libc::strlen(tmp.pw_dir)),
150				shell: String::from_raw_parts(tmp.pw_shell as *mut u8, libc::strlen(tmp.pw_shell), libc::strlen(tmp.pw_shell)),
151			}.clone())
152		}
153	}
154}
155
156#[cfg(target_os = "linux")]
157impl GroupEntry {
158	/// Get the entry from the group database
159	pub fn get_entry_from_group<T: ToString>(name: &T) -> Result<GroupEntry, i32> {
160		let groupname = match CString::new(name.to_string()) {
161			Ok(s) => s,
162			_ => return Err(-1),
163		};
164
165		unsafe {
166			let mut pass = MaybeUninit::zeroed().assume_init();
167        	let mut pass_ptr = MaybeUninit::zeroed().assume_init();
168        	let mut buf = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
169        	let mut res ;
170			let mut ret;
171			let tmp;
172        	res = getgrnam_r(groupname.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
173
174        	while res != 0 && errno() == libc::ERANGE {
175            	let mut nb = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
176            	buf.append(&mut nb);
177            	res = getgrnam_r(groupname.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
178        	}
179
180			if res != 0 {
181				return Err(errno());
182			}
183
184			tmp = pass_ptr.as_mut().unwrap();
185
186			ret = GroupEntry {
187				groupname: String::from_raw_parts(tmp.gr_name as *mut u8, libc::strlen(tmp.gr_name), libc::strlen(tmp.gr_name)),
188				gid: tmp.gr_gid,
189				list: Vec::new(),
190			};
191
192			let mut i = 0;
193			while tmp.gr_mem.offset(i).read() != null_mut() {
194				let tmp_tmp = tmp.gr_mem.offset(i).read() as *mut i8;
195				ret.list.push(String::from_raw_parts(tmp_tmp as *mut u8, libc::strlen(tmp_tmp), libc::strlen(tmp_tmp)));
196				i += 1;
197			}
198
199			Ok(ret.clone())
200		}
201	}
202
203	/// Get the entry from the group database by GID
204	pub fn get_entry_by_gid(gid: gid_t) -> Result<GroupEntry, i32> {
205		unsafe {
206			let mut pass = MaybeUninit::zeroed().assume_init();
207        	let mut pass_ptr = MaybeUninit::zeroed().assume_init();
208        	let mut buf = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
209        	let mut res ;
210			let mut ret;
211			let tmp;
212        	res = getgrgid_r(gid, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
213
214        	while res != 0 && errno() == libc::ERANGE {
215            	let mut nb = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
216            	buf.append(&mut nb);
217            	res = getgrgid_r(gid, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
218        	}
219
220			if res != 0 {
221				return Err(errno());
222			}
223
224			tmp = pass_ptr.as_mut().unwrap();
225
226			ret = GroupEntry {
227				groupname: String::from_raw_parts(tmp.gr_name as *mut u8, libc::strlen(tmp.gr_name), libc::strlen(tmp.gr_name)),
228				gid: tmp.gr_gid,
229				list: Vec::new(),
230			};
231
232			let mut i = 0;
233			while tmp.gr_mem.offset(i).read() != null_mut() {
234				let tmp_tmp = tmp.gr_mem.offset(i).read() as *mut i8;
235				ret.list.push(String::from_raw_parts(tmp_tmp as *mut u8, libc::strlen(tmp_tmp), libc::strlen(tmp_tmp)));
236				i += 1;
237			}
238
239			Ok(ret.clone())
240		}
241	}
242}
243
244/// Checks if a user with username `name` exists on the system
245pub fn user_exists<T: ToString>(n: &T) -> Result<bool, i32> {
246	let name = n.to_string();
247	#[cfg(target_os = "linux")]
248	unsafe {
249		let username = match CString::new(name) {
250			Ok(s) => s,
251			_ => return Err(-1),
252		};
253
254		let mut pass = MaybeUninit::zeroed().assume_init();
255        let mut pass_ptr = MaybeUninit::zeroed().assume_init();
256    	let mut buf = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
257    	let mut res ;
258    	res = getpwnam_r(username.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
259
260        while res != 0 && errno() == libc::ERANGE {
261        	let mut nb = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
262            buf.append(&mut nb);
263        	res = getpwnam_r(username.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
264    	}
265
266		if res == 0 {
267			Ok(true)
268		} else {
269			match errno() {
270				0 | libc::ENOENT | libc::ESRCH | libc::EBADF | libc::EPERM => Ok(false),
271				_ => Err(errno()),
272			}
273		}
274	}
275	#[cfg(target_os = "windows")]
276	unsafe {
277		let mut user: LPUSER_INFO_0 = null_mut();
278		let uname_utf16 = name.encode_utf16().collect::<Vec<u16>>();
279
280		match NetGroupGetInfo(null(), uname_utf16.as_ptr(), 0, *&mut user as *mut *mut u8) {
281			0 => { NetApiBufferFree(user as *mut c_void); Ok(true) },
282			2220 => Ok(false), /* NERR_GroupNotFound */
283			_ => Err(errno()),
284		}
285	}
286}
287
288/// Checks if a group named `name` exists on the system
289pub fn group_exists<T: ToString>(n: &T) -> Result<bool, i32> {
290	let name = n.to_string();
291	#[cfg(target_os = "linux")]
292	unsafe {
293		let groupname = match CString::new(name) {
294			Ok(s) => s,
295			_ => return Err(-1),
296		};
297
298		let mut pass = MaybeUninit::zeroed().assume_init();
299        let mut pass_ptr = MaybeUninit::zeroed().assume_init();
300    	let mut buf = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
301    	let mut res ;
302    	res = getgrnam_r(groupname.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
303
304        while res != 0 && errno() == libc::ERANGE {
305        	let mut nb = vec![0i8; sysconf(libc::_SC_GETPW_R_SIZE_MAX) as usize];
306            buf.append(&mut nb);
307        	res = getgrnam_r(groupname.as_ptr() as *const i8, &mut pass, buf.as_mut_ptr(), buf.len(), &mut pass_ptr);
308    	}
309
310		if res == 0 {
311			Ok(true)
312		} else {
313			match errno() {
314				0 | libc::ENOENT | libc::ESRCH | libc::EBADF | libc::EPERM => Ok(false),
315				_ => Err(errno()),
316			}
317		}
318	}
319	#[cfg(target_os = "windows")]
320	unsafe {
321		let mut group: LPGROUP_INFO_0 = null_mut();
322		let gname_utf16 = name.encode_utf16().collect::<Vec<u16>>();
323
324		match NetGroupGetInfo(null(), gname_utf16.as_ptr(), 0, *&mut group as *mut *mut u8) {
325			0 => { NetApiBufferFree(group as *mut c_void); Ok(true) },
326			2220 => Ok(false),
327			_ => Err(errno()),
328		}
329	}
330}
331
332/// Checks if a user named `uname` is in the group named `gname`.
333/// 
334/// If it returns an [`Ok`] value, the both the user and group exist, and the payload contains if the user is in the group.
335/// If it returns an [`Err`] value, either the user or group doesn't exist
336pub fn user_is_in_group<A: ToString, B: ToString>(u: &A, g: &B) -> Result<bool, i32> {
337	user_exists(u)?;
338	group_exists(g)?;
339	#[cfg(target_os = "linux")]
340	{
341		let group = GroupEntry::get_entry_from_group(g)?;
342		Ok(group.list.contains(&g.to_string()))
343	}
344	#[cfg(target_os = "windows")]
345	unsafe {
346		let uname = u.to_string();
347		let gname = g.to_string();
348		let mut groups: LPLOCALGROUP_USERS_INFO_0 = null_mut();
349		let mut uname_utf16 = uname.encode_utf16().collect::<Vec<u16>>();
350		let mut gname_utf16 = gname.encode_utf16().collect::<Vec<u16>>();
351		let mut num_entries = 0;
352		let mut tmp = 0;
353		uname_utf16.push(0);
354		gname_utf16.push(0);
355
356		let status = NetUserGetLocalGroups(null(), uname_utf16.as_ptr(), 0, 1, *&mut groups as *mut *mut u8, u32::MAX, &mut num_entries, &mut tmp);
357		let entries = ManuallyDrop::new(Vec::<LOCALGROUP_USERS_INFO_0>::from_raw_parts(groups, num_entries as usize, num_entries as usize));
358
359		if status == 0 {
360			let mut strlen = 0;
361			for c in gname_utf16.iter() {
362				if *c == 0 {
363					break;
364				}
365				strlen += 1;
366			}
367
368			'groups: for raw_entry in (*entries).iter() {
369				let mut i = 0;
370				while *raw_entry.lgrui0_name.offset(i) != 0 {
371					i += 1;
372				}
373
374				let entry = ManuallyDrop::new(Vec::from_raw_parts(raw_entry.lgrui0_name, i as usize, i as usize));
375				if i != strlen {
376					continue;
377				}
378
379				for j in 0..i {
380					if entry[j as usize] != gname_utf16[j as usize] {
381						continue 'groups;
382					}
383				}
384
385				NetApiBufferFree(groups as *mut c_void);
386				return Ok(true);
387			}
388
389			NetApiBufferFree(groups as *mut c_void);
390			return Ok(false);
391		} else {
392			Err(errno())
393		}
394	}
395}
396
397/// Checks if the user has administrator privileges. 
398/// 
399/// On Linux, it checks if the user is either root, or if they have access to sudo.
400/// On Windows, it checks if the user is a member of the Administrators group.
401/// 
402/// If it returns an [`Ok`] value, the user exists and the payload contians if the user has admin privileges
403/// If it returns an [`Err`] value, the user does not exist
404pub fn user_is_admin<T: ToString>(name: &T) -> Result<bool, i32> {
405
406    #[cfg(target_os = "linux")]
407    {
408        if name.to_string() == "root" {
409            Ok(true)
410        } else {
411            if user_exists(name).unwrap_or(false) {
412                let cmd = Command::new("sudo")
413                    .args(&["-l", "-U", &format!("{}", name.to_string())]).stderr(Stdio::null()).stdout(Stdio::piped())
414		            .output().expect("Sudo failed to run.");
415                let mensaje = format!("User {} is not allowed to run sudo", name.to_string());
416
417                Ok(!String::from_utf8_lossy(&cmd.stdout).contains(&mensaje))
418            } else {
419                Err(errno())
420            }
421        }
422    }
423    #[cfg(target_os = "windows")]
424    {
425        user_is_in_group(name, &"Administrators")
426    }
427}