use libc;
use std::collections::HashMap;
use std::convert;
use std::ffi::{CStr, CString};
use std::iter::FromIterator;
use std::mem;
use std::net;
use std::slice;
use sys::JailFlags;
use JailError;
use byteorder::{ByteOrder, LittleEndian, NetworkEndian, WriteBytesExt};
#[cfg(feature = "serialize")]
use serde::Serialize;
use sysctl::{Ctl, CtlFlags, CtlType, CtlValue, Sysctl};
use nix;
#[cfg(target_os = "freebsd")]
impl Type {
pub fn of_param(name: &str) -> Result<Type, JailError> {
trace!("Type::of_param(name={:?})", name);
let (ctl_type, _, _) = info(name)?;
ctltype_to_type(name, ctl_type)
}
pub fn is_string(&self) -> bool {
trace!("Type::is_string({:?})", self);
match self {
Type::String => true,
_ => false,
}
}
pub fn is_numeric(&self) -> bool {
trace!("Type::is_numeric({:?})", self);
match self {
Type::U8 => true,
Type::U16 => true,
Type::U32 => true,
Type::U64 => true,
Type::S8 => true,
Type::S16 => true,
Type::S32 => true,
Type::S64 => true,
Type::Int => true,
Type::Long => true,
Type::Uint => true,
Type::Ulong => true,
_ => false,
}
}
pub fn is_signed(&self) -> bool {
trace!("Type::is_signed({:?})", self);
match self {
Type::S8 => true,
Type::S16 => true,
Type::S32 => true,
Type::S64 => true,
Type::Int => true,
Type::Long => true,
_ => false,
}
}
pub fn is_ip(&self) -> bool {
trace!("Type::is_ip({:?})", self);
match self {
Type::Ipv4Addrs => true,
Type::Ipv6Addrs => true,
_ => false,
}
}
pub fn is_ipv4(&self) -> bool {
trace!("Type::is_ipv4({:?})", self);
match self {
Type::Ipv4Addrs => true,
_ => false,
}
}
pub fn is_ipv6(&self) -> bool {
trace!("Type::is_ipv6({:?})", self);
match self {
Type::Ipv6Addrs => true,
_ => false,
}
}
}
impl convert::Into<CtlType> for Type {
fn into(self: Type) -> CtlType {
trace!("Type::into::<CtlType>({:?})", self);
match self {
Type::String => CtlType::String,
Type::U8 => CtlType::U8,
Type::U16 => CtlType::U16,
Type::U32 => CtlType::U32,
Type::U64 => CtlType::U64,
Type::S8 => CtlType::S8,
Type::S16 => CtlType::S16,
Type::S32 => CtlType::S32,
Type::S64 => CtlType::S64,
Type::Int => CtlType::Int,
Type::Long => CtlType::Long,
Type::Uint => CtlType::Uint,
Type::Ulong => CtlType::Ulong,
Type::Ipv4Addrs => CtlType::Struct,
Type::Ipv6Addrs => CtlType::Struct,
}
}
}
#[derive(EnumDiscriminants, Clone, PartialEq, Eq, Debug, Hash)]
#[strum_discriminants(name(Type), derive(PartialOrd, Ord, Hash))]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum Value {
Int(libc::c_int),
String(String),
S64(i64),
Uint(libc::c_uint),
Long(libc::c_long),
Ulong(libc::c_ulong),
U64(u64),
U8(u8),
U16(u16),
S8(i8),
S16(i16),
S32(i32),
U32(u32),
Ipv4Addrs(Vec<net::Ipv4Addr>),
Ipv6Addrs(Vec<net::Ipv6Addr>),
}
impl Value {
pub fn get_type(&self) -> Type {
trace!("Value::get_type({:?})", self);
self.into()
}
pub fn as_bytes(self) -> Result<Vec<u8>, JailError> {
trace!("Value::as_bytes({:?})", self);
let mut bytes: Vec<u8> = vec![];
#[cfg_attr(feature = "cargo-clippy", allow(identity_conversion))]
match self {
Value::String(s) => {
bytes = CString::new(s)
.expect("Could not create CString from value")
.to_bytes_with_nul()
.to_vec();
Ok(())
}
Value::U8(v) => bytes.write_u8(v),
Value::S8(v) => bytes.write_i8(v),
Value::U16(v) => bytes.write_u16::<LittleEndian>(v),
Value::U32(v) => bytes.write_u32::<LittleEndian>(v),
Value::U64(v) => bytes.write_u64::<LittleEndian>(v),
Value::S16(v) => bytes.write_i16::<LittleEndian>(v),
Value::S32(v) => bytes.write_i32::<LittleEndian>(v),
Value::S64(v) => bytes.write_i64::<LittleEndian>(v),
Value::Int(v) => {
bytes.write_int::<LittleEndian>(v.into(), mem::size_of::<libc::c_int>())
}
Value::Long(v) => {
bytes.write_int::<LittleEndian>(v.into(), mem::size_of::<libc::c_long>())
}
Value::Uint(v) => {
bytes.write_uint::<LittleEndian>(v.into(), mem::size_of::<libc::c_uint>())
}
Value::Ulong(v) => {
bytes.write_uint::<LittleEndian>(v.into(), mem::size_of::<libc::c_ulong>())
}
Value::Ipv4Addrs(addrs) => {
for addr in addrs {
let s_addr = nix::sys::socket::Ipv4Addr::from_std(&addr).0.s_addr;
let host_u32 = u32::from_be(s_addr);
bytes
.write_u32::<NetworkEndian>(host_u32)
.map_err(|_| JailError::SerializeFailed)?;
}
Ok(())
}
Value::Ipv6Addrs(addrs) => {
for addr in addrs {
bytes.extend_from_slice(&addr.octets());
}
Ok(())
}
}
.map_err(|_| JailError::SerializeFailed)?;
Ok(bytes)
}
pub fn unpack_ipv4(self) -> Result<Vec<net::Ipv4Addr>, JailError> {
trace!("Value::unpack_ipv4({:?})", self);
match self {
Value::Ipv4Addrs(v) => Ok(v),
_ => Err(JailError::ParameterUnpackError),
}
}
pub fn unpack_ipv6(self) -> Result<Vec<net::Ipv6Addr>, JailError> {
trace!("Value::unpack_ipv6({:?})", self);
match self {
Value::Ipv6Addrs(v) => Ok(v),
_ => Err(JailError::ParameterUnpackError),
}
}
pub fn unpack_string(self) -> Result<String, JailError> {
trace!("Value::unpack_string({:?})", self);
match self {
Value::String(v) => Ok(v),
_ => Err(JailError::ParameterUnpackError),
}
}
pub fn unpack_u64(self) -> Result<u64, JailError> {
trace!("Value::unpack_u64({:?})", self);
#[cfg_attr(feature = "cargo-clippy", allow(identity_conversion))]
match self {
Value::U64(v) => Ok(v),
Value::U32(v) => Ok(v.into()),
Value::U16(v) => Ok(v.into()),
Value::U8(v) => Ok(v.into()),
Value::Uint(v) => Ok(v.into()),
Value::Ulong(v) => Ok(v.into()),
_ => Err(JailError::ParameterUnpackError),
}
}
pub fn unpack_i64(self) -> Result<i64, JailError> {
trace!("Value::unpack_i64({:?})", self);
#[cfg_attr(feature = "cargo-clippy", allow(identity_conversion))]
match self {
Value::S64(v) => Ok(v),
Value::S32(v) => Ok(v.into()),
Value::S16(v) => Ok(v.into()),
Value::S8(v) => Ok(v.into()),
Value::U32(v) => Ok(v.into()),
Value::U16(v) => Ok(v.into()),
Value::U8(v) => Ok(v.into()),
Value::Uint(v) => Ok(v.into()),
Value::Int(v) => Ok(v.into()),
Value::Long(v) => Ok(v.into()),
_ => Err(JailError::ParameterUnpackError),
}
}
}
#[cfg(target_os = "freebsd")]
fn info(name: &str) -> Result<(CtlType, CtlFlags, usize), JailError> {
trace!("info({:?})", name);
let ctlname = format!("security.jail.param.{}", name);
let ctl = Ctl::new(&ctlname).map_err(|_| JailError::NoSuchParameter(name.to_string()))?;
let flags = ctl.flags().map_err(JailError::SysctlError)?;
let paramtype = ctl.value_type().map_err(JailError::ParameterTypeError)?;
let typesize = match paramtype {
CtlType::Int => mem::size_of::<libc::c_int>(),
CtlType::String => {
let length = match ctl.value().map_err(JailError::ParameterStringLengthError)? {
CtlValue::String(l) => l,
_ => panic!("param sysctl reported to be string, but isn't"),
};
length
.parse::<usize>()
.map_err(|_| JailError::ParameterLengthNaN(length.to_string()))?
}
CtlType::S64 => mem::size_of::<i64>(),
CtlType::Uint => mem::size_of::<libc::c_uint>(),
CtlType::Long => mem::size_of::<libc::c_long>(),
CtlType::Ulong => mem::size_of::<libc::c_ulong>(),
CtlType::U64 => mem::size_of::<u64>(),
CtlType::U8 => mem::size_of::<u8>(),
CtlType::U16 => mem::size_of::<u16>(),
CtlType::S8 => mem::size_of::<i8>(),
CtlType::S16 => mem::size_of::<i16>(),
CtlType::S32 => mem::size_of::<i32>(),
CtlType::U32 => mem::size_of::<u32>(),
CtlType::Struct => match ctl.value().map_err(JailError::ParameterStructLengthError)? {
CtlValue::Struct(data) => {
assert!(
data.len() >= mem::size_of::<usize>(),
"Error: struct sysctl returned too few bytes."
);
LittleEndian::read_uint(&data, mem::size_of::<usize>()) as usize
}
_ => panic!("param sysctl reported to be struct, but isn't"),
},
_ => return Err(JailError::ParameterTypeUnsupported(paramtype)),
};
Ok((paramtype, flags, typesize))
}
#[cfg(target_os = "freebsd")]
fn ctltype_to_type(name: &str, ctl_type: CtlType) -> Result<Type, JailError> {
trace!("ctltype_to_type({:?}, ctl_type={:?})", name, ctl_type);
let param_type = match ctl_type {
CtlType::Int => Type::Int,
CtlType::S64 => Type::S64,
CtlType::Uint => Type::Uint,
CtlType::Long => Type::Long,
CtlType::Ulong => Type::Ulong,
CtlType::U64 => Type::U64,
CtlType::U8 => Type::U8,
CtlType::U16 => Type::U16,
CtlType::S8 => Type::S8,
CtlType::S16 => Type::S16,
CtlType::S32 => Type::S32,
CtlType::U32 => Type::U32,
CtlType::String => Type::String,
CtlType::Struct => match name {
"ip4.addr" => Type::Ipv4Addrs,
"ip6.addr" => Type::Ipv6Addrs,
_ => return Err(JailError::ParameterTypeUnsupported(ctl_type)),
},
_ => return Err(JailError::ParameterTypeUnsupported(ctl_type)),
};
Ok(param_type)
}
#[cfg(target_os = "freebsd")]
pub fn get(jid: i32, name: &str) -> Result<Value, JailError> {
trace!("get(jid={}, name={:?})", jid, name);
let (paramtype, _, typesize) = info(name)?;
let jail_max_af_ips = match Ctl::new("security.jail.jail_max_af_ips")
.map_err(JailError::JailMaxAfIpsFailed)?
.value()
.map_err(JailError::JailMaxAfIpsFailed)?
{
CtlValue::Uint(u) => u as usize,
_ => panic!("security.jail.jail_max_af_ips has the wrong type."),
};
let valuesize = match name {
"ip4.addr" => typesize * jail_max_af_ips,
"ip6.addr" => typesize * jail_max_af_ips,
_ => typesize,
};
let paramname = CString::new(name).expect("Could not convert parameter name to CString");
let mut value: Vec<u8> = vec![0; valuesize];
let mut errmsg: [u8; 256] = unsafe { mem::zeroed() };
let mut jiov: Vec<libc::iovec> = vec![
iovec!(b"jid\0"),
iovec!(&jid as *const _, mem::size_of::<i32>()),
iovec!(paramname.as_ptr(), paramname.as_bytes().len() + 1),
iovec!(value.as_mut_ptr(), valuesize),
iovec!(b"errmsg\0"),
iovec!(errmsg.as_mut_ptr(), errmsg.len()),
];
let jid = unsafe {
libc::jail_get(
jiov[..].as_mut_ptr() as *mut libc::iovec,
jiov.len() as u32,
JailFlags::empty().bits(),
)
};
let err = unsafe { CStr::from_ptr(errmsg.as_ptr() as *mut i8) }
.to_string_lossy()
.to_string();
let value = match jid {
e if e < 0 => match errmsg[0] {
0 => Err(JailError::from_errno()),
_ => Err(JailError::JailGetError(err)),
},
_ => Ok(value),
}?;
match ctltype_to_type(name, paramtype)? {
Type::Int => Ok(Value::Int(
LittleEndian::read_int(&value, mem::size_of::<libc::c_int>()) as libc::c_int,
)),
Type::S64 => Ok(Value::S64(LittleEndian::read_i64(&value))),
Type::Uint => Ok(Value::Uint(
LittleEndian::read_uint(&value, mem::size_of::<libc::c_uint>()) as libc::c_uint,
)),
Type::Long => Ok(Value::Long(
LittleEndian::read_int(&value, mem::size_of::<libc::c_long>()) as libc::c_long,
)),
Type::Ulong => Ok(Value::Ulong(LittleEndian::read_uint(
&value,
mem::size_of::<libc::c_ulong>(),
) as libc::c_ulong)),
Type::U64 => Ok(Value::U64(LittleEndian::read_u64(&value))),
Type::U8 => Ok(Value::U8(value[0])),
Type::U16 => Ok(Value::U16(LittleEndian::read_u16(&value))),
Type::S8 => Ok(Value::S8(value[0] as i8)),
Type::S16 => Ok(Value::S16(LittleEndian::read_i16(&value))),
Type::S32 => Ok(Value::S32(LittleEndian::read_i32(&value))),
Type::U32 => Ok(Value::U32(LittleEndian::read_u32(&value))),
Type::String => Ok(Value::String({
unsafe { CStr::from_ptr(value.as_ptr() as *mut i8) }
.to_string_lossy()
.into_owned()
})),
Type::Ipv4Addrs => {
let addrsize = mem::size_of::<libc::in_addr>();
let count = valuesize / addrsize;
assert_eq!(
0,
typesize % addrsize,
"Error: memory size mismatch. Length of data \
retrieved is not a multiple of the size of in_addr."
);
#[cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
let ips: Vec<net::Ipv4Addr> =
unsafe { slice::from_raw_parts(value.as_ptr() as *const libc::in_addr, count) }
.iter()
.map(|in_addr| u32::from_be(in_addr.s_addr))
.map(net::Ipv4Addr::from)
.filter(|ip| !ip.is_unspecified())
.collect();
Ok(Value::Ipv4Addrs(ips))
}
Type::Ipv6Addrs => {
let addrsize = mem::size_of::<libc::in6_addr>();
let count = valuesize / addrsize;
assert_eq!(
0,
typesize % addrsize,
"Error: memory size mismatch. Length of data \
retrieved is not a multiple of the size of in_addr."
);
#[cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
let ips: Vec<net::Ipv6Addr> =
unsafe { slice::from_raw_parts(value.as_ptr() as *const libc::in6_addr, count) }
.iter()
.map(|in6_addr| net::Ipv6Addr::from(in6_addr.s6_addr))
.filter(|ip| !ip.is_unspecified())
.collect();
Ok(Value::Ipv6Addrs(ips))
}
}
}
pub fn set(jid: i32, name: &str, value: Value) -> Result<(), JailError> {
trace!("set(jid={}, name={:?}, value={:?})", jid, name, value);
let (ctltype, ctl_flags, _) = info(name)?;
if ctl_flags.contains(CtlFlags::TUN) {
return Err(JailError::ParameterTunableError(name.into()));
}
let paramname = CString::new(name).expect("Could not convert parameter name to CString");
let mut errmsg: [u8; 256] = unsafe { mem::zeroed() };
let paramtype: Type = (&value).into();
assert_eq!(ctltype, paramtype.into());
let mut bytes = value.as_bytes()?;
let mut jiov: Vec<libc::iovec> = vec![
iovec!(b"jid\0"),
iovec!(&jid as *const _, mem::size_of::<i32>()),
iovec!(paramname.as_ptr(), paramname.as_bytes().len() + 1),
iovec!(bytes.as_mut_ptr(), bytes.len()),
iovec!(b"errmsg\0"),
iovec!(errmsg.as_mut_ptr(), errmsg.len()),
];
let jid = unsafe {
libc::jail_set(
jiov[..].as_mut_ptr() as *mut libc::iovec,
jiov.len() as u32,
JailFlags::UPDATE.bits(),
)
};
let err = unsafe { CStr::from_ptr(errmsg.as_ptr() as *mut i8) }
.to_string_lossy()
.to_string();
match jid {
e if e < 0 => match errmsg[0] {
0 => Err(JailError::from_errno()),
_ => Err(JailError::JailSetError(err)),
},
_ => Ok(()),
}
}
pub fn get_all(jid: i32) -> Result<HashMap<String, Value>, JailError> {
trace!("get_all(jid={})", jid);
let filtered_names = vec![
"jid",
"dying",
"parent",
"children.cur",
"cpuset.id",
"name",
"hostname",
"path",
"ip4.addr",
"ip6.addr",
];
let params: Result<Vec<(String, Value)>, JailError> = Ctl::new("security.jail.param")
.map_err(JailError::SysctlError)?
.into_iter()
.filter_map(Result::ok)
.map(|ctl| ctl.name())
.filter_map(Result::ok)
.filter(|name| name.starts_with("security.jail.param"))
.map(|string| string["security.jail.param.".len()..].to_string())
.filter(|name| {
!name.ends_with('.')
&& !filtered_names.contains(&name.as_str())
})
.map(|name| get(jid, &name).map(|v| (name, v)))
.collect();
Ok(HashMap::from_iter(params?))
}