git_sec/
identity.rs

1use std::path::Path;
2
3#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
4#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
5/// An account based identity
6pub struct Account {
7    /// The user's name
8    pub username: String,
9    /// The user's password
10    pub password: String,
11}
12
13/// Returns true if the given `path` is owned by the user who is executing the current process.
14///
15/// Note that this method is very specific to avoid having to deal with any operating system types.
16pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> {
17    impl_::is_path_owned_by_current_user(path)
18}
19
20#[cfg(not(windows))]
21mod impl_ {
22    use std::path::Path;
23
24    pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> {
25        fn owner_from_path(path: impl AsRef<Path>) -> std::io::Result<u32> {
26            use std::os::unix::fs::MetadataExt;
27            let meta = std::fs::symlink_metadata(path)?;
28            Ok(meta.uid())
29        }
30
31        fn owner_of_current_process() -> std::io::Result<u32> {
32            // SAFETY: there is no documented possibility for failure
33            #[allow(unsafe_code)]
34            let uid = unsafe { libc::geteuid() };
35            Ok(uid)
36        }
37        use std::str::FromStr;
38
39        let owner_of_path = owner_from_path(path)?;
40        let owner_of_process = owner_of_current_process()?;
41        if owner_of_path == owner_of_process {
42            Ok(true)
43        } else if let Some(sudo_uid) =
44            std::env::var_os("SUDO_UID").and_then(|val| val.to_str().and_then(|val_str| u32::from_str(val_str).ok()))
45        {
46            Ok(owner_of_path == sudo_uid)
47        } else {
48            Ok(false)
49        }
50    }
51}
52
53#[cfg(windows)]
54mod impl_ {
55    use std::path::Path;
56
57    fn err(msg: impl Into<String>) -> std::io::Error {
58        std::io::Error::new(std::io::ErrorKind::Other, msg.into())
59    }
60
61    pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> {
62        use windows::{
63            core::{Error, PCWSTR},
64            Win32::{
65                Foundation::{CloseHandle, BOOL, HANDLE, PSID},
66                Security::{
67                    Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT},
68                    CheckTokenMembership, EqualSid, GetTokenInformation, IsWellKnownSid, TokenOwner,
69                    WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_OWNER,
70                    TOKEN_QUERY,
71                },
72                System::{
73                    Memory::LocalFree,
74                    Threading::{GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken},
75                },
76            },
77        };
78
79        let mut err_msg = None;
80        let mut is_owned = false;
81        let path = path.as_ref();
82
83        if !path.exists() {
84            return Err(std::io::Error::new(
85                std::io::ErrorKind::NotFound,
86                format!("{:?} does not exist.", path),
87            ));
88        }
89
90        // Home is not actually owned by the corresponding user
91        // but it can be considered de-facto owned by the user
92        // Ignore errors here and just do the regular checks below
93        if git_path::realpath(path).ok() == dirs::home_dir() {
94            return Ok(true);
95        }
96
97        #[allow(unsafe_code)]
98        unsafe {
99            let mut folder_owner = PSID::default();
100            let mut pdescriptor = PSECURITY_DESCRIPTOR::default();
101            let result = GetNamedSecurityInfoW(
102                PCWSTR(to_wide_path(path).as_ptr()),
103                SE_FILE_OBJECT,
104                OWNER_SECURITY_INFORMATION,
105                Some(&mut folder_owner),
106                None,
107                None,
108                None,
109                &mut pdescriptor,
110            );
111
112            // Workaround for https://github.com/microsoft/win32metadata/issues/884
113            if result.is_ok() {
114                let mut token = HANDLE::default();
115                // Use the current thread token if possible, otherwise open the process token
116                OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, true, &mut token)
117                    .ok()
118                    .or_else(|_| OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).ok())?;
119
120                let mut buffer_size = 0;
121                let mut buffer = Vec::<u8>::new();
122                GetTokenInformation(token, TokenOwner, None, 0, &mut buffer_size);
123                if buffer_size != 0 {
124                    buffer.resize(buffer_size as usize, 0);
125                    if GetTokenInformation(
126                        token,
127                        TokenOwner,
128                        Some(buffer.as_mut_ptr() as *mut std::ffi::c_void),
129                        buffer_size,
130                        &mut buffer_size,
131                    )
132                    .as_bool()
133                    {
134                        let token_owner = buffer.as_ptr() as *const TOKEN_OWNER;
135                        let token_owner = (*token_owner).Owner;
136
137                        is_owned = EqualSid(folder_owner, token_owner).as_bool();
138
139                        // Admin-group owned folders are considered owned by the current user, if they are in the admin group
140                        if !is_owned && IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid).as_bool() {
141                            let mut is_member = BOOL::default();
142                            // TODO: re-use the handle
143                            match CheckTokenMembership(HANDLE::default(), token_owner, &mut is_member).ok() {
144                                Err(e) => err_msg = Some(format!("Couldn't check if user is an administrator: {}", e)),
145                                Ok(()) => is_owned = is_member.as_bool(),
146                            }
147                        }
148                    } else {
149                        err_msg = format!(
150                            "Couldn't get actual token information for current process with err: {}",
151                            Error::from_win32()
152                        )
153                        .into();
154                    }
155                } else {
156                    err_msg = format!(
157                        "Couldn't get token information size info for current process with err: {}",
158                        Error::from_win32()
159                    )
160                    .into();
161                }
162                CloseHandle(token);
163            } else {
164                err_msg = format!(
165                    "Couldn't get security information for path '{}' with err {}",
166                    path.display(),
167                    Error::from_win32()
168                )
169                .into();
170            }
171            LocalFree(pdescriptor.0 as isize);
172        }
173
174        err_msg.map(|msg| Err(err(msg))).unwrap_or(Ok(is_owned))
175    }
176
177    fn to_wide_path(path: impl AsRef<Path>) -> Vec<u16> {
178        use std::os::windows::ffi::OsStrExt;
179        let mut wide_path: Vec<_> = path.as_ref().as_os_str().encode_wide().collect();
180        wide_path.push(0);
181        wide_path
182    }
183}