Mercurial > crates > nonstick
comparison src/libpam/handle.rs @ 143:ebb71a412b58
Turn everything into OsString and Just Walk Out! for strings with nul.
To reduce the hazard surface of the API, this replaces most uses of &str
with &OsStr (and likewise with String/OsString).
Also, I've decided that instead of dealing with callers putting `\0`
in their parameters, I'm going to follow the example of std::env and
Just Walk Out! (i.e., panic!()).
This makes things a lot less annoying for both me and (hopefully) users.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 05 Jul 2025 22:12:46 -0400 |
parents | a508a69c068a |
children | 56b559b7ecea |
comparison
equal
deleted
inserted
replaced
142:5c1e315c18ff | 143:ebb71a412b58 |
---|---|
11 PamHandleModule, | 11 PamHandleModule, |
12 }; | 12 }; |
13 use libpam_sys_helpers::constants; | 13 use libpam_sys_helpers::constants; |
14 use num_enum::{IntoPrimitive, TryFromPrimitive}; | 14 use num_enum::{IntoPrimitive, TryFromPrimitive}; |
15 use std::cell::Cell; | 15 use std::cell::Cell; |
16 use std::ffi::{c_char, c_int, CString}; | 16 use std::ffi::{c_char, c_int, CString, OsStr, OsString}; |
17 use std::mem::ManuallyDrop; | 17 use std::mem::ManuallyDrop; |
18 use std::os::unix::ffi::OsStrExt; | |
18 use std::ptr; | 19 use std::ptr; |
19 use std::ptr::NonNull; | 20 use std::ptr::NonNull; |
20 | 21 |
21 /// An owned PAM handle. | 22 /// An owned PAM handle. |
22 pub struct OwnedLibPamHandle<C: Conversation> { | 23 pub struct OwnedLibPamHandle<C: Conversation> { |
34 conversation: Box<OwnedConversation<C>>, | 35 conversation: Box<OwnedConversation<C>>, |
35 } | 36 } |
36 | 37 |
37 #[derive(Debug, PartialEq)] | 38 #[derive(Debug, PartialEq)] |
38 pub struct HandleBuilder { | 39 pub struct HandleBuilder { |
39 service_name: String, | 40 service_name: OsString, |
40 username: Option<String>, | 41 username: Option<OsString>, |
41 } | 42 } |
42 | 43 |
43 impl HandleBuilder { | 44 impl HandleBuilder { |
44 /// Updates the service name. | 45 /// Updates the service name. |
45 pub fn service_name(mut self, service_name: String) -> Self { | 46 pub fn service_name(mut self, service_name: OsString) -> Self { |
46 self.service_name = service_name; | 47 self.service_name = service_name; |
47 self | 48 self |
48 } | 49 } |
49 /// Sets the username. Setting this will avoid the need for an extra | 50 /// Sets the username. Setting this will avoid the need for an extra |
50 /// round trip through the conversation and may otherwise improve | 51 /// round trip through the conversation and may otherwise improve |
51 /// the login experience. | 52 /// the login experience. |
52 pub fn username(mut self, username: String) -> Self { | 53 pub fn username(mut self, username: OsString) -> Self { |
53 self.username = Some(username); | 54 self.username = Some(username); |
54 self | 55 self |
55 } | 56 } |
56 /// Builds a PAM handle and starts the transaction. | 57 /// Builds a PAM handle and starts the transaction. |
57 pub fn build(self, conv: impl Conversation) -> Result<OwnedLibPamHandle<impl Conversation>> { | 58 pub fn build(self, conv: impl Conversation) -> Result<OwnedLibPamHandle<impl Conversation>> { |
69 /// # References | 70 /// # References |
70 #[doc = linklist!(pam_start: adg, _std)] | 71 #[doc = linklist!(pam_start: adg, _std)] |
71 /// | 72 /// |
72 #[doc = stdlinks!(3 pam_start)] | 73 #[doc = stdlinks!(3 pam_start)] |
73 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")] | 74 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")] |
74 pub fn build_with_service(service_name: String) -> HandleBuilder { | 75 pub fn build_with_service(service_name: OsString) -> HandleBuilder { |
75 HandleBuilder { | 76 HandleBuilder { |
76 service_name, | 77 service_name, |
77 username: None, | 78 username: None, |
78 } | 79 } |
79 } | 80 } |
80 | 81 |
81 fn start(service_name: String, username: Option<String>, conversation: C) -> Result<Self> { | 82 fn start(service_name: OsString, username: Option<OsString>, conversation: C) -> Result<Self> { |
82 let conv = Box::new(OwnedConversation::new(conversation)); | 83 let conv = Box::new(OwnedConversation::new(conversation)); |
83 let service_cstr = CString::new(service_name).map_err(|_| ErrorCode::ConversationError)?; | 84 let service_cstr = CString::new(service_name.as_bytes()).expect("null is forbidden"); |
84 let username_cstr = memory::prompt_ptr(memory::option_cstr(username.as_deref())?.as_ref()); | 85 let username_cstr = memory::option_cstr_os(username.as_deref()); |
86 let username_cstr = memory::prompt_ptr(username_cstr.as_deref()); | |
85 | 87 |
86 let mut handle: *mut libpam_sys::pam_handle = ptr::null_mut(); | 88 let mut handle: *mut libpam_sys::pam_handle = ptr::null_mut(); |
87 // SAFETY: We've set everything up properly to call `pam_start`. | 89 // SAFETY: We've set everything up properly to call `pam_start`. |
88 // The returned value will be a valid pointer provided the result is OK. | 90 // The returned value will be a valid pointer provided the result is OK. |
89 let result = unsafe { | 91 let result = unsafe { |
189 self.handle.$meth($($param),*) | 191 self.handle.$meth($($param),*) |
190 } | 192 } |
191 }; | 193 }; |
192 // Then have item getters / setters | 194 // Then have item getters / setters |
193 (get = $get:ident$(, set = $set:ident)?) => { | 195 (get = $get:ident$(, set = $set:ident)?) => { |
194 delegate!(fn $get(&self) -> Result<Option<String>>); | 196 delegate!(fn $get(&self) -> Result<Option<OsString>>); |
195 $(delegate!(set = $set);)? | 197 $(delegate!(set = $set);)? |
196 }; | 198 }; |
197 (set = $set:ident) => { | 199 (set = $set:ident) => { |
198 delegate!(fn $set(&mut self, value: Option<&str>) -> Result<()>); | 200 delegate!(fn $set(&mut self, value: Option<&OsStr>) -> Result<()>); |
199 }; | 201 }; |
200 } | 202 } |
201 | 203 |
202 fn split<T>(result: &Result<T>) -> Result<()> { | 204 fn split<T>(result: &Result<T>) -> Result<()> { |
203 result.as_ref().map(drop).map_err(|&e| e) | 205 result.as_ref().map(drop).map_err(|&e| e) |
205 | 207 |
206 impl<C: Conversation> PamShared for OwnedLibPamHandle<C> { | 208 impl<C: Conversation> PamShared for OwnedLibPamHandle<C> { |
207 delegate!(fn log(&self, level: Level, location: Location<'_>, entry: &str) -> ()); | 209 delegate!(fn log(&self, level: Level, location: Location<'_>, entry: &str) -> ()); |
208 delegate!(fn environ(&self) -> impl EnvironMap); | 210 delegate!(fn environ(&self) -> impl EnvironMap); |
209 delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut); | 211 delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut); |
210 delegate!(fn username(&mut self, prompt: Option<&str>) -> Result<String>); | 212 delegate!(fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString>); |
211 delegate!(get = user_item, set = set_user_item); | 213 delegate!(get = user_item, set = set_user_item); |
212 delegate!(get = service, set = set_service); | 214 delegate!(get = service, set = set_service); |
213 delegate!(get = user_prompt, set = set_user_prompt); | 215 delegate!(get = user_prompt, set = set_user_prompt); |
214 delegate!(get = tty_name, set = set_tty_name); | 216 delegate!(get = tty_name, set = set_tty_name); |
215 delegate!(get = remote_user, set = set_remote_user); | 217 delegate!(get = remote_user, set = set_remote_user); |
219 } | 221 } |
220 | 222 |
221 /// Macro to implement getting/setting a CStr-based item. | 223 /// Macro to implement getting/setting a CStr-based item. |
222 macro_rules! cstr_item { | 224 macro_rules! cstr_item { |
223 (get = $getter:ident, item = $item_type:path) => { | 225 (get = $getter:ident, item = $item_type:path) => { |
224 fn $getter(&self) -> Result<Option<String>> { | 226 fn $getter(&self) -> Result<Option<OsString>> { |
225 unsafe { self.get_cstr_item($item_type) } | 227 unsafe { self.get_cstr_item($item_type) } |
226 } | 228 } |
227 }; | 229 }; |
228 (set = $setter:ident, item = $item_type:path) => { | 230 (set = $setter:ident, item = $item_type:path) => { |
229 fn $setter(&mut self, value: Option<&str>) -> Result<()> { | 231 fn $setter(&mut self, value: Option<&OsStr>) -> Result<()> { |
230 unsafe { self.set_cstr_item($item_type, value) } | 232 unsafe { self.set_cstr_item($item_type, value) } |
231 } | 233 } |
232 }; | 234 }; |
233 } | 235 } |
234 | 236 |
326 fn log(&self, level: Level, loc: Location<'_>, entry: &str) { | 328 fn log(&self, level: Level, loc: Location<'_>, entry: &str) { |
327 let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) { | 329 let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) { |
328 Ok(cstr) => cstr, | 330 Ok(cstr) => cstr, |
329 _ => return, | 331 _ => return, |
330 }; | 332 }; |
331 #[cfg(pam_impl = "linux-pam")] | 333 #[cfg(pam_impl = "LinuxPam")] |
332 { | 334 { |
333 _ = loc; | 335 _ = loc; |
334 // SAFETY: We're calling this function with a known value. | 336 // SAFETY: We're calling this function with a known value. |
335 unsafe { | 337 unsafe { |
336 libpam_sys::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr()) | 338 libpam_sys::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr()) |
337 } | 339 } |
338 } | 340 } |
339 #[cfg(pam_impl = "openpam")] | 341 #[cfg(pam_impl = "OpenPam")] |
340 { | 342 { |
341 let func = CString::new(loc.function).unwrap_or(CString::default()); | 343 let func = CString::new(loc.function).unwrap_or(CString::default()); |
342 // SAFETY: We're calling this function with a known value. | 344 // SAFETY: We're calling this function with a known value. |
343 unsafe { | 345 unsafe { |
344 libpam_sys::_openpam_log( | 346 libpam_sys::_openpam_log( |
351 } | 353 } |
352 } | 354 } |
353 | 355 |
354 fn log(&self, _level: Level, _loc: Location<'_>, _entry: &str) {} | 356 fn log(&self, _level: Level, _loc: Location<'_>, _entry: &str) {} |
355 | 357 |
356 fn username(&mut self, prompt: Option<&str>) -> Result<String> { | 358 fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { |
357 let prompt = memory::option_cstr(prompt)?; | 359 let prompt = memory::option_cstr_os(prompt); |
358 let mut output: *const c_char = ptr::null(); | 360 let mut output: *const c_char = ptr::null(); |
359 let ret = unsafe { | 361 let ret = unsafe { |
360 libpam_sys::pam_get_user( | 362 libpam_sys::pam_get_user( |
361 self.raw_mut(), | 363 self.raw_mut(), |
362 &mut output, | 364 &mut output, |
363 memory::prompt_ptr(prompt.as_ref()), | 365 memory::prompt_ptr(prompt.as_deref()), |
364 ) | 366 ) |
365 }; | 367 }; |
366 ErrorCode::result_from(ret)?; | 368 ErrorCode::result_from(ret)?; |
367 unsafe { memory::copy_pam_string(output) } | 369 Ok(unsafe { memory::copy_pam_string(output).ok_or(ErrorCode::ConversationError)? }) |
368 .transpose() | |
369 .unwrap_or(Err(ErrorCode::ConversationError)) | |
370 } | 370 } |
371 | 371 |
372 fn environ(&self) -> impl EnvironMap { | 372 fn environ(&self) -> impl EnvironMap { |
373 LibPamEnviron::new(self) | 373 LibPamEnviron::new(self) |
374 } | 374 } |
405 } | 405 } |
406 } | 406 } |
407 } | 407 } |
408 | 408 |
409 impl PamHandleModule for RawPamHandle { | 409 impl PamHandleModule for RawPamHandle { |
410 fn authtok(&mut self, prompt: Option<&str>) -> Result<String> { | 410 fn authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { |
411 self.get_authtok(prompt, ItemType::AuthTok) | 411 self.get_authtok(prompt, ItemType::AuthTok) |
412 } | 412 } |
413 | 413 |
414 fn old_authtok(&mut self, prompt: Option<&str>) -> Result<String> { | 414 fn old_authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { |
415 self.get_authtok(prompt, ItemType::OldAuthTok) | 415 self.get_authtok(prompt, ItemType::OldAuthTok) |
416 } | 416 } |
417 | 417 |
418 cstr_item!(get = authtok_item, item = ItemType::AuthTok); | 418 cstr_item!(get = authtok_item, item = ItemType::AuthTok); |
419 cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); | 419 cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); |
430 } | 430 } |
431 | 431 |
432 // Implementations of internal functions. | 432 // Implementations of internal functions. |
433 impl RawPamHandle { | 433 impl RawPamHandle { |
434 #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))] | 434 #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))] |
435 fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> { | 435 fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> { |
436 let prompt = memory::option_cstr(prompt)?; | 436 let prompt = memory::option_cstr_os(prompt); |
437 let mut output: *const c_char = ptr::null_mut(); | 437 let mut output: *const c_char = ptr::null_mut(); |
438 // SAFETY: We're calling this with known-good values. | 438 // SAFETY: We're calling this with known-good values. |
439 let res = unsafe { | 439 let res = unsafe { |
440 libpam_sys::pam_get_authtok( | 440 libpam_sys::pam_get_authtok( |
441 self.raw_mut(), | 441 self.raw_mut(), |
442 item_type.into(), | 442 item_type.into(), |
443 &mut output, | 443 &mut output, |
444 memory::prompt_ptr(prompt.as_ref()), | 444 memory::prompt_ptr(prompt.as_deref()), |
445 ) | 445 ) |
446 }; | 446 }; |
447 ErrorCode::result_from(res)?; | 447 ErrorCode::result_from(res)?; |
448 // SAFETY: We got this string from PAM. | 448 // SAFETY: We got this string from PAM. |
449 unsafe { memory::copy_pam_string(output) } | 449 unsafe { memory::copy_pam_string(output) }.ok_or(ErrorCode::ConversationError) |
450 .transpose() | |
451 .unwrap_or(Err(ErrorCode::ConversationError)) | |
452 } | 450 } |
453 | 451 |
454 #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))] | 452 #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))] |
455 fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> { | 453 fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> { |
456 Err(ErrorCode::ConversationError) | 454 Err(ErrorCode::ConversationError) |
459 /// Gets a C string item. | 457 /// Gets a C string item. |
460 /// | 458 /// |
461 /// # Safety | 459 /// # Safety |
462 /// | 460 /// |
463 /// You better be requesting an item which is a C string. | 461 /// You better be requesting an item which is a C string. |
464 unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<String>> { | 462 unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<OsString>> { |
465 let mut output = ptr::null(); | 463 let mut output = ptr::null(); |
466 let ret = | 464 let ret = |
467 unsafe { libpam_sys::pam_get_item(self.raw_ref(), item_type as c_int, &mut output) }; | 465 unsafe { libpam_sys::pam_get_item(self.raw_ref(), item_type as c_int, &mut output) }; |
468 ErrorCode::result_from(ret)?; | 466 ErrorCode::result_from(ret)?; |
469 memory::copy_pam_string(output.cast()) | 467 Ok(memory::copy_pam_string(output.cast())) |
470 } | 468 } |
471 | 469 |
472 /// Sets a C string item. | 470 /// Sets a C string item. |
473 /// | 471 /// |
474 /// # Safety | 472 /// # Safety |
475 /// | 473 /// |
476 /// You better be setting an item which is a C string. | 474 /// You better be setting an item which is a C string. |
477 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { | 475 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&OsStr>) -> Result<()> { |
478 let data_str = memory::option_cstr(data)?; | 476 let data_str = memory::option_cstr_os(data); |
479 let ret = unsafe { | 477 let ret = unsafe { |
480 libpam_sys::pam_set_item( | 478 libpam_sys::pam_set_item( |
481 self.raw_mut(), | 479 self.raw_mut(), |
482 item_type as c_int, | 480 item_type as c_int, |
483 memory::prompt_ptr(data_str.as_ref()).cast(), | 481 memory::prompt_ptr(data_str.as_deref()).cast(), |
484 ) | 482 ) |
485 }; | 483 }; |
486 ErrorCode::result_from(ret) | 484 ErrorCode::result_from(ret) |
487 } | 485 } |
488 | 486 |