1use crate::base::{Error, Result};
9#[cfg(all(target_os = "macos", feature = "job-bless"))]
10use core_foundation::base::Boolean;
11use core_foundation::base::{CFTypeRef, TCFType};
12use core_foundation::bundle::CFBundleRef;
13use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
14#[cfg(all(target_os = "macos", feature = "job-bless"))]
15use core_foundation::error::CFError;
16#[cfg(all(target_os = "macos", feature = "job-bless"))]
17use core_foundation::error::CFErrorRef;
18use core_foundation::string::{CFString, CFStringRef};
19use security_framework_sys::authorization as sys;
20use security_framework_sys::base::errSecConversionError;
21use std::ffi::{CStr, CString};
22use std::fs::File;
23use std::marker::PhantomData;
24use std::mem::MaybeUninit;
25use std::os::raw::c_void;
26use std::ptr::addr_of;
27use sys::AuthorizationExternalForm;
28
29macro_rules! optional_str_to_cfref {
30 ($string:ident) => {{
31 $string
32 .map(CFString::new)
33 .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef())
34 }};
35}
36
37macro_rules! cstring_or_err {
38 ($x:expr) => {{
39 CString::new($x).map_err(|_| Error::from_code(errSecConversionError))
40 }};
41}
42
43bitflags::bitflags! {
44 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46 pub struct Flags: sys::AuthorizationFlags {
47 const DEFAULTS = sys::kAuthorizationFlagDefaults;
50
51 const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed;
53
54 const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights;
57
58 const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights;
61
62 const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights;
64
65 const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize;
68 }
69}
70
71impl Default for Flags {
72 #[inline(always)]
73 fn default() -> Self {
74 Self::DEFAULTS
75 }
76}
77
78#[repr(C)]
80pub struct AuthorizationItem(sys::AuthorizationItem);
81
82impl AuthorizationItem {
83 #[must_use]
88 pub fn name(&self) -> &str {
89 unsafe {
90 CStr::from_ptr(self.0.name)
91 .to_str()
92 .expect("AuthorizationItem::name failed to convert &str to CStr")
93 }
94 }
95
96 #[inline]
99 #[must_use]
100 pub fn value(&self) -> Option<&[u8]> {
101 if self.0.value.is_null() {
102 return None;
103 }
104
105 let value = unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) };
106
107 Some(value)
108 }
109}
110
111#[derive(Debug)]
113#[repr(C)]
114pub struct AuthorizationItemSet<'a> {
115 inner: *const sys::AuthorizationItemSet,
116 phantom: PhantomData<&'a sys::AuthorizationItemSet>,
117}
118
119impl Drop for AuthorizationItemSet<'_> {
120 #[inline]
121 fn drop(&mut self) {
122 unsafe {
123 sys::AuthorizationFreeItemSet(self.inner.cast_mut());
124 }
125 }
126}
127
128#[derive(Debug)]
131pub struct AuthorizationItemSetStorage {
132 names: Vec<CString>,
136 values: Vec<Option<Vec<u8>>>,
137 items: Vec<sys::AuthorizationItem>,
138
139 pub set: sys::AuthorizationItemSet,
144}
145
146impl Default for AuthorizationItemSetStorage {
147 #[inline]
148 fn default() -> Self {
149 Self {
150 names: Vec::new(),
151 values: Vec::new(),
152 items: Vec::new(),
153 set: sys::AuthorizationItemSet {
154 count: 0,
155 items: std::ptr::null_mut(),
156 },
157 }
158 }
159}
160
161#[derive(Debug, Default)]
164pub struct AuthorizationItemSetBuilder {
165 storage: AuthorizationItemSetStorage,
166}
167
168impl AuthorizationItemSetBuilder {
170 #[inline(always)]
173 #[must_use]
174 pub fn new() -> Self {
175 Default::default()
176 }
177
178 pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> {
184 self.storage.names.push(cstring_or_err!(name)?);
185 self.storage.values.push(None);
186 Ok(self)
187 }
188
189 pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self>
194 where
195 N: Into<Vec<u8>>,
196 V: Into<Vec<u8>>,
197 {
198 self.storage.names.push(cstring_or_err!(name)?);
199 self.storage.values.push(Some(value.into()));
200 Ok(self)
201 }
202
203 pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self>
208 where
209 N: Into<Vec<u8>>,
210 V: Into<Vec<u8>>,
211 {
212 self.storage.names.push(cstring_or_err!(name)?);
213 self.storage
214 .values
215 .push(Some(cstring_or_err!(value)?.to_bytes().to_vec()));
216 Ok(self)
217 }
218
219 #[must_use]
222 pub fn build(mut self) -> AuthorizationItemSetStorage {
223 self.storage.items = self
224 .storage
225 .names
226 .iter()
227 .zip(self.storage.values.iter())
228 .map(|(n, v)| sys::AuthorizationItem {
229 name: n.as_ptr(),
230 value: v
231 .as_ref()
232 .map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void),
233 valueLength: v.as_ref().map_or(0, |v| v.len()),
234 flags: 0,
235 })
236 .collect();
237
238 self.storage.set = sys::AuthorizationItemSet {
239 count: self.storage.items.len() as u32,
240 items: self.storage.items.as_ptr().cast_mut(),
241 };
242
243 self.storage
244 }
245}
246
247#[derive(Copy, Clone)]
249pub enum RightDefinition<'a> {
250 FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>),
252
253 FromExistingRight(&'a str),
255}
256
257#[derive(Debug)]
260pub struct Authorization {
261 handle: sys::AuthorizationRef,
262 free_flags: Flags,
263}
264
265impl TryFrom<AuthorizationExternalForm> for Authorization {
266 type Error = Error;
267
268 #[cold]
270 fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> {
271 let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
272
273 let status = unsafe {
274 sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr())
275 };
276
277 if status != sys::errAuthorizationSuccess {
278 return Err(Error::from_code(status));
279 }
280
281 let auth = Self {
282 handle: unsafe { handle.assume_init() },
283 free_flags: Flags::default(),
284 };
285
286 Ok(auth)
287 }
288}
289
290impl Authorization {
291 #[inline]
294 #[allow(clippy::should_implement_trait)]
295 pub fn default() -> Result<Self> {
296 Self::new(None, None, Default::default())
297 }
298
299 #[allow(clippy::unnecessary_cast)]
310 #[allow(clippy::needless_pass_by_value)]
311 pub fn new(
312 rights: Option<AuthorizationItemSetStorage>,
314 environment: Option<AuthorizationItemSetStorage>,
315 flags: Flags,
316 ) -> Result<Self> {
317 let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| {
318 addr_of!(r.set).cast::<sys::AuthorizationItemSet>()
319 });
320
321 let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| {
322 addr_of!(e.set).cast::<sys::AuthorizationItemSet>()
323 });
324
325 let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
326
327 let status = unsafe {
328 sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr())
329 };
330
331 if status != sys::errAuthorizationSuccess {
332 return Err(Error::from_code(status));
333 }
334
335 Ok(Self {
336 handle: unsafe { handle.assume_init() },
337 free_flags: Default::default(),
338 })
339 }
340
341 #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")]
343 pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> {
344 external_form.try_into()
345 }
346
347 #[inline(always)]
351 pub fn destroy_rights(mut self) {
352 self.free_flags = Flags::DESTROY_RIGHTS;
353 }
354
355 pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> {
364 let name = cstring_or_err!(name)?;
365 let mut dict = MaybeUninit::<CFDictionaryRef>::uninit();
366
367 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) };
368
369 if status != sys::errAuthorizationSuccess {
370 return Err(Error::from_code(status));
371 }
372
373 let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) };
374
375 Ok(dict)
376 }
377
378 pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> {
384 let name = cstring_or_err!(name)?;
385
386 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) };
387
388 Ok(status == sys::errAuthorizationSuccess)
389 }
390
391 pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> {
398 let name = cstring_or_err!(name)?;
399
400 let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) };
401
402 if status != sys::errAuthorizationSuccess {
403 return Err(Error::from_code(status));
404 }
405
406 Ok(())
407 }
408
409 pub fn set_right<T: Into<Vec<u8>>>(
429 &self,
430 name: T,
431 definition: RightDefinition<'_>,
432 description: Option<&str>,
433 bundle: Option<CFBundleRef>,
434 locale: Option<&str>,
435 ) -> Result<()> {
436 let name = cstring_or_err!(name)?;
437
438 let definition_cfstring: CFString;
439 let definition_ref = match definition {
440 RightDefinition::FromDictionary(def) => def.as_CFTypeRef(),
441 RightDefinition::FromExistingRight(def) => {
442 definition_cfstring = CFString::new(def);
443 definition_cfstring.as_CFTypeRef()
444 },
445 };
446
447 let status = unsafe {
448 sys::AuthorizationRightSet(
449 self.handle,
450 name.as_ptr(),
451 definition_ref,
452 optional_str_to_cfref!(description),
453 bundle.unwrap_or(std::ptr::null_mut()),
454 optional_str_to_cfref!(locale),
455 )
456 };
457
458 if status != sys::errAuthorizationSuccess {
459 return Err(Error::from_code(status));
460 }
461
462 Ok(())
463 }
464
465 pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> {
475 let tag_with_nul: CString;
476
477 let tag_ptr = match tag {
478 Some(tag) => {
479 tag_with_nul = cstring_or_err!(tag)?;
480 tag_with_nul.as_ptr()
481 },
482 None => std::ptr::null(),
483 };
484
485 let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit();
486
487 let status = unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) };
488
489 if status != sys::errAuthorizationSuccess {
490 return Err(Error::from(status));
491 }
492
493 let set = AuthorizationItemSet {
494 inner: unsafe { inner.assume_init() },
495 phantom: PhantomData,
496 };
497
498 Ok(set)
499 }
500
501 pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> {
504 let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit();
505
506 let status = unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) };
507
508 if status != sys::errAuthorizationSuccess {
509 return Err(Error::from(status));
510 }
511
512 Ok(unsafe { external_form.assume_init() })
513 }
514
515 #[cfg(target_os = "macos")]
518 #[inline(always)]
519 pub fn execute_with_privileges<P, S, I>(
520 &self,
521 command: P,
522 arguments: I,
523 flags: Flags,
524 ) -> Result<()>
525 where
526 P: AsRef<std::path::Path>,
527 I: IntoIterator<Item = S>,
528 S: AsRef<std::ffi::OsStr>,
529 {
530 use std::os::unix::ffi::OsStrExt;
531
532 let arguments = arguments
533 .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes()))
534 .collect::<Vec<_>>();
535 self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, false)?;
536 Ok(())
537 }
538
539 #[cfg(target_os = "macos")]
542 #[inline(always)]
543 pub fn execute_with_privileges_piped<P, S, I>(
544 &self,
545 command: P,
546 arguments: I,
547 flags: Flags,
548 ) -> Result<File>
549 where
550 P: AsRef<std::path::Path>,
551 I: IntoIterator<Item = S>,
552 S: AsRef<std::ffi::OsStr>,
553 {
554 use std::os::unix::ffi::OsStrExt;
555
556 let arguments = arguments
557 .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes()))
558 .collect::<Vec<_>>();
559 Ok(self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, true)?.unwrap())
560 }
561
562 #[cfg(all(target_os = "macos", feature = "job-bless"))]
564 pub fn job_bless(&self, label: &str) -> Result<(), CFError> {
565 #[link(name = "ServiceManagement", kind = "framework")]
566 extern "C" {
567 static kSMDomainSystemLaunchd: CFStringRef;
568
569 fn SMJobBless(
570 domain: CFStringRef,
571 executableLabel: CFStringRef,
572 auth: sys::AuthorizationRef,
573 error: *mut CFErrorRef,
574 ) -> Boolean;
575 }
576
577 unsafe {
578 let mut error = std::ptr::null_mut();
579 SMJobBless(
580 kSMDomainSystemLaunchd,
581 CFString::new(label).as_concrete_TypeRef(),
582 self.handle,
583 &mut error,
584 );
585 if !error.is_null() {
586 return Err(CFError::wrap_under_create_rule(error));
587 }
588
589 Ok(())
590 }
591 }
592
593 #[cfg(target_os = "macos")]
595 fn execute_with_privileges_internal(
596 &self,
597 command: &[u8],
598 arguments: &[CString],
599 flags: Flags,
600 make_pipe: bool,
601 ) -> Result<Option<File>> {
602 use std::os::unix::io::{FromRawFd, RawFd};
603
604 let c_cmd = cstring_or_err!(command)?;
605
606 let mut c_args = arguments.iter().map(|a| a.as_ptr().cast_mut()).collect::<Vec<_>>();
607 c_args.push(std::ptr::null_mut());
608
609 let mut pipe: *mut libc::FILE = std::ptr::null_mut();
610
611 let status = unsafe {
612 sys::AuthorizationExecuteWithPrivileges(
613 self.handle,
614 c_cmd.as_ptr(),
615 flags.bits(),
616 c_args.as_ptr(),
617 if make_pipe { &mut pipe } else { std::ptr::null_mut() },
618 )
619 };
620
621 crate::cvt(status)?;
622 Ok(if make_pipe {
623 if pipe.is_null() {
624 return Err(Error::from_code(32)); }
626 Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) })
627 } else {
628 None
629 })
630 }
631}
632
633impl Drop for Authorization {
634 #[inline]
635 fn drop(&mut self) {
636 unsafe {
637 sys::AuthorizationFree(self.handle, self.free_flags.bits());
638 }
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645
646 #[test]
647 fn test_create_default_authorization() {
648 Authorization::default().unwrap();
649 }
650
651 #[test]
652 fn test_create_allowed_authorization() -> Result<()> {
653 let rights = AuthorizationItemSetBuilder::new()
654 .add_right("system.hdd.smart")?
655 .add_right("system.login.done")?
656 .build();
657
658 Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
659
660 Ok(())
661 }
662
663 #[test]
664 fn test_create_then_destroy_allowed_authorization() -> Result<()> {
665 let rights = AuthorizationItemSetBuilder::new()
666 .add_right("system.hdd.smart")?
667 .add_right("system.login.done")?
668 .build();
669
670 let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
671 auth.destroy_rights();
672
673 Ok(())
674 }
675
676 #[test]
677 fn test_create_authorization_requiring_interaction() -> Result<()> {
678 let rights = AuthorizationItemSetBuilder::new()
679 .add_right("system.privilege.admin")?
680 .build();
681
682 let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err();
683
684 assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed);
685
686 Ok(())
687 }
688
689 fn create_credentials_env() -> Result<AuthorizationItemSetStorage> {
690 let set = AuthorizationItemSetBuilder::new()
691 .add_string("username", std::env::var("USER").expect("You must set the USER environment variable"))?
692 .add_string("password", std::env::var("PASSWORD").expect("You must set the PASSWORD environment varible"))?
693 .build();
694
695 Ok(set)
696 }
697
698 #[test]
699 fn test_create_authorization_with_bad_credentials() -> Result<()> {
700 let rights = AuthorizationItemSetBuilder::new()
701 .add_right("system.privilege.admin")?
702 .build();
703
704 let env = AuthorizationItemSetBuilder::new()
705 .add_string("username", "Tim Apple")?
706 .add_string("password", "butterfly")?
707 .build();
708
709 let error =
710 Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err();
711
712 assert_eq!(error.code(), sys::errAuthorizationDenied);
713
714 Ok(())
715 }
716
717 #[test]
718 fn test_create_authorization_with_credentials() -> Result<()> {
719 if std::env::var_os("PASSWORD").is_none() {
720 return Ok(());
721 }
722
723 let rights = AuthorizationItemSetBuilder::new()
724 .add_right("system.privilege.admin")?
725 .build();
726
727 let env = create_credentials_env()?;
728
729 Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
730
731 Ok(())
732 }
733
734 #[test]
735 fn test_query_authorization_database() -> Result<()> {
736 assert!(Authorization::right_exists("system.hdd.smart")?);
737 assert!(!Authorization::right_exists("EMPTY")?);
738
739 let dict = Authorization::get_right("system.hdd.smart").unwrap();
740
741 let key = CFString::from_static_string("class");
742 assert!(dict.contains_key(&key));
743
744 let invalid_key = CFString::from_static_string("EMPTY");
745 assert!(!dict.contains_key(&invalid_key));
746
747 Ok(())
748 }
749
750 #[test]
752 fn test_modify_authorization_database() -> Result<()> {
753 if std::env::var_os("PASSWORD").is_none() {
754 return Ok(());
755 }
756
757 let rights = AuthorizationItemSetBuilder::new()
758 .add_right("config.modify.")?
759 .build();
760
761 let env = create_credentials_env()?;
762
763 let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
764
765 assert!(!Authorization::right_exists("TEST_RIGHT")?);
766
767 auth.set_right(
768 "TEST_RIGHT",
769 RightDefinition::FromExistingRight("system.hdd.smart"),
770 None,
771 None,
772 None,
773 )
774 .unwrap();
775
776 assert!(Authorization::right_exists("TEST_RIGHT")?);
777
778 auth.remove_right("TEST_RIGHT").unwrap();
779
780 assert!(!Authorization::right_exists("TEST_RIGHT")?);
781
782 Ok(())
783 }
784
785 #[test]
787 fn test_execute_with_privileges() -> Result<()> {
788 if std::env::var_os("PASSWORD").is_none() {
789 return Ok(());
790 }
791
792 let rights = AuthorizationItemSetBuilder::new()
793 .add_right("system.privilege.admin")?
794 .build();
795
796 let auth = Authorization::new(
797 Some(rights),
798 None,
799 Flags::DEFAULTS
800 | Flags::INTERACTION_ALLOWED
801 | Flags::PREAUTHORIZE
802 | Flags::EXTEND_RIGHTS,
803 )?;
804
805 let file = auth.execute_with_privileges_piped("/bin/ls", ["/"], Flags::DEFAULTS)?;
806
807 use std::io::{self, BufRead};
808 for line in io::BufReader::new(file).lines() {
809 let _ = line.unwrap();
810 }
811
812 Ok(())
813 }
814}