Mercurial > crates > nonstick
comparison src/libpam/environ.rs @ 100:3f11b8d30f63
Implement environment variable management.
This actually wires up the environment variable handling to libpam,
so that applications and modules can manage the environment through
the authentication process.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 24 Jun 2025 17:08:01 -0400 |
parents | b87100c5eed4 |
children | dfcd96a74ac4 |
comparison
equal
deleted
inserted
replaced
99:8840fa6534f6 | 100:3f11b8d30f63 |
---|---|
1 #![allow(unused_variables)] // for now | 1 #![allow(unused_variables)] // for now |
2 use crate::environ::EnvironMapMut; | 2 use crate::constants::{ErrorCode, Result}; |
3 use crate::environ::{EnvironMap, EnvironMapMut}; | |
3 use crate::libpam::memory::CHeapString; | 4 use crate::libpam::memory::CHeapString; |
4 use crate::{EnvironMap, LibPamHandle}; | 5 use crate::libpam::{memory, pam_ffi, LibPamHandle}; |
5 use std::ffi::{c_char, OsStr, OsString}; | 6 use std::ffi::{c_char, CStr, CString, OsStr, OsString}; |
6 use std::os::unix::ffi::OsStrExt; | 7 use std::marker::PhantomData; |
8 use std::os::unix::ffi::{OsStrExt, OsStringExt}; | |
9 use std::ptr; | |
7 use std::ptr::NonNull; | 10 use std::ptr::NonNull; |
8 use std::{iter, ptr}; | |
9 use crate::libpam::memory; | |
10 | 11 |
11 pub struct LibPamEnviron<'a> { | 12 pub struct LibPamEnviron<'a> { |
12 source: &'a LibPamHandle, | 13 source: &'a LibPamHandle, |
13 } | 14 } |
14 | 15 |
15 pub struct LibPamEnvironMut<'a> { | 16 pub struct LibPamEnvironMut<'a> { |
16 source: &'a mut LibPamHandle, | 17 source: &'a mut LibPamHandle, |
18 } | |
19 | |
20 impl LibPamHandle { | |
21 fn environ_get(&self, key: &OsStr) -> Option<OsString> { | |
22 let key = CString::new(key.as_bytes()).ok()?; | |
23 // SAFETY: We are a valid handle and are calling with a good key. | |
24 unsafe { | |
25 copy_env(pam_ffi::pam_getenv( | |
26 (self as *const LibPamHandle).cast_mut(), | |
27 key.as_ptr(), | |
28 )) | |
29 } | |
30 } | |
31 | |
32 fn environ_set(&mut self, key: &OsStr, value: Option<&OsStr>) -> Result<Option<OsString>> { | |
33 let old = self.environ_get(key); | |
34 let total_len = key.len() + value.map(OsStr::len).unwrap_or_default() + 2; | |
35 let mut result = Vec::with_capacity(total_len); | |
36 result.extend(key.as_bytes()); | |
37 if let Some(value) = value { | |
38 result.push(b'='); | |
39 result.extend(value.as_bytes()); | |
40 } | |
41 let put = CString::new(result).map_err(|_| ErrorCode::ConversationError)?; | |
42 // SAFETY: This is a valid handle and a valid environment string. | |
43 ErrorCode::result_from(unsafe { pam_ffi::pam_putenv(self, put.as_ptr()) })?; | |
44 Ok(old) | |
45 } | |
46 | |
47 fn environ_iter(&self) -> Result<impl Iterator<Item = (OsString, OsString)>> { | |
48 // SAFETY: This is a valid PAM handle. | |
49 unsafe { | |
50 NonNull::new(pam_ffi::pam_getenvlist( | |
51 (self as *const LibPamHandle).cast_mut(), | |
52 )) | |
53 .map(|ptr| EnvList::from_ptr(ptr.cast())) | |
54 .ok_or(ErrorCode::BufferError) | |
55 } | |
56 } | |
57 } | |
58 | |
59 /// Copies the data of the given C string pointer to an OsString, | |
60 /// or None if src is null. | |
61 unsafe fn copy_env(src: *const c_char) -> Option<OsString> { | |
62 let val = match NonNull::new(src.cast_mut()) { | |
63 None => return None, | |
64 Some(ptr) => ptr.as_ptr(), | |
65 }; | |
66 // SAFETY: We were just returned this string from PAM. | |
67 // We have to trust it. | |
68 let c_str = unsafe { CStr::from_ptr(val) }; | |
69 Some(OsString::from_vec(c_str.to_bytes().to_vec())) | |
17 } | 70 } |
18 | 71 |
19 impl<'a> LibPamEnviron<'a> { | 72 impl<'a> LibPamEnviron<'a> { |
20 pub fn new(source: &'a LibPamHandle) -> Self { | 73 pub fn new(source: &'a LibPamHandle) -> Self { |
21 Self { source } | 74 Self { source } |
24 | 77 |
25 impl<'a> LibPamEnvironMut<'a> { | 78 impl<'a> LibPamEnvironMut<'a> { |
26 pub fn new(source: &'a mut LibPamHandle) -> Self { | 79 pub fn new(source: &'a mut LibPamHandle) -> Self { |
27 Self { source } | 80 Self { source } |
28 } | 81 } |
29 | 82 } |
30 fn immut(&self) -> LibPamEnviron { | 83 |
31 LibPamEnviron { | 84 impl EnvironMap<'_> for LibPamEnviron<'_> { |
32 source: self.source, | 85 fn get(&self, key: impl AsRef<OsStr>) -> Option<OsString> { |
33 } | 86 self.source.environ_get(key.as_ref()) |
34 } | 87 } |
35 } | 88 |
36 | 89 fn iter(&self) -> Result<impl Iterator<Item = (OsString, OsString)>> { |
37 impl EnvironMap for LibPamEnviron<'_> { | 90 self.source.environ_iter() |
38 fn get(&self, val: &OsStr) -> Option<&OsStr> { | 91 } |
39 todo!() | 92 } |
40 } | 93 |
41 | 94 impl EnvironMap<'_> for LibPamEnvironMut<'_> { |
42 fn iter(&self) -> impl Iterator<Item = (&OsStr, &OsStr)> { | 95 fn get(&self, key: impl AsRef<OsStr>) -> Option<OsString> { |
43 iter::from_fn(|| todo!()) | 96 self.source.environ_get(key.as_ref()) |
44 } | 97 } |
45 } | 98 |
46 | 99 fn iter(&self) -> Result<impl Iterator<Item = (OsString, OsString)>> { |
47 impl EnvironMap for LibPamEnvironMut<'_> { | 100 self.source.environ_iter() |
48 fn get(&self, val: &OsStr) -> Option<&OsStr> { | 101 } |
49 todo!() | 102 } |
50 } | 103 |
51 | 104 impl EnvironMapMut<'_> for LibPamEnvironMut<'_> { |
52 fn iter(&self) -> impl Iterator<Item = (&OsStr, &OsStr)> { | 105 fn insert( |
53 iter::from_fn(|| todo!()) | 106 &mut self, |
54 } | 107 key: impl AsRef<OsStr>, |
55 } | 108 val: impl AsRef<OsStr>, |
56 | 109 ) -> Result<Option<OsString>> { |
57 impl EnvironMapMut for LibPamEnvironMut<'_> { | 110 self.source.environ_set(key.as_ref(), Some(val.as_ref())) |
58 fn insert(&mut self, key: &OsStr, val: &OsStr) -> Option<OsString> { | 111 } |
59 todo!() | 112 |
60 } | 113 fn remove(&mut self, key: impl AsRef<OsStr>) -> Result<Option<OsString>> { |
61 | 114 self.source.environ_set(key.as_ref(), None) |
62 fn remove(&mut self, key: &OsStr) -> Option<OsString> { | 115 } |
63 todo!() | 116 } |
64 } | 117 |
65 } | 118 struct EnvList<'a> { |
66 | |
67 struct EnvList { | |
68 /// Pointer to the start of the environment variable list. | 119 /// Pointer to the start of the environment variable list. |
69 /// | 120 /// |
70 /// This can't be a `CHeapBox` because it's not just a single | 121 /// This can't be a `CHeapBox` because it's not just a single |
71 /// `Option<EnvVar>`. | 122 /// `Option<EnvVar>`. |
72 vars: NonNull<Option<EnvVar>>, | 123 start: NonNull<Option<EnvVar>>, |
73 } | 124 /// The environment variable we're about to iterate into. |
74 | 125 current: NonNull<Option<EnvVar>>, |
75 impl EnvList { | 126 _owner: PhantomData<&'a LibPamHandle>, |
127 } | |
128 | |
129 impl EnvList<'_> { | |
76 unsafe fn from_ptr(ptr: NonNull<*mut c_char>) -> Self { | 130 unsafe fn from_ptr(ptr: NonNull<*mut c_char>) -> Self { |
77 Self{vars: ptr.cast()} | 131 Self { |
78 } | 132 start: ptr.cast(), |
79 | 133 current: ptr.cast(), |
80 fn iter(&self) -> impl Iterator<Item = (&OsStr, &OsStr)> { | 134 _owner: Default::default(), |
81 let mut current = self.vars; | 135 } |
82 iter::from_fn(move || { | 136 } |
83 match unsafe {current.as_ref()} { | 137 } |
84 None => None, | 138 impl Iterator for EnvList<'_> { |
85 Some(item) => { | 139 type Item = (OsString, OsString); |
86 let ret = item.as_kv(); | 140 |
87 current = unsafe {current.add(1) }; | 141 fn next(&mut self) -> Option<Self::Item> { |
88 Some(ret) | 142 // SAFETY: We were given a pointer to a valid environment list, |
143 // and we only ever advance it to the exact end of the list. | |
144 match unsafe { self.current.as_mut() } { | |
145 None => None, | |
146 Some(item) => { | |
147 let ret = item.as_kv(); | |
148 // SAFETY: We know we're still pointing to a valid pointer, | |
149 // and advancing it one more is allowed. | |
150 unsafe { | |
151 self.current = self.current.add(1); | |
152 ptr::drop_in_place(item as *mut EnvVar); | |
89 } | 153 } |
154 Some(ret) | |
90 } | 155 } |
91 }) | 156 } |
92 } | 157 } |
93 } | 158 } |
94 | 159 |
95 impl Drop for EnvList { | 160 impl Drop for EnvList<'_> { |
96 fn drop(&mut self) { | 161 fn drop(&mut self) { |
97 // SAFETY: We own this pointer, and we know it's valid environment data | 162 // SAFETY: We own self.start, and we know that self.current points to |
98 // from PAM. | 163 // either an item we haven't used, or to the None end. |
99 unsafe { | 164 unsafe { |
100 let mut var = self.vars; | 165 while let Some(var_ref) = self.current.as_mut() { |
101 while let Some(var_ref) = var.as_mut() { | |
102 ptr::drop_in_place(var_ref as *mut EnvVar); | 166 ptr::drop_in_place(var_ref as *mut EnvVar); |
103 var = var.add(1); | 167 self.current = self.current.add(1); |
104 } | 168 } |
105 memory::free(self.vars.as_ptr()) | 169 memory::free(self.start.as_ptr()) |
106 } | 170 } |
107 } | 171 } |
108 } | 172 } |
109 | 173 |
110 struct EnvVar(CHeapString); | 174 struct EnvVar(CHeapString); |
111 | 175 |
112 impl EnvVar { | 176 impl EnvVar { |
113 fn as_kv(&self) -> (&OsStr, &OsStr) { | 177 fn as_kv(&self) -> (OsString, OsString) { |
114 let bytes = self.0.to_bytes(); | 178 let bytes = self.0.to_bytes(); |
115 let mut split = bytes.splitn(2, |&b| b == b'='); | 179 let mut split = bytes.splitn(2, |&b| b == b'='); |
116 ( | 180 ( |
117 OsStr::from_bytes(split.next().unwrap_or_default()), | 181 OsString::from_vec(split.next().unwrap_or_default().into()), |
118 OsStr::from_bytes(split.next().unwrap_or_default()), | 182 OsString::from_vec(split.next().unwrap_or_default().into()), |
119 ) | 183 ) |
120 } | 184 } |
121 } | 185 } |
122 | 186 |
123 #[cfg(test)] | 187 #[cfg(test)] |
124 mod tests { | 188 mod tests { |
125 use crate::libpam::memory::CHeapBox; | |
126 use super::*; | 189 use super::*; |
190 | |
191 fn os(text: &str) -> OsString { | |
192 OsString::from_vec(text.into()) | |
193 } | |
127 | 194 |
128 #[test] | 195 #[test] |
129 fn test_split_kv() { | 196 fn test_split_kv() { |
130 fn test(input: &str, key: &str, value: &str) { | 197 fn test(input: &str, key: &str, value: &str) { |
131 let data = CHeapString::new(input).unwrap(); | 198 let data = CHeapString::new(input).unwrap(); |
132 let key = OsStr::from_bytes(key.as_bytes()); | 199 let key = os(key); |
133 let value = OsStr::from_bytes(value.as_bytes()); | 200 let value = os(value); |
134 | 201 |
135 assert_eq!(EnvVar(data).as_kv(), (key, value)); | 202 assert_eq!(EnvVar(data).as_kv(), (key, value)); |
136 } | 203 } |
137 test("THIS=that", "THIS", "that"); | 204 test("THIS=that", "THIS", "that"); |
138 test("THESE=those, no one=knows", "THESE", "those, no one=knows"); | 205 test("THESE=those, no one=knows", "THESE", "those, no one=knows"); |
139 test("HERE=", "HERE", ""); | 206 test("HERE=", "HERE", ""); |
140 test("SOME", "SOME", ""); | 207 test("SOME", "SOME", ""); |
141 test("", "", ""); | 208 test("", "", ""); |
142 } | 209 } |
143 | 210 |
211 fn env_list(strings: &[&'static str]) -> EnvList<'static> { | |
212 let ptrs: NonNull<Option<CHeapString>> = memory::calloc(strings.len() + 1).unwrap(); | |
213 unsafe { | |
214 for (idx, &text) in strings.iter().enumerate() { | |
215 ptr::write( | |
216 ptrs.add(idx).as_ptr(), | |
217 Some(CHeapString::new(text).unwrap()), | |
218 ) | |
219 } | |
220 ptr::write(ptrs.add(strings.len()).as_ptr(), None); | |
221 EnvList::from_ptr(ptrs.cast()) | |
222 } | |
223 } | |
224 | |
144 #[test] | 225 #[test] |
145 fn test_iter() { | 226 fn test_iter() { |
146 let bx = CHeapBox::new([ | 227 let envs = env_list(&["ONE=two", "BIRDS=birds=birds", "me", "you="]); |
147 Some("ONE=two"), | 228 let result: Vec<_> = envs.collect(); |
148 Some("BIRDS=birds=birds"), | 229 assert_eq!( |
149 Some("me"), | 230 vec![ |
150 Some("you="), | 231 (os("ONE"), os("two")), |
151 None, | 232 (os("BIRDS"), os("birds=birds")), |
152 ].map(|txt| txt.map(|txt| CHeapString::new(txt).unwrap()))).unwrap(); | 233 (os("me"), os("")), |
153 let env_ptr = CHeapBox::into_ptr(bx); | 234 (os("you"), os("")), |
154 | 235 ], |
155 let envs = unsafe {EnvList::from_ptr(env_ptr.cast())}; | 236 result |
156 let bytes = |data: &'static str| OsStr::from_bytes(data.as_ref()); | 237 ); |
157 let result: Vec<_> = envs.iter().collect(); | 238 } |
158 assert_eq!(vec![ | 239 |
159 (bytes("ONE"), bytes("two")), | 240 #[test] |
160 (bytes("BIRDS"), bytes("birds=birds")), | 241 fn test_iter_partial() { |
161 (bytes("me"), bytes("")), | 242 let mut envs = env_list(&[ |
162 (bytes("you"), bytes("")), | 243 "iterating=this", |
163 ], | 244 "also=here", |
164 result); | 245 "but not=this one", |
165 } | 246 "or even=the last", |
166 } | 247 ]); |
248 | |
249 assert_eq!(Some((os("iterating"), os("this"))), envs.next()); | |
250 assert_eq!(Some((os("also"), os("here"))), envs.next()); | |
251 // let envs drop | |
252 } | |
253 } |