Skip to main content

relibc/header/shadow/
mod.rs

1//! `shadow.h` implementation.
2//!
3//! Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getspnam.3.html>.
4
5use core::{
6    cell::SyncUnsafeCell,
7    ops::{Deref, DerefMut},
8    pin::Pin,
9    ptr,
10    str::FromStr,
11};
12
13use alloc::{boxed::Box, string::String};
14
15use crate::{
16    c_str::CStr,
17    fs::File,
18    header::fcntl,
19    io::{BufReader, Lines, prelude::*},
20    platform,
21    platform::types::{c_char, c_int, c_long, c_ulong, size_t},
22};
23
24use super::errno::*;
25
26/// cbindgen:ignore
27#[cfg(target_os = "linux")]
28const SEPARATOR: char = ':';
29
30/// cbindgen:ignore
31#[cfg(target_os = "redox")]
32const SEPARATOR: char = ';';
33
34/// cbindgen:ignore
35const SHADOW_FILE: &core::ffi::CStr = c"/etc/shadow";
36
37#[derive(Clone, Copy, Debug)]
38struct DestBuffer {
39    ptr: *mut u8,
40    len: usize,
41}
42
43#[derive(Debug)]
44enum MaybeAllocated {
45    Owned(Pin<Box<[u8]>>),
46    Borrowed(DestBuffer),
47}
48impl Deref for MaybeAllocated {
49    type Target = [u8];
50
51    fn deref(&self) -> &Self::Target {
52        match self {
53            MaybeAllocated::Owned(boxed) => boxed,
54            MaybeAllocated::Borrowed(dst) => unsafe {
55                core::slice::from_raw_parts(dst.ptr, dst.len)
56            },
57        }
58    }
59}
60impl DerefMut for MaybeAllocated {
61    fn deref_mut(&mut self) -> &mut Self::Target {
62        match self {
63            MaybeAllocated::Owned(boxed) => boxed,
64            MaybeAllocated::Borrowed(dst) => unsafe {
65                core::slice::from_raw_parts_mut(dst.ptr, dst.len)
66            },
67        }
68    }
69}
70
71/// cbindgen:ignore
72static mut SHADOW_BUF: Option<MaybeAllocated> = None;
73/// cbindgen:ignore
74static mut SHADOW: spwd = spwd {
75    sp_namp: ptr::null_mut(),
76    sp_pwdp: ptr::null_mut(),
77    sp_lstchg: -1,
78    sp_min: -1,
79    sp_max: -1,
80    sp_warn: -1,
81    sp_inact: -1,
82    sp_expire: -1,
83    sp_flag: 0,
84};
85
86/// cbindgen:ignore
87static LINE_READER: SyncUnsafeCell<Option<Lines<BufReader<File>>>> = SyncUnsafeCell::new(None);
88
89/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getspnam.3.html>.
90#[repr(C)]
91#[derive(Debug, Clone, Copy)]
92pub struct spwd {
93    pub sp_namp: *mut c_char,
94    pub sp_pwdp: *mut c_char,
95    pub sp_lstchg: c_long,
96    pub sp_min: c_long,
97    pub sp_max: c_long,
98    pub sp_warn: c_long,
99    pub sp_inact: c_long,
100    pub sp_expire: c_long,
101    pub sp_flag: c_ulong,
102}
103
104#[derive(Debug)]
105enum Error {
106    EOF,
107    BufTooSmall,
108    Syntax,
109}
110
111#[derive(Debug)]
112struct OwnedSpwd {
113    buffer: MaybeAllocated,
114    reference: spwd,
115}
116
117impl OwnedSpwd {
118    fn into_global(self) -> *mut spwd {
119        unsafe {
120            SHADOW_BUF = Some(self.buffer);
121            SHADOW = self.reference;
122            &raw mut SHADOW
123        }
124    }
125}
126fn to_long(s: &str) -> c_long {
127    c_long::from_str(s).unwrap_or(-1)
128}
129fn to_ulong(s: &str) -> c_ulong {
130    c_ulong::from_str(s).unwrap_or(0)
131}
132
133fn parse_spwd(line: String, destbuf: Option<DestBuffer>) -> Result<OwnedSpwd, Error> {
134    let mut parts = line.split(SEPARATOR);
135
136    let sp_namp_str = parts.next().ok_or(Error::Syntax)?;
137    let sp_pwdp_str = parts.next().ok_or(Error::Syntax)?;
138
139    //TODO: these are not implemented redox-users crate
140    let sp_lstchg = to_long(parts.next().unwrap_or(""));
141    let sp_min = to_long(parts.next().unwrap_or(""));
142    let sp_max = to_long(parts.next().unwrap_or(""));
143    let sp_warn = to_long(parts.next().unwrap_or(""));
144    let sp_inact = to_long(parts.next().unwrap_or(""));
145    let sp_expire = to_long(parts.next().unwrap_or(""));
146    let sp_flag = to_ulong(parts.next().unwrap_or(""));
147
148    let string_data_len = sp_namp_str.len() + 1 + sp_pwdp_str.len() + 1;
149
150    let mut buffer = match destbuf {
151        Some(buf) => {
152            if buf.len < string_data_len {
153                platform::ERRNO.set(ERANGE);
154                return Err(Error::BufTooSmall);
155            }
156            MaybeAllocated::Borrowed(buf)
157        }
158        None => {
159            let vec = vec![0; string_data_len];
160            MaybeAllocated::Owned(Box::into_pin(vec.into_boxed_slice()))
161        }
162    };
163
164    let (name_slice, rest) = buffer.split_at_mut(sp_namp_str.len() + 1);
165    name_slice[..sp_namp_str.len()].copy_from_slice(sp_namp_str.as_bytes());
166    name_slice[sp_namp_str.len()] = 0;
167
168    let (pwd_slice, _) = rest.split_at_mut(sp_pwdp_str.len() + 1);
169    pwd_slice[..sp_pwdp_str.len()].copy_from_slice(sp_pwdp_str.as_bytes());
170    pwd_slice[sp_pwdp_str.len()] = 0;
171
172    let sp_namp = name_slice.as_mut_ptr().cast::<c_char>();
173    let sp_pwdp = pwd_slice.as_mut_ptr().cast::<c_char>();
174
175    let reference = spwd {
176        sp_namp,
177        sp_pwdp,
178        sp_lstchg,
179        sp_min,
180        sp_max,
181        sp_warn,
182        sp_inact,
183        sp_expire,
184        sp_flag,
185    };
186
187    Ok(OwnedSpwd { buffer, reference })
188}
189
190/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getspnam.3.html>.
191#[unsafe(no_mangle)]
192pub unsafe extern "C" fn getspnam(name: *const c_char) -> *mut spwd {
193    let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) else {
194        return ptr::null_mut();
195    };
196    let c_name = unsafe { CStr::from_ptr(name) };
197
198    for line in BufReader::new(db).lines() {
199        let Ok(line) = line else { continue };
200        if line.starts_with(c_name.to_str().unwrap_or("\0"))
201            && let Ok(pwd) = parse_spwd(line, None)
202        {
203            return pwd.into_global();
204        }
205    }
206    ptr::null_mut()
207}
208
209/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getspnam.3.html>.
210#[unsafe(no_mangle)]
211pub unsafe extern "C" fn getspnam_r(
212    name: *const c_char,
213    result_buf: *mut spwd,
214    buffer: *mut c_char,
215    buflen: size_t,
216    result: *mut *mut spwd,
217) -> c_int {
218    unsafe { *result = ptr::null_mut() };
219
220    let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) else {
221        return ENOENT;
222    };
223    let c_name = unsafe { CStr::from_ptr(name).to_str().unwrap_or("\0") };
224
225    for line in BufReader::new(db).lines() {
226        let Ok(line) = line else { continue };
227        if line.starts_with(c_name) {
228            let dest_buf = Some(DestBuffer {
229                ptr: buffer.cast::<u8>(),
230                len: buflen,
231            });
232            return match parse_spwd(line, dest_buf) {
233                Ok(sp) => {
234                    unsafe {
235                        *result_buf = sp.reference;
236                        *result = result_buf;
237                    }
238                    0
239                }
240                Err(Error::BufTooSmall) => ERANGE,
241                _ => ENOENT,
242            };
243        }
244    }
245    ENOENT
246}
247
248/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getspnam.3.html>.
249#[unsafe(no_mangle)]
250pub unsafe extern "C" fn setspent() {
251    let line_reader = unsafe { &mut *LINE_READER.get() };
252    if let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) {
253        *line_reader = Some(BufReader::new(db).lines());
254    }
255}
256
257/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getspnam.3.html>.
258#[unsafe(no_mangle)]
259pub unsafe extern "C" fn endspent() {
260    unsafe {
261        *LINE_READER.get() = None;
262    }
263}
264
265/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getspnam.3.html>.
266#[unsafe(no_mangle)]
267pub unsafe extern "C" fn getspent() -> *mut spwd {
268    let line_reader = unsafe { &mut *LINE_READER.get() };
269    if line_reader.is_none() {
270        unsafe {
271            setspent();
272        }
273    }
274    if let Some(lines) = line_reader
275        && let Some(Ok(line)) = lines.next()
276        && let Ok(sp) = parse_spwd(line, None)
277    {
278        return sp.into_global();
279    }
280    ptr::null_mut()
281}