From 57c39437bc50992dfe11b104229db854cb912a18 Mon Sep 17 00:00:00 2001 From: Alexey Chernyshov Date: Tue, 14 Jul 2020 00:55:42 +0300 Subject: feat(directory): Show lock symbol if current directory is read only (#1298) Add feature to display icon if current directory is read-only. --- src/modules/utils/directory_nix.rs | 61 +++++++++++++++++++ src/modules/utils/directory_win.rs | 117 +++++++++++++++++++++++++++++++++++++ src/modules/utils/mod.rs | 6 ++ 3 files changed, 184 insertions(+) create mode 100644 src/modules/utils/directory_nix.rs create mode 100644 src/modules/utils/directory_win.rs (limited to 'src/modules/utils') diff --git a/src/modules/utils/directory_nix.rs b/src/modules/utils/directory_nix.rs new file mode 100644 index 000000000..87f94b7eb --- /dev/null +++ b/src/modules/utils/directory_nix.rs @@ -0,0 +1,61 @@ +use nix::sys::stat::Mode; +use nix::unistd::{Gid, Uid}; +use std::fs; +use std::os::unix::fs::MetadataExt; +use std::os::unix::fs::PermissionsExt; + +/// Checks if the current user can write to the `folder_path`. +/// +/// It extracts Unix access rights from the directory and checks whether +/// 1) the current user is the owner of the directory and whether it has the write access +/// 2) the current user's primary group is the directory group owner whether if it has write access +/// 2a) (not implemented on macOS) one of the supplementary groups of the current user is the +/// directory group owner and whether it has write access +/// 3) 'others' part of the access mask has the write access +pub fn is_write_allowed(folder_path: &str) -> Result { + let meta = fs::metadata(folder_path).map_err(|_| "Unable to stat() directory")?; + let perms = meta.permissions().mode(); + + let euid = Uid::effective(); + if euid.is_root() { + return Ok(true); + } + if meta.uid() == euid.as_raw() { + Ok(perms & Mode::S_IWUSR.bits() as u32 != 0) + } else if (meta.gid() == Gid::effective().as_raw()) + || (get_supplementary_groups().contains(&meta.gid())) + { + Ok(perms & Mode::S_IWGRP.bits() as u32 != 0) + } else { + Ok(perms & Mode::S_IWOTH.bits() as u32 != 0) + } +} + +#[cfg(all(unix, not(target_os = "macos")))] +fn get_supplementary_groups() -> Vec { + match nix::unistd::getgroups() { + Err(_) => Vec::new(), + Ok(v) => v.into_iter().map(|i| i.as_raw()).collect(), + } +} + +#[cfg(all(unix, target_os = "macos"))] +fn get_supplementary_groups() -> Vec { + // at the moment nix crate does not provide it for macOS + Vec::new() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[ignore] + fn read_only_test() { + assert_eq!(is_write_allowed("/etc"), Ok(false)); + assert_eq!( + is_write_allowed("/i_dont_exist"), + Err("Unable to stat() directory") + ); + } +} diff --git a/src/modules/utils/directory_win.rs b/src/modules/utils/directory_win.rs new file mode 100644 index 000000000..6574be555 --- /dev/null +++ b/src/modules/utils/directory_win.rs @@ -0,0 +1,117 @@ +extern crate winapi; + +use std::ffi::OsStr; +use std::iter; +use std::mem; +use std::os::windows::ffi::OsStrExt; +use winapi::ctypes::c_void; +use winapi::shared::minwindef::{BOOL, DWORD}; +use winapi::um::handleapi; +use winapi::um::processthreadsapi; +use winapi::um::securitybaseapi; +use winapi::um::winnt::{ + SecurityImpersonation, DACL_SECURITY_INFORMATION, FILE_ALL_ACCESS, FILE_GENERIC_EXECUTE, + FILE_GENERIC_READ, FILE_GENERIC_WRITE, GENERIC_MAPPING, GROUP_SECURITY_INFORMATION, HANDLE, + OWNER_SECURITY_INFORMATION, PRIVILEGE_SET, PSECURITY_DESCRIPTOR, STANDARD_RIGHTS_READ, + TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY, +}; + +/// Checks if the current user has write access right to the `folder_path` +/// +/// First, the function extracts DACL from the given directory and then calls `AccessCheck` against +/// the current process access token and directory's security descriptor. +pub fn is_write_allowed(folder_path: &str) -> std::result::Result { + let folder_name: Vec = OsStr::new(folder_path) + .encode_wide() + .chain(iter::once(0)) + .collect(); + let mut length: DWORD = 0; + + let rc = unsafe { + securitybaseapi::GetFileSecurityW( + folder_name.as_ptr(), + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + std::ptr::null_mut(), + 0, + &mut length, + ) + }; + if rc != 0 { + return Err( + "GetFileSecurityW returned non-zero when asked for the security descriptor size", + ); + } + + let mut buf: Vec = Vec::with_capacity(length as usize); + + let rc = unsafe { + securitybaseapi::GetFileSecurityW( + folder_name.as_ptr(), + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + buf.as_mut_ptr() as *mut c_void, + length, + &mut length, + ) + }; + + if rc != 1 { + return Err("GetFileSecurityW failed to retrieve the security descriptor"); + } + + let mut token: HANDLE = 0 as HANDLE; + let rc = unsafe { + processthreadsapi::OpenProcessToken( + processthreadsapi::GetCurrentProcess(), + TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, + &mut token, + ) + }; + if rc != 1 { + return Err("OpenProcessToken failed to retrieve current process' security token"); + } + + let mut impersonated_token: HANDLE = 0 as HANDLE; + let rc = unsafe { + securitybaseapi::DuplicateToken(token, SecurityImpersonation, &mut impersonated_token) + }; + if rc != 1 { + unsafe { handleapi::CloseHandle(token) }; + return Err("DuplicateToken failed"); + } + + let mut mapping: GENERIC_MAPPING = GENERIC_MAPPING { + GenericRead: FILE_GENERIC_READ, + GenericWrite: FILE_GENERIC_WRITE, + GenericExecute: FILE_GENERIC_EXECUTE, + GenericAll: FILE_ALL_ACCESS, + }; + + let mut priviledges: PRIVILEGE_SET = PRIVILEGE_SET::default(); + let mut priv_size = mem::size_of::() as DWORD; + let mut granted_access: DWORD = 0; + let mut access_rights: DWORD = FILE_GENERIC_WRITE; + let mut result: BOOL = 0 as BOOL; + unsafe { securitybaseapi::MapGenericMask(&mut access_rights, &mut mapping) }; + let rc = unsafe { + securitybaseapi::AccessCheck( + buf.as_mut_ptr() as PSECURITY_DESCRIPTOR, + impersonated_token, + access_rights, + &mut mapping, + &mut priviledges, + &mut priv_size, + &mut granted_access, + &mut result, + ) + }; + unsafe { + handleapi::CloseHandle(impersonated_token); + handleapi::CloseHandle(token); + } + + if rc != 1 { + return Err("AccessCheck failed"); + } + + Ok(result != 0) +} diff --git a/src/modules/utils/mod.rs b/src/modules/utils/mod.rs index 345ce7f40..d164de4d7 100644 --- a/src/modules/utils/mod.rs +++ b/src/modules/utils/mod.rs @@ -1,5 +1,11 @@ pub mod directory; pub mod java_version_parser; +#[cfg(target_os = "windows")] +pub mod directory_win; + +#[cfg(not(target_os = "windows"))] +pub mod directory_nix; + #[cfg(test)] pub mod test; -- cgit v1.2.3