1use crate::base::{Error, Result};
10#[cfg(all(target_os = "macos", feature = "job-bless"))]
11use core_foundation::base::Boolean;
12use core_foundation::base::{CFTypeRef, TCFType};
13use core_foundation::bundle::CFBundleRef;
14use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
15#[cfg(all(target_os = "macos", feature = "job-bless"))]
16use core_foundation::error::CFError;
17#[cfg(all(target_os = "macos", feature = "job-bless"))]
18use core_foundation::error::CFErrorRef;
19use core_foundation::string::{CFString, CFStringRef};
20use apple_security_sys::authorization as sys;
21use apple_security_sys::base::errSecConversionError;
22use std::convert::TryFrom;
23use std::ffi::{CStr, CString};
24use std::fs::File;
25use std::mem::MaybeUninit;
26use std::os::raw::c_void;
27use std::ptr::addr_of;
28use std::{convert::TryInto, marker::PhantomData};
29use sys::AuthorizationExternalForm;
30
31macro_rules! optional_str_to_cfref {
32 ($string:ident) => {{
33 $string
34 .map(CFString::new)
35 .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef())
36 }};
37}
38
39macro_rules! cstring_or_err {
40 ($x:expr) => {{
41 CString::new($x).map_err(|_| Error::from_code(errSecConversionError))
42 }};
43}
44
45bitflags::bitflags! {
46 pub struct Flags: sys::AuthorizationFlags {
48 const DEFAULTS = sys::kAuthorizationFlagDefaults;
51
52 const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed;
54
55 const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights;
58
59 const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights;
62
63 const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights;
65
66 const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize;
69 }
70}
71
72impl Default for Flags {
73 #[inline(always)]
74 fn default() -> Flags {
75 Flags::DEFAULTS
76 }
77}
78
79#[repr(C)]
81pub struct AuthorizationItem(sys::AuthorizationItem);
82
83impl AuthorizationItem {
84 #[must_use] 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] pub fn value(&self) -> Option<&[u8]> {
100 if self.0.value.is_null() {
101 return None;
102 }
103
104 let value =
105 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<'a> Drop for AuthorizationItemSet<'a> {
120 #[inline]
121 fn drop(&mut self) {
122 unsafe {
123 sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet);
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 AuthorizationItemSetStorage {
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() -> AuthorizationItemSetBuilder {
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() as *mut sys::AuthorizationItem,
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 = Authorization {
282 handle: unsafe { handle.assume_init() },
283 free_flags: Default::default(),
284 };
285
286 Ok(auth)
287 }
288}
289
290impl Authorization {
291 #[inline]
294 pub fn default() -> Result<Self> {
295 Self::new(None, None, Default::default())
296 }
297
298 pub fn new(
309 rights: Option<AuthorizationItemSetStorage>,
311 environment: Option<AuthorizationItemSetStorage>,
312 flags: Flags,
313 ) -> Result<Self> {
314 let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| {
315 addr_of!(r.set) as *const sys::AuthorizationItemSet
316 });
317
318 let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| {
319 addr_of!(e.set) as *const sys::AuthorizationItemSet
320 });
321
322 let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
323
324 let status = unsafe {
325 sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr())
326 };
327
328 if status != sys::errAuthorizationSuccess {
329 return Err(Error::from_code(status));
330 }
331
332 Ok(Authorization {
333 handle: unsafe { handle.assume_init() },
334 free_flags: Default::default(),
335 })
336 }
337
338 #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")]
340 pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> {
341 external_form.try_into()
342 }
343
344 #[inline(always)]
348 pub fn destroy_rights(mut self) {
349 self.free_flags = Flags::DESTROY_RIGHTS;
350 }
351
352 pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> {
360 let name = cstring_or_err!(name)?;
361 let mut dict = MaybeUninit::<CFDictionaryRef>::uninit();
362
363 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) };
364
365 if status != sys::errAuthorizationSuccess {
366 return Err(Error::from_code(status));
367 }
368
369 let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) };
370
371 Ok(dict)
372 }
373
374 pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> {
380 let name = cstring_or_err!(name)?;
381
382 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) };
383
384 Ok(status == sys::errAuthorizationSuccess)
385 }
386
387 pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> {
394 let name = cstring_or_err!(name)?;
395
396 let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) };
397
398 if status != sys::errAuthorizationSuccess {
399 return Err(Error::from_code(status));
400 }
401
402 Ok(())
403 }
404
405 pub fn set_right<T: Into<Vec<u8>>>(
425 &self,
426 name: T,
427 definition: RightDefinition<'_>,
428 description: Option<&str>,
429 bundle: Option<CFBundleRef>,
430 locale: Option<&str>,
431 ) -> Result<()> {
432 let name = cstring_or_err!(name)?;
433
434 let definition_cfstring: CFString;
435 let definition_ref = match definition {
436 RightDefinition::FromDictionary(def) => def.as_CFTypeRef(),
437 RightDefinition::FromExistingRight(def) => {
438 definition_cfstring = CFString::new(def);
439 definition_cfstring.as_CFTypeRef()
440 }
441 };
442
443 let status = unsafe {
444 sys::AuthorizationRightSet(
445 self.handle,
446 name.as_ptr(),
447 definition_ref,
448 optional_str_to_cfref!(description),
449 bundle.unwrap_or(std::ptr::null_mut()),
450 optional_str_to_cfref!(locale),
451 )
452 };
453
454 if status != sys::errAuthorizationSuccess {
455 return Err(Error::from_code(status));
456 }
457
458 Ok(())
459 }
460
461 pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> {
471 let tag_with_nul: CString;
472
473 let tag_ptr = match tag {
474 Some(tag) => {
475 tag_with_nul = cstring_or_err!(tag)?;
476 tag_with_nul.as_ptr()
477 }
478 None => std::ptr::null(),
479 };
480
481 let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit();
482
483 let status =
484 unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) };
485
486 if status != sys::errAuthorizationSuccess {
487 return Err(Error::from(status));
488 }
489
490 let set = AuthorizationItemSet {
491 inner: unsafe { inner.assume_init() },
492 phantom: PhantomData,
493 };
494
495 Ok(set)
496 }
497
498 pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> {
501 let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit();
502
503 let status =
504 unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) };
505
506 if status != sys::errAuthorizationSuccess {
507 return Err(Error::from(status));
508 }
509
510 Ok(unsafe { external_form.assume_init() })
511 }
512
513 #[cfg(target_os = "macos")]
516 #[inline(always)]
517 pub fn execute_with_privileges<P, S, I>(
518 &self,
519 command: P,
520 arguments: I,
521 flags: Flags,
522 ) -> Result<()>
523 where
524 P: AsRef<std::path::Path>,
525 I: IntoIterator<Item = S>,
526 S: AsRef<std::ffi::OsStr>,
527 {
528 use std::os::unix::ffi::OsStrExt;
529
530 let arguments = arguments
531 .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes()))
532 .collect::<Vec<_>>();
533 self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, false)?;
534 Ok(())
535 }
536
537 #[cfg(target_os = "macos")]
540 #[inline(always)]
541 pub fn execute_with_privileges_piped<P, S, I>(
542 &self,
543 command: P,
544 arguments: I,
545 flags: Flags,
546 ) -> Result<File>
547 where
548 P: AsRef<std::path::Path>,
549 I: IntoIterator<Item = S>,
550 S: AsRef<std::ffi::OsStr>,
551 {
552 use std::os::unix::ffi::OsStrExt;
553
554 let arguments = arguments
555 .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes()))
556 .collect::<Vec<_>>();
557 Ok(self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, true)?.unwrap())
558 }
559
560 #[cfg(all(target_os = "macos", feature = "job-bless"))]
562 pub fn job_bless(&self, label: &str) -> Result<(), CFError> {
563 #[link(name = "ServiceManagement", kind = "framework")]
564 extern "C" {
565 static kSMDomainSystemLaunchd: CFStringRef;
566
567 fn SMJobBless(
568 domain: CFStringRef,
569 executableLabel: CFStringRef,
570 auth: sys::AuthorizationRef,
571 error: *mut CFErrorRef,
572 ) -> Boolean;
573 }
574
575 unsafe {
576 let mut error = std::ptr::null_mut();
577 SMJobBless(
578 kSMDomainSystemLaunchd,
579 CFString::new(label).as_concrete_TypeRef(),
580 self.handle,
581 &mut error,
582 );
583 if !error.is_null() {
584 return Err(CFError::wrap_under_create_rule(error));
585 }
586
587 Ok(())
588 }
589 }
590
591 #[cfg(target_os = "macos")]
593 fn execute_with_privileges_internal(
594 &self,
595 command: &[u8],
596 arguments: &[CString],
597 flags: Flags,
598 make_pipe: bool,
599 ) -> Result<Option<File>> {
600 use std::os::unix::io::{FromRawFd, RawFd};
601
602 let c_cmd = cstring_or_err!(command)?;
603
604 let mut c_args = arguments.iter().map(|a| a.as_ptr() as _).collect::<Vec<_>>();
605 c_args.push(std::ptr::null_mut());
606
607 let mut pipe: *mut libc::FILE = std::ptr::null_mut();
608
609 let status = unsafe {
610 sys::AuthorizationExecuteWithPrivileges(
611 self.handle,
612 c_cmd.as_ptr(),
613 flags.bits(),
614 c_args.as_ptr(),
615 if make_pipe { &mut pipe } else { std::ptr::null_mut() },
616 )
617 };
618
619 crate::cvt(status)?;
620 Ok(if make_pipe {
621 if pipe.is_null() {
622 return Err(Error::from_code(32)); }
624 Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) })
625 } else {
626 None
627 })
628 }
629}
630
631impl Drop for Authorization {
632 #[inline]
633 fn drop(&mut self) {
634 unsafe {
635 sys::AuthorizationFree(self.handle, self.free_flags.bits());
636 }
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643 use core_foundation::string::CFString;
644
645 #[test]
646 fn test_create_default_authorization() {
647 Authorization::default().unwrap();
648 }
649
650 #[test]
651 fn test_create_allowed_authorization() -> Result<()> {
652 let rights = AuthorizationItemSetBuilder::new()
653 .add_right("system.hdd.smart")?
654 .add_right("system.login.done")?
655 .build();
656
657 Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
658
659 Ok(())
660 }
661
662 #[test]
663 fn test_create_then_destroy_allowed_authorization() -> Result<()> {
664 let rights = AuthorizationItemSetBuilder::new()
665 .add_right("system.hdd.smart")?
666 .add_right("system.login.done")?
667 .build();
668
669 let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
670 auth.destroy_rights();
671
672 Ok(())
673 }
674
675 #[test]
676 fn test_create_authorization_requiring_interaction() -> Result<()> {
677 let rights = AuthorizationItemSetBuilder::new()
678 .add_right("system.privilege.admin")?
679 .build();
680
681 let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err();
682
683 assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed);
684
685 Ok(())
686 }
687
688 fn create_credentials_env() -> Result<AuthorizationItemSetStorage> {
689 let set = AuthorizationItemSetBuilder::new()
690 .add_string(
691 "username",
692 option_env!("USER").expect("You must set the USER environment variable"),
693 )?
694 .add_string(
695 "password",
696 option_env!("PASSWORD").expect("You must set the PASSWORD environment varible"),
697 )?
698 .build();
699
700 Ok(set)
701 }
702
703 #[test]
704 fn test_create_authorization_with_bad_credentials() -> Result<()> {
705 let rights = AuthorizationItemSetBuilder::new()
706 .add_right("system.privilege.admin")?
707 .build();
708
709 let env = AuthorizationItemSetBuilder::new()
710 .add_string("username", "Tim Apple")?
711 .add_string("password", "butterfly")?
712 .build();
713
714 let error =
715 Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err();
716
717 assert_eq!(error.code(), sys::errAuthorizationDenied);
718
719 Ok(())
720 }
721
722 #[test]
723 fn test_create_authorization_with_credentials() -> Result<()> {
724 if option_env!("PASSWORD").is_none() {
725 return Ok(());
726 }
727
728 let rights = AuthorizationItemSetBuilder::new()
729 .add_right("system.privilege.admin")?
730 .build();
731
732 let env = create_credentials_env()?;
733
734 Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
735
736 Ok(())
737 }
738
739 #[test]
740 fn test_query_authorization_database() -> Result<()> {
741 assert!(Authorization::right_exists("system.hdd.smart")?);
742 assert!(!Authorization::right_exists("EMPTY")?);
743
744 let dict = Authorization::get_right("system.hdd.smart").unwrap();
745
746 let key = CFString::from_static_string("class");
747 assert!(dict.contains_key(&key));
748
749 let invalid_key = CFString::from_static_string("EMPTY");
750 assert!(!dict.contains_key(&invalid_key));
751
752 Ok(())
753 }
754
755 #[test]
757 fn test_modify_authorization_database() -> Result<()> {
758 if option_env!("PASSWORD").is_none() {
759 return Ok(());
760 }
761
762 let rights = AuthorizationItemSetBuilder::new()
763 .add_right("config.modify.")?
764 .build();
765
766 let env = create_credentials_env()?;
767
768 let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
769
770 assert!(!Authorization::right_exists("TEST_RIGHT")?);
771
772 auth.set_right(
773 "TEST_RIGHT",
774 RightDefinition::FromExistingRight("system.hdd.smart"),
775 None,
776 None,
777 None,
778 )
779 .unwrap();
780
781 assert!(Authorization::right_exists("TEST_RIGHT")?);
782
783 auth.remove_right("TEST_RIGHT").unwrap();
784
785 assert!(!Authorization::right_exists("TEST_RIGHT")?);
786
787 Ok(())
788 }
789
790 #[test]
792 fn test_execute_with_privileges() -> Result<()> {
793 if option_env!("PASSWORD").is_none() {
794 return Ok(());
795 }
796
797 let rights = AuthorizationItemSetBuilder::new()
798 .add_right("system.privilege.admin")?
799 .build();
800
801 let auth = Authorization::new(
802 Some(rights),
803 None,
804 Flags::DEFAULTS
805 | Flags::INTERACTION_ALLOWED
806 | Flags::PREAUTHORIZE
807 | Flags::EXTEND_RIGHTS,
808 )?;
809
810 let file = auth.execute_with_privileges_piped("/bin/ls", ["/"], Flags::DEFAULTS)?;
811
812 use std::io::{self, BufRead};
813 for line in io::BufReader::new(file).lines() {
814 let _ = line.unwrap();
815 }
816
817 Ok(())
818 }
819}