#[macro_use]
extern crate failure;
extern crate libc;
extern crate nix;
extern crate number_prefix;
extern crate sysctl;
extern crate users;
#[cfg(feature = "serialize")]
#[macro_use]
extern crate serde;
pub use nix::sys::signal::Signal;
use number_prefix::{NumberPrefix, Prefix, Prefixed, Standalone};
use std::collections::HashMap;
use std::ffi::{CStr, CString, NulError};
use std::fmt;
use std::io;
use std::num;
use std::str;
#[cfg(feature = "serialize")]
use serde::Serializer;
const RCTL_DEFAULT_BUFSIZE: usize = 128 * 1024;
#[derive(Debug, Fail, PartialEq, Clone)]
pub enum ParseError {
#[fail(display = "Unknown subject type: {}", _0)]
UnknownSubjectType(String),
#[fail(display = "No such user: {}", _0)]
UnknownUser(String),
#[fail(display = "Unknown resource: {}", _0)]
UnknownResource(String),
#[fail(display = "Unknown action: {}", _0)]
UnknownAction(String),
#[fail(display = "Bogus data at end of limit: {}", _0)]
LimitBogusData(String),
#[fail(display = "Invalid limit literal: {}", _0)]
InvalidLimitLiteral(String),
#[fail(display = "Invalid numeric value: {}", _0)]
InvalidNumeral(#[cause] num::ParseIntError),
#[fail(display = "No subject specified")]
NoSubjectGiven,
#[fail(display = "Bogus data at end of subject: {}", _0)]
SubjectBogusData(String),
#[fail(display = "Invalid Rule syntax: '{}'", _0)]
InvalidRuleSyntax(String),
}
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "Parse Error: {}", _0)]
ParseError(#[cause] ParseError),
#[fail(display = "OS Error: {}", _0)]
OsError(#[cause] io::Error),
#[fail(
display = "An interior Nul byte was found while attempting to construct a CString: {}",
_0
)]
CStringError(#[cause] NulError),
#[fail(display = "The statistics returned by the kernel were invalid.")]
InvalidStatistics,
#[fail(display = "Invalid RCTL / RACCT kernel state: {}", _0)]
InvalidKernelState(State),
}
mod subject {
use super::ParseError;
use libc;
use std::fmt;
use users;
use users::{get_user_by_name, get_user_by_uid};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub struct User(pub users::uid_t);
impl User {
pub fn from_uid(uid: libc::uid_t) -> User {
User(uid as users::uid_t)
}
pub fn from_name(name: &str) -> Result<User, ParseError> {
let uid = get_user_by_name(name)
.ok_or_else(|| ParseError::UnknownUser(name.into()))?
.uid();
Ok(User::from_uid(uid))
}
}
impl fmt::Display for User {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match get_user_by_uid(self.0) {
Some(user) => write!(f, "user:{}", user.name().to_str().ok_or(fmt::Error)?),
None => write!(f, "user:{}", self.0),
}
}
}
impl<'a> Into<String> for &'a User {
fn into(self) -> String {
format!("user:{}", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub struct Process(pub libc::pid_t);
impl fmt::Display for Process {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "process:{}", self.0)
}
}
impl<'a> Into<String> for &'a Process {
fn into(self) -> String {
format!("{}", self)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub struct Jail(pub String);
impl fmt::Display for Jail {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "jail:{}", self.0)
}
}
impl<'a> Into<String> for &'a Jail {
fn into(self) -> String {
format!("{}", self)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub struct LoginClass(pub String);
impl fmt::Display for LoginClass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "loginclass:{}", self.0)
}
}
impl<'a> Into<String> for &'a LoginClass {
fn into(self) -> String {
format!("{}", self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_jail_name() {
assert_eq!(
format!("{}", Jail("testjail_rctl_name".into())),
"jail:testjail_rctl_name".to_string()
);
}
#[test]
fn display_user() {
assert_eq!(
format!("{}", User::from_name("nobody").expect("no nobody user")),
"user:nobody".to_string()
);
assert_eq!(format!("{}", User::from_uid(4242)), "user:4242".to_string());
}
#[test]
fn display_loginclass() {
assert_eq!(
format!("{}", LoginClass("test".into())),
"loginclass:test".to_string()
);
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub enum Subject {
Process(subject::Process),
Jail(subject::Jail),
User(subject::User),
LoginClass(subject::LoginClass),
}
impl Subject {
pub fn process_id(pid: libc::pid_t) -> Self {
Subject::Process(subject::Process(pid))
}
pub fn user_name(name: &str) -> Result<Self, ParseError> {
Ok(Subject::User(subject::User::from_name(name)?))
}
pub fn user_id(uid: libc::uid_t) -> Self {
Subject::User(subject::User::from_uid(uid))
}
pub fn login_class<S: Into<String>>(name: S) -> Self {
Subject::LoginClass(subject::LoginClass(name.into()))
}
pub fn jail_name<S: Into<String>>(name: S) -> Self {
Subject::Jail(subject::Jail(name.into()))
}
pub fn usage(&self) -> Result<HashMap<Resource, usize>, Error> {
extern "C" {
fn rctl_get_racct(
inbufp: *const libc::c_char,
inbuflen: libc::size_t,
outbufp: *mut libc::c_char,
outbuflen: libc::size_t,
) -> libc::c_int;
}
let filter = Filter::new().subject(self);
let rusage = rctl_api_wrapper(rctl_get_racct, &filter)?;
let mut map: HashMap<Resource, usize> = HashMap::new();
for statistic in rusage.split(',') {
let mut kv = statistic.split('=');
let resource = kv
.next()
.ok_or(Error::InvalidStatistics)?
.parse::<Resource>()
.map_err(Error::ParseError)?;
let value = kv
.next()
.ok_or(Error::InvalidStatistics)?
.parse::<usize>()
.map_err(ParseError::InvalidNumeral)
.map_err(Error::ParseError)?;
map.insert(resource, value);
}
Ok(map)
}
pub fn limits(&self) -> Result<RuleParsingIntoIter<String>, Error> {
extern "C" {
fn rctl_get_limits(
inbufp: *const libc::c_char,
inbuflen: libc::size_t,
outbufp: *mut libc::c_char,
outbuflen: libc::size_t,
) -> libc::c_int;
}
let outbuf = rctl_api_wrapper(rctl_get_limits, self)?;
Ok(RuleParsingIntoIter { inner: outbuf })
}
}
impl fmt::Display for Subject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Subject::Process(p) => write!(f, "{}", p),
Subject::User(u) => write!(f, "{}", u),
Subject::Jail(j) => write!(f, "{}", j),
Subject::LoginClass(c) => write!(f, "{}", c),
}
}
}
impl<'a> Into<String> for &'a Subject {
fn into(self) -> String {
match self {
Subject::Process(ref p) => p.into(),
Subject::User(ref u) => u.into(),
Subject::Jail(ref j) => j.into(),
Subject::LoginClass(ref c) => c.into(),
}
}
}
fn parse_process(s: &str) -> Result<Subject, ParseError> {
s.parse::<libc::pid_t>()
.map_err(ParseError::InvalidNumeral)
.map(Subject::process_id)
}
fn parse_user(s: &str) -> Result<Subject, ParseError> {
match s.parse::<libc::uid_t>() {
Ok(uid) => Ok(Subject::user_id(uid)),
Err(_) => Ok(Subject::user_name(s)?),
}
}
fn parse_jail(s: &str) -> Result<Subject, ParseError> {
Ok(Subject::jail_name(s))
}
fn parse_login_class(s: &str) -> Result<Subject, ParseError> {
Ok(Subject::login_class(s))
}
impl str::FromStr for Subject {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split(':').collect();
let subject_type = parts[0].parse::<SubjectType>()?;
match parts.len() {
1 => Err(ParseError::NoSubjectGiven),
2 => match subject_type {
SubjectType::Process => parse_process(parts[1]),
SubjectType::User => parse_user(parts[1]),
SubjectType::LoginClass => parse_login_class(parts[1]),
SubjectType::Jail => parse_jail(parts[1]),
},
_ => Err(ParseError::SubjectBogusData(format!(
":{}",
parts[2..].join(":")
))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub enum SubjectType {
Process,
Jail,
User,
LoginClass,
}
impl str::FromStr for SubjectType {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"process" => Ok(SubjectType::Process),
"jail" => Ok(SubjectType::Jail),
"user" => Ok(SubjectType::User),
"loginclass" => Ok(SubjectType::LoginClass),
_ => Err(ParseError::UnknownSubjectType(s.into())),
}
}
}
impl<'a> From<&'a Subject> for SubjectType {
fn from(subject: &'a Subject) -> Self {
match subject {
Subject::Process(_) => SubjectType::Process,
Subject::Jail(_) => SubjectType::Jail,
Subject::User(_) => SubjectType::User,
Subject::LoginClass(_) => SubjectType::LoginClass,
}
}
}
impl SubjectType {
pub fn as_str(&self) -> &'static str {
match self {
SubjectType::Process => "process",
SubjectType::Jail => "jail",
SubjectType::User => "user",
SubjectType::LoginClass => "loginclass",
}
}
}
impl<'a> Into<&'static str> for &'a SubjectType {
fn into(self) -> &'static str {
self.as_str()
}
}
impl<'a> Into<String> for &'a SubjectType {
fn into(self) -> String {
self.as_str().into()
}
}
impl fmt::Display for SubjectType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let r: &'static str = self.into();
write!(f, "{}", r)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub enum Resource {
CpuTime,
DataSize,
StackSize,
CoreDumpSize,
MemoryUse,
MemoryLocked,
MaxProcesses,
OpenFiles,
VMemoryUse,
PseudoTerminals,
SwapUse,
NThreads,
MsgqQueued,
MsgqSize,
NMsgq,
Nsem,
NSemop,
NShm,
ShmSize,
Wallclock,
PercentCpu,
ReadBps,
WriteBps,
ReadIops,
WriteIops,
}
impl Resource {
pub fn as_str(&self) -> &'static str {
match self {
Resource::CpuTime => "cputime",
Resource::DataSize => "datasize",
Resource::StackSize => "stacksize",
Resource::CoreDumpSize => "coredumpsize",
Resource::MemoryUse => "memoryuse",
Resource::MemoryLocked => "memorylocked",
Resource::MaxProcesses => "maxproc",
Resource::OpenFiles => "openfiles",
Resource::VMemoryUse => "vmemoryuse",
Resource::PseudoTerminals => "pseudoterminals",
Resource::SwapUse => "swapuse",
Resource::NThreads => "nthr",
Resource::MsgqQueued => "msgqqueued",
Resource::MsgqSize => "msgqsize",
Resource::NMsgq => "nmsgq",
Resource::Nsem => "nsem",
Resource::NSemop => "nsemop",
Resource::NShm => "nshm",
Resource::ShmSize => "shmsize",
Resource::Wallclock => "wallclock",
Resource::PercentCpu => "pcpu",
Resource::ReadBps => "readbps",
Resource::WriteBps => "writebps",
Resource::ReadIops => "readiops",
Resource::WriteIops => "writeiops",
}
}
}
impl str::FromStr for Resource {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"cputime" => Ok(Resource::CpuTime),
"datasize" => Ok(Resource::DataSize),
"stacksize" => Ok(Resource::StackSize),
"coredumpsize" => Ok(Resource::CoreDumpSize),
"memoryuse" => Ok(Resource::MemoryUse),
"memorylocked" => Ok(Resource::MemoryLocked),
"maxproc" => Ok(Resource::MaxProcesses),
"openfiles" => Ok(Resource::OpenFiles),
"vmemoryuse" => Ok(Resource::VMemoryUse),
"pseudoterminals" => Ok(Resource::PseudoTerminals),
"swapuse" => Ok(Resource::SwapUse),
"nthr" => Ok(Resource::NThreads),
"msgqqueued" => Ok(Resource::MsgqQueued),
"msgqsize" => Ok(Resource::MsgqSize),
"nmsgq" => Ok(Resource::NMsgq),
"nsem" => Ok(Resource::Nsem),
"nsemop" => Ok(Resource::NSemop),
"nshm" => Ok(Resource::NShm),
"shmsize" => Ok(Resource::ShmSize),
"wallclock" => Ok(Resource::Wallclock),
"pcpu" => Ok(Resource::PercentCpu),
"readbps" => Ok(Resource::ReadBps),
"writebps" => Ok(Resource::WriteBps),
"readiops" => Ok(Resource::ReadIops),
"writeiops" => Ok(Resource::WriteIops),
_ => Err(ParseError::UnknownResource(s.into())),
}
}
}
impl<'a> Into<&'static str> for &'a Resource {
fn into(self) -> &'static str {
self.as_str()
}
}
impl<'a> Into<String> for &'a Resource {
fn into(self) -> String {
self.as_str().into()
}
}
impl fmt::Display for Resource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let r: &'static str = self.into();
write!(f, "{}", r)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub enum Action {
Deny,
Log,
DevCtl,
#[cfg_attr(feature="serialize", serde(serialize_with = "signal_serialize"))]
Signal(Signal),
Throttle,
}
impl Action {
pub fn as_str(&self) -> &'static str {
match self {
Action::Deny => "deny",
Action::Log => "log",
Action::DevCtl => "devctl",
Action::Throttle => "throttle",
Action::Signal(sig) => match sig {
Signal::SIGHUP => "sighup",
Signal::SIGINT => "sigint",
Signal::SIGQUIT => "sigquit",
Signal::SIGILL => "sigill",
Signal::SIGTRAP => "sigtrap",
Signal::SIGABRT => "sigabrt",
Signal::SIGBUS => "sigbus",
Signal::SIGFPE => "sigfpe",
Signal::SIGKILL => "sigkill",
Signal::SIGUSR1 => "sigusr1",
Signal::SIGSEGV => "sigsegv",
Signal::SIGUSR2 => "sigusr2",
Signal::SIGPIPE => "sigpipe",
Signal::SIGALRM => "sigalrm",
Signal::SIGTERM => "sigterm",
Signal::SIGCHLD => "sigchld",
Signal::SIGCONT => "sigcont",
Signal::SIGSTOP => "sigstop",
Signal::SIGTSTP => "sigtstp",
Signal::SIGTTIN => "sigttin",
Signal::SIGTTOU => "sigttou",
Signal::SIGURG => "sigurg",
Signal::SIGXCPU => "sigxcpu",
Signal::SIGXFSZ => "sigxfsz",
Signal::SIGVTALRM => "sigvtalrm",
Signal::SIGPROF => "sigprof",
Signal::SIGWINCH => "sigwinch",
Signal::SIGIO => "sigio",
Signal::SIGSYS => "sigsys",
Signal::SIGEMT => "sigemt",
Signal::SIGINFO => "siginfo",
},
}
}
}
impl str::FromStr for Action {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"deny" => Ok(Action::Deny),
"log" => Ok(Action::Log),
"devctl" => Ok(Action::DevCtl),
"throttle" => Ok(Action::Throttle),
"sighup" => Ok(Action::Signal(Signal::SIGHUP)),
"sigint" => Ok(Action::Signal(Signal::SIGINT)),
"sigquit" => Ok(Action::Signal(Signal::SIGQUIT)),
"sigill" => Ok(Action::Signal(Signal::SIGILL)),
"sigtrap" => Ok(Action::Signal(Signal::SIGTRAP)),
"sigabrt" => Ok(Action::Signal(Signal::SIGABRT)),
"sigbus" => Ok(Action::Signal(Signal::SIGBUS)),
"sigfpe" => Ok(Action::Signal(Signal::SIGFPE)),
"sigkill" => Ok(Action::Signal(Signal::SIGKILL)),
"sigusr1" => Ok(Action::Signal(Signal::SIGUSR1)),
"sigsegv" => Ok(Action::Signal(Signal::SIGSEGV)),
"sigusr2" => Ok(Action::Signal(Signal::SIGUSR2)),
"sigpipe" => Ok(Action::Signal(Signal::SIGPIPE)),
"sigalrm" => Ok(Action::Signal(Signal::SIGALRM)),
"sigterm" => Ok(Action::Signal(Signal::SIGTERM)),
"sigchld" => Ok(Action::Signal(Signal::SIGCHLD)),
"sigcont" => Ok(Action::Signal(Signal::SIGCONT)),
"sigstop" => Ok(Action::Signal(Signal::SIGSTOP)),
"sigtstp" => Ok(Action::Signal(Signal::SIGTSTP)),
"sigttin" => Ok(Action::Signal(Signal::SIGTTIN)),
"sigttou" => Ok(Action::Signal(Signal::SIGTTOU)),
"sigurg" => Ok(Action::Signal(Signal::SIGURG)),
"sigxcpu" => Ok(Action::Signal(Signal::SIGXCPU)),
"sigxfsz" => Ok(Action::Signal(Signal::SIGXFSZ)),
"sigvtalrm" => Ok(Action::Signal(Signal::SIGVTALRM)),
"sigprof" => Ok(Action::Signal(Signal::SIGPROF)),
"sigwinch" => Ok(Action::Signal(Signal::SIGWINCH)),
"sigio" => Ok(Action::Signal(Signal::SIGIO)),
"sigsys" => Ok(Action::Signal(Signal::SIGSYS)),
"sigemt" => Ok(Action::Signal(Signal::SIGEMT)),
"siginfo" => Ok(Action::Signal(Signal::SIGINFO)),
_ => Err(ParseError::UnknownAction(s.into())),
}
}
}
impl<'a> Into<&'static str> for &'a Action {
fn into(self) -> &'static str {
self.as_str()
}
}
impl<'a> Into<String> for &'a Action {
fn into(self) -> String {
self.as_str().into()
}
}
impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(feature="serialize")]
fn signal_serialize<S>(signal: &Signal, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let sig_str = format!("{:?}", signal);
s.serialize_str(&sig_str)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub struct Limit {
amount: usize,
per: Option<SubjectType>,
}
impl Limit {
pub fn amount(amount: usize) -> Limit {
Limit { amount, per: None }
}
pub fn amount_per(amount: usize, per: SubjectType) -> Limit {
Limit {
amount,
per: Some(per),
}
}
pub fn to_string(&self) -> String {
format!("{}", self)
}
}
fn parse_limit_with_suffix(s: &str) -> Result<usize, ParseError> {
let s = s.trim().to_lowercase();
if let Ok(v) = s.parse::<usize>() {
return Ok(v);
}
let suffixes = ["k", "m", "g", "t", "p", "e", "z", "y"];
for (i, suffix) in suffixes.iter().enumerate() {
match s
.split(suffix)
.next()
.expect("could not split the suffix off")
.parse::<usize>()
{
Err(_) => continue,
Ok(v) => return Ok(v * 1024usize.pow((i + 1) as u32)),
};
}
Err(ParseError::InvalidLimitLiteral(s.into()))
}
impl str::FromStr for Limit {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split('/').collect();
let val = parse_limit_with_suffix(parts[0])?;
match parts.len() {
1 => Ok(Limit::amount(val)),
2 => Ok(Limit::amount_per(val, parts[1].parse::<SubjectType>()?)),
_ => Err(ParseError::LimitBogusData(format!(
"/{}",
parts[2..].join("/")
))),
}
}
}
impl fmt::Display for Limit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let amount = match NumberPrefix::binary(self.amount as f64) {
Standalone(amt) => format!("{}", amt),
Prefixed(prefix, amt) => {
let prefix = match prefix {
Prefix::Kibi => "k",
Prefix::Mibi => "m",
Prefix::Gibi => "g",
Prefix::Tebi => "t",
Prefix::Pebi => "p",
Prefix::Exbi => "e",
Prefix::Zebi => "z",
Prefix::Yobi => "y",
_ => panic!("called binary_prefix but got decimal prefix"),
};
format!("{}{}", amt, prefix)
}
};
let per = match &self.per {
Some(ref s) => format!("/{}", s),
None => "".to_string(),
};
write!(f, "{}{}", amount, per)
}
}
impl<'a> Into<String> for &'a Limit {
fn into(self) -> String {
let per = match &self.per {
Some(ref s) => format!("/{}", s),
None => "".to_string(),
};
format!("{}{}", self.amount, per)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature="serialize", derive(Serialize))]
pub struct Rule {
pub subject: Subject,
pub resource: Resource,
pub limit: Limit,
pub action: Action,
}
impl Rule {
pub fn to_string(&self) -> String {
format!("{}", self)
}
pub fn apply(&self) -> Result<(), Error> {
extern "C" {
fn rctl_add_rule(
inbufp: *const libc::c_char,
inbuflen: libc::size_t,
outbufp: *mut libc::c_char,
outbuflen: libc::size_t,
) -> libc::c_int;
}
rctl_api_wrapper(rctl_add_rule, self)?;
Ok(())
}
pub fn remove(&self) -> Result<(), Error> {
let filter: Filter = self.into();
filter.remove_rules()
}
}
impl fmt::Display for Rule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}:{}:{}={}",
self.subject, self.resource, self.action, self.limit
)
}
}
impl<'a> Into<String> for &'a Rule {
fn into(self) -> String {
let subject: String = (&self.subject).into();
let resource: &str = (&self.resource).into();
let action: &str = (&self.action).into();
let limit: String = (&self.limit).into();
format!("{}:{}:{}={}", subject, resource, action, limit)
}
}
impl str::FromStr for Rule {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split(':').collect();
if parts.len() != 4 {
return Err(ParseError::InvalidRuleSyntax(s.into()));
}
let subject = format!("{}:{}", parts[0], parts[1]).parse::<Subject>()?;
let resource = parts[2].parse::<Resource>()?;
let parts: Vec<_> = parts[3].split('=').collect();
if parts.len() != 2 {
return Err(ParseError::InvalidRuleSyntax(s.into()));
}
let action = parts[0].parse::<Action>()?;
let limit = parts[1].parse::<Limit>()?;
Ok(Rule {
subject,
resource,
action,
limit,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RuleParserAdapter<I> {
inner: I,
}
impl<'a, I> Iterator for RuleParserAdapter<I>
where
I: Iterator<Item = &'a str>,
{
type Item = Rule;
fn next(&mut self) -> Option<Rule> {
match self.inner.next() {
Some(item) => item.parse::<Rule>().ok(),
None => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RuleParsingIntoIter<S> {
inner: S,
}
impl<'a> IntoIterator for &'a RuleParsingIntoIter<String> {
type Item = Rule;
type IntoIter = RuleParserAdapter<str::Split<'a, &'a str>>;
fn into_iter(self) -> Self::IntoIter {
RuleParserAdapter {
inner: self.inner.split(","),
}
}
}
trait RuleParsingExt<'a>: Sized {
fn parse_rules(self) -> RuleParserAdapter<Self>;
}
impl<'a> RuleParsingExt<'a> for str::Split<'a, &'a str> {
fn parse_rules(self) -> RuleParserAdapter<Self> {
RuleParserAdapter { inner: self }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Filter {
subject_type: Option<SubjectType>,
subject: Option<Subject>,
resource: Option<Resource>,
limit: Option<Limit>,
action: Option<Action>,
limit_per: Option<SubjectType>,
}
impl Filter {
pub fn new() -> Filter {
Filter {
subject_type: None,
subject: None,
resource: None,
limit: None,
action: None,
limit_per: None,
}
}
pub fn subject_type(mut self: Filter, subject_type: &SubjectType) -> Filter {
if self.subject.is_none() {
self.subject_type = Some(*subject_type);
}
self
}
pub fn subject(mut self: Filter, subject: &Subject) -> Filter {
self.subject = Some(subject.clone());
self.subject_type = None;
self
}
pub fn resource(mut self: Filter, resource: &Resource) -> Filter {
self.resource = Some(*resource);
self
}
pub fn action(mut self: Filter, action: &Action) -> Filter {
self.action = Some(*action);
self
}
pub fn deny(self: Filter) -> Filter {
self.action(&Action::Deny)
}
pub fn log(self: Filter) -> Filter {
self.action(&Action::Log)
}
pub fn devctl(self: Filter) -> Filter {
self.action(&Action::DevCtl)
}
pub fn signal(self: Filter, signal: Signal) -> Filter {
self.action(&Action::Signal(signal))
}
pub fn limit(mut self: Filter, limit: &Limit) -> Filter {
let mut limit = limit.clone();
if let (Some(limit_per), None) = (self.limit_per, limit.per) {
limit.per = Some(limit_per);
}
self.limit_per = None;
self.limit = Some(limit);
self
}
fn sanity(&self) {
if let (Some(ref subject), Some(ref subject_type)) = (&self.subject, &self.subject_type) {
let actual_type: SubjectType = subject.into();
assert_eq!(&actual_type, subject_type);
}
}
pub fn to_string(&self) -> String {
format!("{}", self)
}
pub fn rules(&self) -> Result<RuleParsingIntoIter<String>, Error> {
extern "C" {
fn rctl_get_rules(
inbufp: *const libc::c_char,
inbuflen: libc::size_t,
outbufp: *mut libc::c_char,
outbuflen: libc::size_t,
) -> libc::c_int;
}
let outbuf = rctl_api_wrapper(rctl_get_rules, self)?;
Ok(RuleParsingIntoIter { inner: outbuf })
}
pub fn remove_rules(&self) -> Result<(), Error> {
extern "C" {
fn rctl_remove_rule(
inbufp: *const libc::c_char,
inbuflen: libc::size_t,
outbufp: *mut libc::c_char,
outbuflen: libc::size_t,
) -> libc::c_int;
}
rctl_api_wrapper(rctl_remove_rule, self)?;
Ok(())
}
}
impl fmt::Display for Filter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.sanity();
match self {
Filter {
subject_type: Some(s),
..
} => write!(f, "{}:", s),
Filter {
subject_type: None,
subject: Some(ref s),
..
} => write!(f, "{}", s),
Filter {
subject_type: None,
subject: None,
..
} => write!(f, ":"),
}?;
if let Filter {
resource: None,
action: None,
limit: None,
limit_per: None,
..
} = self
{
return Ok(());
}
match &self.resource {
Some(resource) => write!(f, ":{}", resource),
None => write!(f, ":"),
}?;
if let Filter {
action: None,
limit: None,
limit_per: None,
..
} = self
{
return Ok(());
}
match &self.action {
Some(action) => write!(f, ":{}", action),
None => write!(f, ":"),
}?;
if let Filter {
limit: None,
limit_per: None,
..
} = self
{
return Ok(());
}
match &self.limit {
Some(limit) => write!(f, "={}", limit),
None => write!(
f,
"=/{}",
self.limit_per.expect("could not unwrap limit_per")
),
}
}
}
impl<'a> Into<String> for &'a Filter {
fn into(self) -> String {
let subject: String = match self.subject {
Some(ref s) => s.into(),
None => ":".into(),
};
let resource: &str = match self.resource {
Some(ref r) => r.into(),
None => "",
};
let action: &str = match self.action {
Some(ref a) => a.into(),
None => "",
};
let limit: String = match self.limit {
Some(ref l) => l.into(),
None => "".into(),
};
format!("{}:{}:{}={}", subject, resource, action, limit)
}
}
impl From<Rule> for Filter {
fn from(rule: Rule) -> Self {
Filter {
subject_type: None,
subject: Some(rule.subject),
resource: Some(rule.resource),
limit: Some(rule.limit),
limit_per: None,
action: Some(rule.action),
}
}
}
impl<'a> From<&'a Rule> for Filter {
fn from(rule: &'a Rule) -> Self {
let rule = rule.clone();
Filter {
subject_type: None,
subject: Some(rule.subject),
resource: Some(rule.resource),
limit: Some(rule.limit),
limit_per: None,
action: Some(rule.action),
}
}
}
impl<'a> From<&'a Subject> for Filter {
fn from(subject: &'a Subject) -> Self {
Filter::new().subject(subject)
}
}
impl From<Subject> for Filter {
fn from(subject: Subject) -> Self {
Filter::new().subject(&subject)
}
}
impl<'a> From<&'a SubjectType> for Filter {
fn from(subject_type: &'a SubjectType) -> Self {
Filter::new().subject_type(subject_type)
}
}
impl From<SubjectType> for Filter {
fn from(subject_type: SubjectType) -> Self {
Filter::new().subject_type(&subject_type)
}
}
impl<'a> From<&'a Action> for Filter {
fn from(action: &'a Action) -> Self {
Filter::new().action(action)
}
}
impl From<Action> for Filter {
fn from(action: Action) -> Self {
Filter::new().action(&action)
}
}
impl<'a> From<&'a Limit> for Filter {
fn from(limit: &'a Limit) -> Self {
Filter::new().limit(limit)
}
}
impl From<Limit> for Filter {
fn from(limit: Limit) -> Self {
Filter::new().limit(&limit)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum State {
Disabled,
Enabled,
NotPresent,
Jailed,
}
impl State {
pub fn check() -> State {
let jailed = sysctl::Ctl::new("security.jail.jailed");
if jailed.is_err() {
return State::Jailed;
}
match jailed.unwrap().value() {
Ok(sysctl::CtlValue::Int(0)) => {}
_ => return State::Jailed,
};
let enable_racct = sysctl::Ctl::new("kern.racct.enable");
if enable_racct.is_err() {
return State::NotPresent;
}
match enable_racct.unwrap().value() {
Ok(sysctl::CtlValue::Uint(v)) => match v {
1 => State::Enabled,
_ => State::Disabled,
},
_ => State::NotPresent,
}
}
pub fn is_enabled(&self) -> bool {
match self {
State::Enabled => true,
_ => false,
}
}
pub fn is_present(&self) -> bool {
match self {
State::NotPresent => false,
_ => true,
}
}
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
State::Enabled => write!(f, "enabled"),
State::Disabled => write!(f, "disabled"),
State::NotPresent => write!(f, "not present"),
State::Jailed => write!(f, "not available in a jail"),
}
}
}
fn rctl_api_wrapper<S: Into<String>>(
api: unsafe extern "C" fn(*const libc::c_char, libc::size_t, *mut libc::c_char, libc::size_t)
-> libc::c_int,
input: S,
) -> Result<String, Error> {
let input: String = input.into();
let inputlen = input.len() + 1;
let inbuf = CString::new(input).map_err(Error::CStringError)?;
let mut outbuf: Vec<i8> = vec![0; RCTL_DEFAULT_BUFSIZE];
loop {
if unsafe { api(inbuf.as_ptr(), inputlen, outbuf.as_mut_ptr(), outbuf.len()) } != 0 {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::ERANGE) => {
let current_len = outbuf.len();
outbuf.resize(current_len + RCTL_DEFAULT_BUFSIZE, 0);
continue;
}
Some(libc::EPERM) => {
let state = State::check();
break match state.is_enabled() {
true => Err(Error::OsError(err)),
false => Err(Error::InvalidKernelState(State::check())),
};
}
Some(libc::ENOSYS) => break Err(Error::InvalidKernelState(State::check())),
Some(libc::ESRCH) => break Ok("".into()),
_ => break Err(Error::OsError(err)),
}
}
break Ok(unsafe { CStr::from_ptr(outbuf.as_ptr() as *mut i8) }
.to_string_lossy()
.into());
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn parse_subject_type() {
assert_eq!(
"user"
.parse::<SubjectType>()
.expect("could not parse user subject type"),
SubjectType::User,
);
assert_eq!(
"jail"
.parse::<SubjectType>()
.expect("could not parse jail subject type"),
SubjectType::Jail,
);
assert!("bogus".parse::<SubjectType>().is_err());
}
#[test]
fn parse_subject() {
assert_eq!(
"user:42"
.parse::<Subject>()
.expect("Could not parse 'user:42' as Subject"),
Subject::user_id(42)
);
assert_eq!(
"user:nobody"
.parse::<Subject>()
.expect("Could not parse 'user:nobody' as Subject"),
Subject::user_name("nobody").expect("no user 'nobody'")
);
assert_eq!(
"process:42"
.parse::<Subject>()
.expect("Could not parse 'process:42' as Subject"),
Subject::process_id(42)
);
assert_eq!(
"jail:www"
.parse::<Subject>()
.expect("Could not parse 'jail:www' as Subject"),
Subject::jail_name("www")
);
assert_eq!(
"loginclass:test"
.parse::<Subject>()
.expect("Could not parse 'loginclass:test' as Subject"),
Subject::login_class("test")
);
assert!("".parse::<Subject>().is_err());
assert!(":".parse::<Subject>().is_err());
assert!(":1234".parse::<Subject>().is_err());
assert!("bogus".parse::<Subject>().is_err());
assert!("user".parse::<Subject>().is_err());
assert!("process:bogus".parse::<Subject>().is_err());
assert!("process:".parse::<Subject>().is_err());
assert!("user:test:bogus".parse::<Subject>().is_err());
}
#[test]
fn parse_resource() {
assert_eq!(
"vmemoryuse"
.parse::<Resource>()
.expect("could not parse vmemoryuse resource"),
Resource::VMemoryUse,
);
assert!("bogus".parse::<Resource>().is_err());
}
#[test]
fn parse_action() {
assert_eq!(
"deny"
.parse::<Action>()
.expect("could not parse deny action"),
Action::Deny
);
assert_eq!(
"log".parse::<Action>().expect("could not parse log action"),
Action::Log
);
assert_eq!(
"throttle"
.parse::<Action>()
.expect("could not parse throttle action"),
Action::Throttle
);
assert_eq!(
"devctl"
.parse::<Action>()
.expect("could not parse devctl action"),
Action::DevCtl
);
assert_eq!(
"sigterm"
.parse::<Action>()
.expect("could not parse sigterm action"),
Action::Signal(Signal::SIGTERM)
);
assert!("bogus".parse::<Action>().is_err());
}
#[test]
fn display_limit() {
assert_eq!(
Limit {
amount: 100 * 1024 * 1024,
per: None
}.to_string(),
"100m".to_string()
);
assert_eq!(
Limit {
amount: 100 * 1024 * 1024,
per: Some(SubjectType::User)
}.to_string(),
"100m/user".to_string()
);
assert_eq!(
Limit {
amount: 42,
per: Some(SubjectType::LoginClass)
}.to_string(),
"42/loginclass".to_string()
);
}
#[test]
fn parse_limit() {
assert_eq!(
"100m"
.parse::<Limit>()
.expect("Could not parse '100m' as Limit"),
Limit::amount(100 * 1024 * 1024),
);
assert_eq!(
"100m/user"
.parse::<Limit>()
.expect("Could not parse '100m/user' as Limit"),
Limit::amount_per(100 * 1024 * 1024, SubjectType::User),
);
assert!("100m/bogus".parse::<Limit>().is_err());
assert!("100m/userbogus".parse::<Limit>().is_err());
assert!("100q".parse::<Limit>().is_err());
assert!("-42".parse::<Limit>().is_err());
assert!("".parse::<Limit>().is_err());
assert!("bogus".parse::<Limit>().is_err());
}
#[test]
fn parse_rule() {
assert_eq!(
"user:nobody:vmemoryuse:deny=1g"
.parse::<Rule>()
.expect("Could not parse 'user:nobody:vmemoryuse:deny=1g' as Rule"),
Rule {
subject: Subject::user_name("nobody").expect("no user 'nobody'"),
resource: Resource::VMemoryUse,
action: Action::Deny,
limit: Limit::amount(1 * 1024 * 1024 * 1024),
}
);
assert!(":::=/".parse::<Rule>().is_err());
assert!("user:missing_resource:=100m/user".parse::<Rule>().is_err());
assert!("user:missing_resource=100m/user".parse::<Rule>().is_err());
assert!(
"user:too:many:colons:vmemoryuse:deny=100m/user"
.parse::<Rule>()
.is_err()
);
assert!(
"loginclass:nolimit:vmemoryuse:deny="
.parse::<Rule>()
.is_err()
);
assert!(
"loginclass:nolimit:vmemoryuse:deny"
.parse::<Rule>()
.is_err()
);
assert!(
"loginclass:equals:vmemoryuse:deny=123=456"
.parse::<Rule>()
.is_err()
);
assert!("-42".parse::<Rule>().is_err());
assert!("".parse::<Rule>().is_err());
assert!("bogus".parse::<Rule>().is_err());
}
#[test]
fn display_filter() {
assert_eq!(Filter::new().to_string(), ":".to_string());
assert_eq!(
Filter::new()
.subject_type(&SubjectType::LoginClass)
.to_string(),
"loginclass:".to_string()
);
assert_eq!(
Filter::new().subject(&Subject::user_id(42)).to_string(),
"user:42".to_string()
);
assert_eq!(
Filter::new().resource(&Resource::MaxProcesses).to_string(),
"::maxproc".to_string()
);
assert_eq!(
Filter::new()
.subject(&Subject::user_id(42))
.resource(&Resource::MemoryUse)
.to_string(),
"user:42:memoryuse".to_string()
);
assert_eq!(Filter::new().deny().to_string(), ":::deny".to_string());
}
#[test]
fn iterate_rules() {
if !State::check().is_enabled() {
return;
}
let common_subject = Subject::jail_name("testjail_rctl_rules");
let rule1 = Rule {
subject: common_subject.clone(),
resource: Resource::VMemoryUse,
action: Action::Log,
limit: Limit::amount(100 * 1024 * 1024),
};
rule1.apply().expect("Could not apply rule 1");
let rule2 = Rule {
subject: common_subject.clone(),
resource: Resource::StackSize,
action: Action::Log,
limit: Limit::amount(100 * 1024 * 1024),
};
rule2.apply().expect("Could not apply rule 2");
let filter = Filter::new().subject(&common_subject);
let rules: HashSet<_> = filter
.rules()
.expect("Could not get rules matching filter")
.into_iter()
.collect();
assert!(rules.contains(&rule1));
assert!(rules.contains(&rule2));
filter.remove_rules().expect("Could not remove rules");
}
#[cfg(feature="serialize")]
#[test]
fn serialize_rule() {
let rule = "process:23:vmemoryuse:sigterm=100m"
.parse::<Rule>()
.expect("Could not parse rule");
let serialized = serde_json::to_string(&rule)
.expect("Could not serialize rule");
let rule_map: serde_json::Value = serde_json::from_str(&serialized)
.expect("Could not load serialized rule");
assert_eq!(rule_map["subject"]["Process"], 23);
assert_eq!(rule_map["resource"], "VMemoryUse");
assert_eq!(rule_map["action"]["Signal"], "SIGTERM");
assert_eq!(rule_map["limit"]["amount"], 100 * 1024 * 1024)
}
}