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 |
