1use std::{
4 convert::{TryFrom, TryInto},
5 ffi::{CStr, CString},
6 fs::File,
7 marker::PhantomData,
8 mem::MaybeUninit,
9 os::raw::c_void,
10 ptr::addr_of,
11};
12
13#[cfg(all(target_os = "macos", feature = "job-bless"))]
14use core_foundation::{
15 base::Boolean,
16 error::{CFError, CFErrorRef},
17};
18use core_foundation::{
19 base::{CFTypeRef, TCFType},
20 bundle::CFBundleRef,
21 dictionary::{CFDictionary, CFDictionaryRef},
22 string::{CFString, CFStringRef},
23};
24use security_framework_sys::{authorization as sys, base::errSecConversionError};
25use sys::AuthorizationExternalForm;
26
27use crate::base::Error;
34
35use crate::base::Result;
42
43macro_rules! optional_str_to_cfref {
44 ($string:ident) => {{
45 $string
46 .map(CFString::new)
47 .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef())
48 }};
49}
50
51macro_rules! cstring_or_err {
52 ($x:expr) => {{
53 CString::new($x).map_err(|_| Error::from_code(errSecConversionError))
54 }};
55}
56
57bitflags::bitflags! {
58 #[derive(Debug, Clone)]
60 pub struct Flags: sys::AuthorizationFlags {
61 const DEFAULTS = sys::kAuthorizationFlagDefaults;
64
65 const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed;
67
68 const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights;
71
72 const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights;
75
76 const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights;
78
79 const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize;
82 }
83}
84
85impl Default for Flags {
86 #[inline(always)]
87 fn default() -> Flags {
88 Flags::DEFAULTS
89 }
90}
91
92#[repr(C)]
94pub struct AuthorizationItem(sys::AuthorizationItem);
95
96impl AuthorizationItem {
97 #[must_use]
102 pub fn name(&self) -> &str {
103 unsafe {
104 CStr::from_ptr(self.0.name)
105 .to_str()
106 .expect("AuthorizationItem::name failed to convert &str to CStr")
107 }
108 }
109
110 #[inline]
113 #[must_use]
114 pub fn value(&self) -> Option<&[u8]> {
115 if self.0.value.is_null() {
116 return None;
117 }
118
119 let value =
120 unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) };
121
122 Some(value)
123 }
124}
125
126#[derive(Debug)]
128#[repr(C)]
129pub struct AuthorizationItemSet<'a> {
130 inner: *const sys::AuthorizationItemSet,
131 phantom: PhantomData<&'a sys::AuthorizationItemSet>,
132}
133
134impl<'a> Drop for AuthorizationItemSet<'a> {
135 #[inline]
136 fn drop(&mut self) {
137 unsafe {
138 sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet);
139 }
140 }
141}
142
143#[derive(Debug)]
146pub struct AuthorizationItemSetStorage {
147 names: Vec<CString>,
151 values: Vec<Option<Vec<u8>>>,
152 items: Vec<sys::AuthorizationItem>,
153
154 pub set: sys::AuthorizationItemSet,
159}
160
161impl Default for AuthorizationItemSetStorage {
162 #[inline]
163 fn default() -> Self {
164 AuthorizationItemSetStorage {
165 names: Vec::new(),
166 values: Vec::new(),
167 items: Vec::new(),
168 set: sys::AuthorizationItemSet {
169 count: 0,
170 items: std::ptr::null_mut(),
171 },
172 }
173 }
174}
175
176#[derive(Debug, Default)]
179pub struct AuthorizationItemSetBuilder {
180 storage: AuthorizationItemSetStorage,
181}
182
183impl AuthorizationItemSetBuilder {
185 #[inline(always)]
188 #[must_use]
189 pub fn new() -> AuthorizationItemSetBuilder {
190 Default::default()
191 }
192
193 pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> {
199 self.storage.names.push(cstring_or_err!(name)?);
200 self.storage.values.push(None);
201 Ok(self)
202 }
203
204 pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self>
209 where
210 N: Into<Vec<u8>>,
211 V: Into<Vec<u8>>,
212 {
213 self.storage.names.push(cstring_or_err!(name)?);
214 self.storage.values.push(Some(value.into()));
215 Ok(self)
216 }
217
218 pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self>
223 where
224 N: Into<Vec<u8>>,
225 V: Into<Vec<u8>>,
226 {
227 self.storage.names.push(cstring_or_err!(name)?);
228 self.storage
229 .values
230 .push(Some(cstring_or_err!(value)?.to_bytes().to_vec()));
231 Ok(self)
232 }
233
234 #[must_use]
237 pub fn build(mut self) -> AuthorizationItemSetStorage {
238 self.storage.items = self
239 .storage
240 .names
241 .iter()
242 .zip(self.storage.values.iter())
243 .map(|(n, v)| sys::AuthorizationItem {
244 name: n.as_ptr(),
245 value: v
246 .as_ref()
247 .map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void),
248 valueLength: v.as_ref().map_or(0, |v| v.len()),
249 flags: 0,
250 })
251 .collect();
252
253 self.storage.set = sys::AuthorizationItemSet {
254 count: self.storage.items.len() as u32,
255 items: self.storage.items.as_ptr() as *mut sys::AuthorizationItem,
256 };
257
258 self.storage
259 }
260}
261
262#[derive(Copy, Clone)]
264pub enum RightDefinition<'a> {
265 FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>),
267
268 FromExistingRight(&'a str),
270}
271
272#[derive(Debug)]
275pub struct Authorization {
276 handle: sys::AuthorizationRef,
277 free_flags: Flags,
278}
279
280impl TryFrom<AuthorizationExternalForm> for Authorization {
281 type Error = Error;
282
283 #[cold]
285 fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> {
286 let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
287
288 let status = unsafe {
289 sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr())
290 };
291
292 if status != sys::errAuthorizationSuccess {
293 return Err(Error::from_code(status));
294 }
295
296 let auth = Authorization {
297 handle: unsafe { handle.assume_init() },
298 free_flags: Default::default(),
299 };
300
301 Ok(auth)
302 }
303}
304
305impl Authorization {
306 #[allow(clippy::should_implement_trait)]
309 #[inline]
310 pub fn default() -> Result<Self> {
311 Self::new(None, None, Default::default())
312 }
313
314 pub fn new(
325 rights: Option<AuthorizationItemSetStorage>,
327 environment: Option<AuthorizationItemSetStorage>,
328 flags: Flags,
329 ) -> Result<Self> {
330 let rights_ptr = rights
331 .as_ref()
332 .map_or(std::ptr::null(), |r| addr_of!(r.set) as *const _);
333
334 let env_ptr = environment
335 .as_ref()
336 .map_or(std::ptr::null(), |e| addr_of!(e.set) as *const _);
337
338 let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
339
340 let status = unsafe {
341 sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr())
342 };
343
344 if status != sys::errAuthorizationSuccess {
345 return Err(Error::from_code(status));
346 }
347
348 Ok(Authorization {
349 handle: unsafe { handle.assume_init() },
350 free_flags: Default::default(),
351 })
352 }
353
354 #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")]
356 pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> {
357 external_form.try_into()
358 }
359
360 #[inline(always)]
364 pub fn destroy_rights(mut self) {
365 self.free_flags = Flags::DESTROY_RIGHTS;
366 }
367
368 pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> {
376 let name = cstring_or_err!(name)?;
377 let mut dict = MaybeUninit::<CFDictionaryRef>::uninit();
378
379 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) };
380
381 if status != sys::errAuthorizationSuccess {
382 return Err(Error::from_code(status));
383 }
384
385 let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) };
386
387 Ok(dict)
388 }
389
390 pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> {
396 let name = cstring_or_err!(name)?;
397
398 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) };
399
400 Ok(status == sys::errAuthorizationSuccess)
401 }
402
403 pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> {
410 let name = cstring_or_err!(name)?;
411
412 let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) };
413
414 if status != sys::errAuthorizationSuccess {
415 return Err(Error::from_code(status));
416 }
417
418 Ok(())
419 }
420
421 pub fn set_right<T: Into<Vec<u8>>>(
441 &self,
442 name: T,
443 definition: RightDefinition<'_>,
444 description: Option<&str>,
445 bundle: Option<CFBundleRef>,
446 locale: Option<&str>,
447 ) -> Result<()> {
448 let name = cstring_or_err!(name)?;
449
450 let definition_cfstring: CFString;
451 let definition_ref = match definition {
452 RightDefinition::FromDictionary(def) => def.as_CFTypeRef(),
453 RightDefinition::FromExistingRight(def) => {
454 definition_cfstring = CFString::new(def);
455 definition_cfstring.as_CFTypeRef()
456 }
457 };
458
459 let status = unsafe {
460 sys::AuthorizationRightSet(
461 self.handle,
462 name.as_ptr(),
463 definition_ref,
464 optional_str_to_cfref!(description),
465 bundle.unwrap_or(std::ptr::null_mut()),
466 optional_str_to_cfref!(locale),
467 )
468 };
469
470 if status != sys::errAuthorizationSuccess {
471 return Err(Error::from_code(status));
472 }
473
474 Ok(())
475 }
476
477 pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> {
487 let tag_with_nul: CString;
488
489 let tag_ptr = match tag {
490 Some(tag) => {
491 tag_with_nul = cstring_or_err!(tag)?;
492 tag_with_nul.as_ptr()
493 }
494 None => std::ptr::null(),
495 };
496
497 let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit();
498
499 let status =
500 unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) };
501
502 if status != sys::errAuthorizationSuccess {
503 return Err(Error::from(status));
504 }
505
506 let set = AuthorizationItemSet {
507 inner: unsafe { inner.assume_init() },
508 phantom: PhantomData,
509 };
510
511 Ok(set)
512 }
513
514 pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> {
517 let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit();
518
519 let status =
520 unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) };
521
522 if status != sys::errAuthorizationSuccess {
523 return Err(Error::from(status));
524 }
525
526 Ok(unsafe { external_form.assume_init() })
527 }
528
529 #[cfg(target_os = "macos")]
532 #[inline(always)]
533 pub fn execute_with_privileges<P, S, I>(
534 &self,
535 command: P,
536 arguments: I,
537 flags: Flags,
538 ) -> Result<()>
539 where
540 P: AsRef<std::path::Path>,
541 I: IntoIterator<Item = S>,
542 S: AsRef<std::ffi::OsStr>,
543 {
544 use std::os::unix::ffi::OsStrExt;
545
546 let arguments = arguments
547 .into_iter()
548 .flat_map(|a| CString::new(a.as_ref().as_bytes()))
549 .collect::<Vec<_>>();
550 self.execute_with_privileges_internal(
551 command.as_ref().as_os_str().as_bytes(),
552 &arguments,
553 flags,
554 false,
555 )?;
556 Ok(())
557 }
558
559 #[cfg(target_os = "macos")]
562 #[inline(always)]
563 pub fn execute_with_privileges_piped<P, S, I>(
564 &self,
565 command: P,
566 arguments: I,
567 flags: Flags,
568 ) -> Result<File>
569 where
570 P: AsRef<std::path::Path>,
571 I: IntoIterator<Item = S>,
572 S: AsRef<std::ffi::OsStr>,
573 {
574 use std::os::unix::ffi::OsStrExt;
575
576 let arguments = arguments
577 .into_iter()
578 .flat_map(|a| CString::new(a.as_ref().as_bytes()))
579 .collect::<Vec<_>>();
580 Ok(self
581 .execute_with_privileges_internal(
582 command.as_ref().as_os_str().as_bytes(),
583 &arguments,
584 flags,
585 true,
586 )?
587 .unwrap())
588 }
589
590 #[cfg(all(target_os = "macos", feature = "job-bless"))]
592 pub fn job_bless(&self, label: &str) -> Result<(), CFError> {
593 #[link(name = "ServiceManagement", kind = "framework")]
594 extern "C" {
595 static kSMDomainSystemLaunchd: CFStringRef;
596
597 fn SMJobBless(
598 domain: CFStringRef,
599 executableLabel: CFStringRef,
600 auth: sys::AuthorizationRef,
601 error: *mut CFErrorRef,
602 ) -> Boolean;
603 }
604
605 unsafe {
606 let mut error = std::ptr::null_mut();
607 SMJobBless(
608 kSMDomainSystemLaunchd,
609 CFString::new(label).as_concrete_TypeRef(),
610 self.handle,
611 &mut error,
612 );
613 if !error.is_null() {
614 return Err(CFError::wrap_under_create_rule(error));
615 }
616
617 Ok(())
618 }
619 }
620
621 #[cfg(target_os = "macos")]
623 fn execute_with_privileges_internal(
624 &self,
625 command: &[u8],
626 arguments: &[CString],
627 flags: Flags,
628 make_pipe: bool,
629 ) -> Result<Option<File>> {
630 use std::os::unix::io::{FromRawFd, RawFd};
631
632 let c_cmd = cstring_or_err!(command)?;
633
634 let mut c_args = arguments
635 .iter()
636 .map(|a| a.as_ptr() as _)
637 .collect::<Vec<_>>();
638 c_args.push(std::ptr::null_mut());
639
640 let mut pipe: *mut libc::FILE = std::ptr::null_mut();
641
642 let status = unsafe {
643 sys::AuthorizationExecuteWithPrivileges(
644 self.handle,
645 c_cmd.as_ptr(),
646 flags.bits(),
647 c_args.as_ptr(),
648 if make_pipe {
649 &mut pipe
650 } else {
651 std::ptr::null_mut()
652 },
653 )
654 };
655
656 crate::cvt(status)?;
657 Ok(if make_pipe {
658 if pipe.is_null() {
659 return Err(Error::from_code(32)); }
661 Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) })
662 } else {
663 None
664 })
665 }
666}
667
668impl Drop for Authorization {
669 #[inline]
670 fn drop(&mut self) {
671 unsafe {
672 sys::AuthorizationFree(self.handle, self.free_flags.bits());
673 }
674 }
675}
676
677#[cfg(test)]
678mod tests {
679 use core_foundation::string::CFString;
680
681 use super::*;
682
683 #[test]
684 fn test_create_default_authorization() {
685 Authorization::default().unwrap();
686 }
687
688 #[test]
689 fn test_create_allowed_authorization() -> Result<()> {
690 let rights = AuthorizationItemSetBuilder::new()
691 .add_right("system.hdd.smart")?
692 .add_right("system.login.done")?
693 .build();
694
695 Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
696
697 Ok(())
698 }
699
700 #[test]
701 fn test_create_then_destroy_allowed_authorization() -> Result<()> {
702 let rights = AuthorizationItemSetBuilder::new()
703 .add_right("system.hdd.smart")?
704 .add_right("system.login.done")?
705 .build();
706
707 let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
708 auth.destroy_rights();
709
710 Ok(())
711 }
712
713 #[test]
714 fn test_create_authorization_requiring_interaction() -> Result<()> {
715 let rights = AuthorizationItemSetBuilder::new()
716 .add_right("system.privilege.admin")?
717 .build();
718
719 let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err();
720
721 assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed);
722
723 Ok(())
724 }
725
726 fn create_credentials_env() -> Result<AuthorizationItemSetStorage> {
727 #![allow(clippy::option_env_unwrap)]
728
729 let set = AuthorizationItemSetBuilder::new()
730 .add_string(
731 "username",
732 option_env!("USER").expect("You must set the USER environment variable"),
733 )?
734 .add_string(
735 "password",
736 option_env!("PASSWORD").expect("You must set the PASSWORD environment variable"),
737 )?
738 .build();
739
740 Ok(set)
741 }
742
743 #[test]
744 fn test_create_authorization_with_bad_credentials() -> Result<()> {
745 let rights = AuthorizationItemSetBuilder::new()
746 .add_right("system.privilege.admin")?
747 .build();
748
749 let env = AuthorizationItemSetBuilder::new()
750 .add_string("username", "Tim Apple")?
751 .add_string("password", "butterfly")?
752 .build();
753
754 let error =
755 Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err();
756
757 assert_eq!(error.code(), sys::errAuthorizationDenied);
758
759 Ok(())
760 }
761
762 #[test]
763 fn test_create_authorization_with_credentials() -> Result<()> {
764 if option_env!("PASSWORD").is_none() {
765 return Ok(());
766 }
767
768 let rights = AuthorizationItemSetBuilder::new()
769 .add_right("system.privilege.admin")?
770 .build();
771
772 let env = create_credentials_env()?;
773
774 Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
775
776 Ok(())
777 }
778
779 #[test]
780 fn test_query_authorization_database() -> Result<()> {
781 assert!(Authorization::right_exists("system.hdd.smart")?);
782 assert!(!Authorization::right_exists("EMPTY")?);
783
784 let dict = Authorization::get_right("system.hdd.smart").unwrap();
785
786 let key = CFString::from_static_string("class");
787 assert!(dict.contains_key(&key));
788
789 let invalid_key = CFString::from_static_string("EMPTY");
790 assert!(!dict.contains_key(&invalid_key));
791
792 Ok(())
793 }
794
795 #[test]
797 fn test_modify_authorization_database() -> Result<()> {
798 if option_env!("PASSWORD").is_none() {
799 return Ok(());
800 }
801
802 let rights = AuthorizationItemSetBuilder::new()
803 .add_right("config.modify.")?
804 .build();
805
806 let env = create_credentials_env()?;
807
808 let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
809
810 assert!(!Authorization::right_exists("TEST_RIGHT")?);
811
812 auth.set_right(
813 "TEST_RIGHT",
814 RightDefinition::FromExistingRight("system.hdd.smart"),
815 None,
816 None,
817 None,
818 )
819 .unwrap();
820
821 assert!(Authorization::right_exists("TEST_RIGHT")?);
822
823 auth.remove_right("TEST_RIGHT").unwrap();
824
825 assert!(!Authorization::right_exists("TEST_RIGHT")?);
826
827 Ok(())
828 }
829
830 #[test]
832 fn test_execute_with_privileges() -> Result<()> {
833 if option_env!("PASSWORD").is_none() {
834 return Ok(());
835 }
836
837 let rights = AuthorizationItemSetBuilder::new()
838 .add_right("system.privilege.admin")?
839 .build();
840
841 let auth = Authorization::new(
842 Some(rights),
843 None,
844 Flags::DEFAULTS
845 | Flags::INTERACTION_ALLOWED
846 | Flags::PREAUTHORIZE
847 | Flags::EXTEND_RIGHTS,
848 )?;
849
850 let file = auth.execute_with_privileges_piped("/bin/ls", ["/"], Flags::DEFAULTS)?;
851
852 use std::io::{
853 BufRead, {self},
854 };
855 for line in io::BufReader::new(file).lines() {
856 let _ = line.unwrap();
857 }
858
859 Ok(())
860 }
861}