mod option_map;
mod command_info;
mod settings;
mod user_info;
mod traits;
use super::errors::*;
use super::version::Version;
pub use self::option_map::OptionMap;
use self::command_info::CommandInfo;
use self::settings::Settings;
use self::user_info::UserInfo;
use std::collections::HashSet;
use std::path::PathBuf;
use std::ffi::{CString, CStr};
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::path::Path;
use std::slice;
use std::sync::{Arc, Mutex};
use libc::{c_char, c_int, c_uint, gid_t};
#[allow(missing_debug_implementations)]
pub struct Plugin {
pub version: Version,
pub command: Vec<CString>,
pub settings: Settings,
pub user_info: UserInfo,
pub command_info: CommandInfo,
pub user_env: OptionMap,
pub plugin_options: OptionMap,
printf: Arc<Mutex<sudo_plugin_sys::sudo_printf_non_null_t>>,
_conversation: sudo_plugin_sys::sudo_conv_t,
}
impl Plugin {
#[cfg_attr(feature="cargo-clippy", allow(clippy::new_ret_no_self))]
#[cfg_attr(feature="cargo-clippy", allow(clippy::cast_sign_loss))]
#[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))]
#[cfg_attr(feature="cargo-clippy", allow(clippy::missing_safety_doc))]
pub unsafe fn new(
version: c_uint,
argc: c_int,
argv: *const *mut c_char,
conversation: sudo_plugin_sys::sudo_conv_t,
plugin_printf: sudo_plugin_sys::sudo_printf_t,
settings: *const *mut c_char,
user_info: *const *mut c_char,
command_info: *const *mut c_char,
user_env: *const *mut c_char,
plugin_options: *const *mut c_char,
) -> Result<Self> {
let version = Version::from(version).check()?;
let printf = plugin_printf.ok_or(ErrorKind::Uninitialized)?;
let _ = conversation .ok_or(ErrorKind::Uninitialized)?;
let mut argv = slice::from_raw_parts(argv, argc as usize).to_vec();
let command = argv
.drain(..)
.map(|ptr| CStr::from_ptr(ptr).to_owned())
.collect();
let plugin = Self {
version,
command,
settings: Settings ::try_from(OptionMap::from_raw(settings as _))?,
user_info: UserInfo ::try_from(OptionMap::from_raw(user_info as _))?,
command_info: CommandInfo::try_from(OptionMap::from_raw(command_info as _))?,
user_env: OptionMap ::from_raw(user_env as _),
plugin_options: OptionMap ::from_raw(plugin_options as _),
printf: Arc::new(Mutex::new(printf)),
_conversation: conversation,
};
Ok(plugin)
}
pub fn stdout(&self) -> Printf {
Printf {
facility: self.printf.clone(),
level: sudo_plugin_sys::SUDO_CONV_INFO_MSG
}
}
pub fn stderr(&self) -> Printf {
Printf {
facility: self.printf.clone(),
level: sudo_plugin_sys::SUDO_CONV_ERROR_MSG
}
}
pub fn tty(&self) -> Option<Tty> {
self.user_info.tty.as_ref().and_then(|path|
Tty::try_from(path).ok()
)
}
pub fn invocation(&self) -> Vec<u8> {
let mut sudo = self.settings.progname.as_bytes().to_vec();
let flags = self.settings.flags();
if !flags.is_empty() {
sudo.push(b' ');
sudo.extend_from_slice(&flags.join(&b' ')[..]);
}
for entry in &self.command {
sudo.push(b' ');
sudo.extend_from_slice(entry.as_bytes());
}
sudo
}
pub fn cwd(&self) -> &PathBuf {
self.command_info.cwd.as_ref().unwrap_or(
&self.user_info.cwd
)
}
pub fn runas_gids(&self) -> HashSet<gid_t> {
if self.command_info.preserve_groups {
debug_assert!(self.command_info.runas_groups.is_none())
} else {
debug_assert!(self.command_info.runas_groups.is_some())
}
let mut set : HashSet<_> = self.command_info.runas_groups.as_ref().unwrap_or(
&self.user_info.groups
).iter().cloned().collect();
let _ = set.insert(self.command_info.runas_egid);
set
}
}
#[derive(Debug)]
pub struct Tty(File);
impl Tty {
fn try_from(path: &Path) -> io::Result<Self> {
OpenOptions::new().write(true).open(path).map(Tty)
}
}
impl Write for Tty {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
#[derive(Debug)]
pub struct Printf {
pub facility: Arc<Mutex<sudo_plugin_sys::sudo_printf_non_null_t>>,
pub level: u32,
}
impl Printf {
pub fn write_error(&mut self, tag: &[u8], error: &Error) -> io::Result<()> {
let mut message = vec![b'\n'];
let mut stack = vec![];
for e in error.iter() {
stack.push(e);
}
for e in stack.iter().rev() {
message.extend_from_slice(tag);
message.extend_from_slice(format!(": {}", e).as_bytes());
message.push(b'\n');
}
self.write_all(&message[..]).and_then(|_| self.flush() )
}
}
impl Write for Printf {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let message = CString::new(buf).map_err(|err|
io::Error::new(io::ErrorKind::InvalidData, err)
)?;
let ret = unsafe {
#[cfg_attr(feature="cargo-clippy", allow(clippy::cast_possible_wrap))]
(self.facility.lock().unwrap())(self.level as i32, message.as_ptr())
};
if ret == -1 {
return Err(io::Error::last_os_error());
}
#[cfg_attr(feature="cargo-clippy", allow(clippy::cast_sign_loss))]
Ok(ret as _)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}