1use core::{
6 cell::SyncUnsafeCell,
7 convert::TryInto,
8 mem::{self, MaybeUninit},
9 num::ParseIntError,
10 ops::{Deref, DerefMut},
11 pin::Pin,
12 ptr, slice,
13};
14
15use alloc::string::{FromUtf8Error, String};
16
17use crate::{
18 error::ResultExt,
19 fs::File,
20 header::{errno, fcntl, limits, string::strlen},
21 io::{self, BufReader, Lines, prelude::*},
22 platform::{
23 self, Pal, Sys,
24 types::{c_char, c_int, c_void, gid_t, size_t},
25 },
26};
27
28use super::{
29 errno::{EINVAL, EIO, ENOENT, ERANGE},
30 string::strncmp,
31};
32
33#[cfg(target_os = "linux")]
35const SEPARATOR: char = ':';
36
37#[cfg(target_os = "redox")]
39const SEPARATOR: char = ';';
40
41const GROUP_FILE: &core::ffi::CStr = c"/etc/group";
43
44#[derive(Clone, Copy, Debug)]
45struct DestBuffer {
46 ptr: *mut u8,
47 len: usize,
48}
49
50#[derive(Debug)]
52enum MaybeAllocated {
53 Owned(Pin<Box<[u8]>>),
54 Borrowed(DestBuffer),
55}
56impl Deref for MaybeAllocated {
57 type Target = [u8];
58
59 fn deref(&self) -> &Self::Target {
60 match self {
61 MaybeAllocated::Owned(boxed) => boxed,
62 MaybeAllocated::Borrowed(dst) => unsafe {
63 core::slice::from_raw_parts(dst.ptr, dst.len)
64 },
65 }
66 }
67}
68impl DerefMut for MaybeAllocated {
69 fn deref_mut(&mut self) -> &mut Self::Target {
70 match self {
71 MaybeAllocated::Owned(boxed) => boxed,
72 MaybeAllocated::Borrowed(dst) => unsafe {
73 core::slice::from_raw_parts_mut(dst.ptr, dst.len)
74 },
75 }
76 }
77}
78
79static mut GROUP_BUF: Option<MaybeAllocated> = None;
81static mut GROUP: group = group {
83 gr_name: ptr::null_mut(),
84 gr_passwd: ptr::null_mut(),
85 gr_gid: 0,
86 gr_mem: ptr::null_mut(),
87};
88
89static LINE_READER: SyncUnsafeCell<Option<Lines<BufReader<File>>>> = SyncUnsafeCell::new(None);
91
92#[allow(non_camel_case_types)]
94#[repr(C)]
95#[derive(Debug)]
96pub struct group {
97 pub gr_name: *mut c_char,
99 pub gr_passwd: *mut c_char,
103 pub gr_gid: gid_t,
105 pub gr_mem: *mut *mut c_char,
108}
109
110#[derive(Debug)]
111enum Error {
112 EOF,
113 Syntax,
114 BufTooSmall,
115 Misc(io::Error),
116 FromUtf8(FromUtf8Error),
117 ParseInt(ParseIntError),
118 Other,
119}
120
121#[derive(Debug)]
122struct OwnedGrp {
123 buffer: MaybeAllocated,
124 reference: group,
125}
126
127impl OwnedGrp {
128 fn into_global(self) -> *mut group {
129 unsafe {
130 GROUP_BUF = Some(self.buffer);
131 GROUP = self.reference;
132 &raw mut GROUP
133 }
134 }
135}
136
137fn split(buf: &mut [u8]) -> Option<group> {
138 let gr_gid = match buf[0..mem::size_of::<gid_t>()].try_into() {
139 Ok(buf) => gid_t::from_ne_bytes(buf),
140 Err(err) => return None,
141 };
142
143 let buf_addr = buf.as_ptr() as usize;
145
146 let mut parts = buf[mem::size_of::<gid_t>()..].split_mut(|&c| c == b'\0');
148 let gr_name = parts.next()?.as_mut_ptr().cast::<c_char>();
149 let gr_passwd = parts.next()?.as_mut_ptr().cast::<c_char>();
150 let gr_mem = parts.next()?.as_mut_ptr().cast::<usize>();
151
152 for i in 0..4096 {
155 unsafe {
156 if *gr_mem.add(i) == 0 {
157 break;
159 }
160 *gr_mem.add(i) += buf_addr;
161 }
162 }
163
164 Some(group {
165 gr_name,
166 gr_passwd,
167 gr_gid,
168 gr_mem: gr_mem.cast::<*mut c_char>(),
169 })
170}
171
172fn parse_grp(line: String, destbuf: Option<DestBuffer>) -> Result<OwnedGrp, Error> {
173 let buffer = line.clone().into_bytes();
174
175 let mut buffer = buffer
176 .into_iter()
177 .map(|i| if i == SEPARATOR as u8 { b'\0' } else { i })
178 .chain(*b"\0")
179 .collect::<Vec<_>>();
180 let mut buffer = buffer.split_mut(|i| *i == b'\0');
181
182 let strings = {
183 let mut vec: Vec<u8> = Vec::new();
184
185 let gr_name = buffer.next().ok_or(Error::EOF)?.to_vec();
186 let gr_passwd = buffer.next().ok_or(Error::EOF)?.to_vec();
187 let gr_gid = String::from_utf8(buffer.next().ok_or(Error::EOF)?.to_vec())
188 .map_err(Error::FromUtf8)?
189 .parse::<gid_t>()
190 .map_err(Error::ParseInt)?;
191
192 vec.extend(gr_gid.to_ne_bytes());
195 vec.extend(gr_name);
196 vec.push(0);
197 vec.extend(gr_passwd);
198 vec.push(0);
199
200 let members = buffer.next().ok_or(Error::EOF)?;
201
202 let member_array_start = vec.len();
204
205 for _member in members
207 .split(|b| *b == b',')
208 .filter(|member| !member.is_empty())
209 {
210 vec.extend(0usize.to_ne_bytes());
211 }
212 let member_array_end = vec.len();
213 vec.extend(0usize.to_ne_bytes());
215
216 for (i, member) in members
218 .split(|b| *b == b',')
219 .filter(|member| !member.is_empty())
220 .enumerate()
221 {
222 let cur_offset = vec.len();
223
224 let member_array = &mut vec[member_array_start..member_array_end];
227 let member_ptr = {
228 const SIZEOF_PTR: usize = mem::size_of::<*mut c_void>();
229 let start = i * SIZEOF_PTR;
230 let end = start + SIZEOF_PTR;
231 &mut member_array[start..end]
232 };
233
234 member_ptr.copy_from_slice(&cur_offset.to_ne_bytes());
236
237 vec.extend(member);
238 vec.push(0);
239 }
240
241 vec
242 };
243
244 let mut buffer = match destbuf {
245 None => MaybeAllocated::Owned(Box::into_pin(strings.into_boxed_slice())),
246 Some(buf) => {
247 let mut buf = MaybeAllocated::Borrowed(buf);
248
249 if buf.len() < strings.len() {
250 platform::ERRNO.set(errno::ERANGE);
251 return Err(Error::BufTooSmall);
252 }
253
254 buf[..strings.len()].copy_from_slice(&strings);
255 buf
256 }
257 };
258 let reference = split(&mut buffer).ok_or(Error::Other)?;
259
260 Ok(OwnedGrp { buffer, reference })
261}
262
263#[unsafe(no_mangle)]
269pub unsafe extern "C" fn getgrgid(gid: gid_t) -> *mut group {
270 let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
271 return ptr::null_mut();
272 };
273
274 for line in BufReader::new(db).lines() {
275 let Ok(line) = line else {
276 return ptr::null_mut();
277 };
278 let Ok(grp) = parse_grp(line, None) else {
279 return ptr::null_mut();
280 };
281
282 if grp.reference.gr_gid == gid {
283 return grp.into_global();
284 }
285 }
286
287 ptr::null_mut()
288}
289
290#[unsafe(no_mangle)]
296pub unsafe extern "C" fn getgrnam(name: *const c_char) -> *mut group {
297 let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
298 return ptr::null_mut();
299 };
300
301 for line in BufReader::new(db).lines() {
302 let Ok(line) = line else {
303 return ptr::null_mut();
304 };
305
306 let Ok(grp) = parse_grp(line, None) else {
307 return ptr::null_mut();
308 };
309
310 if unsafe {
312 strncmp(
313 grp.reference.gr_name,
314 name,
315 strlen(grp.reference.gr_name).min(strlen(name)),
316 ) == 0
317 } {
318 return grp.into_global();
319 }
320 }
321
322 ptr::null_mut()
323}
324
325#[unsafe(no_mangle)]
332pub unsafe extern "C" fn getgrgid_r(
333 gid: gid_t,
334 result_buf: *mut group,
335 buffer: *mut c_char,
336 buflen: size_t,
337 result: *mut *mut group,
338) -> c_int {
339 unsafe {
341 *result = ptr::null_mut();
342 }
343
344 let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
345 return ENOENT;
346 };
347
348 for line in BufReader::new(db).lines() {
349 let Ok(line) = line else { return EINVAL };
350 let grp = match parse_grp(
351 line,
352 Some(DestBuffer {
353 ptr: buffer.cast::<u8>(),
354 len: buflen,
355 }),
356 ) {
357 Ok(grp) => grp,
358 Err(err) => {
359 return match err {
360 Error::BufTooSmall => ERANGE,
361 Error::EOF
362 | Error::Syntax
363 | Error::FromUtf8(_)
364 | Error::ParseInt(_)
365 | Error::Other => EINVAL,
366 Error::Misc(io_err) => match io_err.kind() {
367 io::ErrorKind::InvalidData | io::ErrorKind::UnexpectedEof => EINVAL,
368 io::ErrorKind::NotFound => ENOENT,
369 _ => EIO,
370 },
371 };
372 }
373 };
374
375 if grp.reference.gr_gid == gid {
376 unsafe {
377 *result_buf = grp.reference;
378 *result = result_buf;
379 }
380
381 return 0;
382 }
383 }
384
385 0
387}
388
389#[unsafe(no_mangle)]
396pub unsafe extern "C" fn getgrnam_r(
397 name: *const c_char,
398 result_buf: *mut group,
399 buffer: *mut c_char,
400 buflen: size_t,
401 result: *mut *mut group,
402) -> c_int {
403 let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
404 return ENOENT;
405 };
406
407 for line in BufReader::new(db).lines() {
408 let Ok(line) = line else { return EINVAL };
409 let Ok(grp) = parse_grp(
410 line,
411 Some(DestBuffer {
412 ptr: buffer.cast::<u8>(),
413 len: buflen,
414 }),
415 ) else {
416 return EINVAL;
417 };
418
419 if unsafe {
420 strncmp(
421 grp.reference.gr_name,
422 name,
423 strlen(grp.reference.gr_name).min(strlen(name)),
424 ) == 0
425 } {
426 unsafe {
427 *result_buf = grp.reference;
428 *result = result_buf;
429 }
430
431 return 0;
432 }
433 }
434
435 ENOENT
436}
437
438#[unsafe(no_mangle)]
445pub unsafe extern "C" fn getgrent() -> *mut group {
446 let mut line_reader = unsafe { &mut *LINE_READER.get() };
447
448 if line_reader.is_none() {
449 let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
450 return ptr::null_mut();
451 };
452 *line_reader = Some(BufReader::new(db).lines());
453 }
454
455 if let Some(lines) = line_reader.deref_mut() {
456 let Some(line) = lines.next() else {
457 return ptr::null_mut();
458 };
459 let Ok(line) = line else {
460 return ptr::null_mut();
461 };
462
463 if let Ok(grp) = parse_grp(line, None) {
464 grp.into_global()
465 } else {
466 ptr::null_mut()
467 }
468 } else {
469 ptr::null_mut()
470 }
471}
472
473#[unsafe(no_mangle)]
479pub unsafe extern "C" fn endgrent() {
480 unsafe {
481 *LINE_READER.get() = None;
482 }
483}
484
485#[unsafe(no_mangle)]
492pub unsafe extern "C" fn setgrent() {
493 let line_reader = unsafe { &mut *LINE_READER.get() };
494 let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
495 return;
496 };
497 *line_reader = Some(BufReader::new(db).lines());
498}
499
500#[unsafe(no_mangle)]
506pub unsafe extern "C" fn getgrouplist(
507 user: *const c_char,
508 group: gid_t,
509 groups: *mut gid_t,
510 ngroups: *mut c_int,
511) -> c_int {
512 let grps = unsafe {
513 slice::from_raw_parts_mut(groups.cast::<MaybeUninit<gid_t>>(), ngroups.read() as usize)
514 };
515
516 let Ok(user) = (unsafe { crate::c_str::CStr::from_ptr(user).to_str() }) else {
520 return 0;
521 };
522
523 let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
524 return 0;
525 };
526
527 let mut groups_found: c_int = 0;
528
529 for line in BufReader::new(db).lines() {
530 let Ok(line) = line else {
531 return 0;
532 };
533
534 let mut parts = line.split(SEPARATOR);
535
536 let group_name = parts.next().unwrap_or("");
537 let group_password = parts.next().unwrap_or("");
538 let group_id = parts.next().unwrap_or("-1").parse::<c_int>().unwrap();
539 let members = parts
540 .next()
541 .unwrap_or("")
542 .split(",")
543 .map(|i| i.trim())
544 .collect::<Vec<_>>();
545
546 if !members.contains(&user) {
547 continue;
548 }
549
550 if let Some(dst) = grps.get_mut(groups_found as usize) {
551 dst.write(group_id);
552 }
553
554 groups_found = match groups_found.checked_add(1) {
555 Some(g) => g,
556 None => break,
557 };
558 }
559
560 unsafe {
561 ngroups.write(groups_found);
562 }
563
564 if groups_found as usize > grps.len() {
565 -1
566 } else {
567 groups_found
568 }
569}
570
571#[unsafe(no_mangle)]
577pub unsafe extern "C" fn initgroups(user: *const c_char, gid: gid_t) -> c_int {
578 let mut groups = [0; limits::NGROUPS_MAX];
579 let mut count = groups.len() as c_int;
580 if unsafe { getgrouplist(user, gid, groups.as_mut_ptr(), &raw mut count) < 0 } {
581 return -1;
582 }
583 unsafe { setgroups(count as size_t, groups.as_ptr()) }
584}
585
586#[unsafe(no_mangle)]
591pub unsafe extern "C" fn setgroups(size: size_t, list: *const gid_t) -> c_int {
592 unsafe { Sys::setgroups(size, list) }
593 .map(|()| 0)
594 .or_minus_one_errno()
595}